cmd.exe: Enhance FOR support.
[wine.git] / programs / cmd / builtins.c
blob027f2bdda8f34d8eb936a4f6991f22715fe37855
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * NOTES:
23 * On entry to each function, global variables quals, param1, param2 contain
24 * the qualifiers (uppercased and concatenated) and parameters entered, with
25 * environment-variable and batch parameter substitution already done.
29 * FIXME:
30 * - No support for pipes, shell parameters
31 * - Lots of functionality missing from builtins
32 * - Messages etc need international support
35 #define WIN32_LEAN_AND_MEAN
37 #include "wcmd.h"
38 #include <shellapi.h>
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
44 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
46 struct env_stack *saved_environment;
47 struct env_stack *pushd_directories;
49 extern HINSTANCE hinst;
50 extern WCHAR inbuilt[][10];
51 extern int echo_mode, verify_mode, defaultColor;
52 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
53 extern BATCH_CONTEXT *context;
54 extern DWORD errorlevel;
56 static const WCHAR dotW[] = {'.','\0'};
57 static const WCHAR dotdotW[] = {'.','.','\0'};
58 static const WCHAR slashW[] = {'\\','\0'};
59 static const WCHAR starW[] = {'*','\0'};
60 static const WCHAR equalW[] = {'=','\0'};
61 static const WCHAR fslashW[] = {'/','\0'};
62 static const WCHAR onW[] = {'O','N','\0'};
63 static const WCHAR offW[] = {'O','F','F','\0'};
64 static const WCHAR parmY[] = {'/','Y','\0'};
65 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
66 static const WCHAR nullW[] = {'\0'};
68 /****************************************************************************
69 * WCMD_clear_screen
71 * Clear the terminal screen.
74 void WCMD_clear_screen (void) {
76 /* Emulate by filling the screen from the top left to bottom right with
77 spaces, then moving the cursor to the top left afterwards */
78 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
79 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
81 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
83 COORD topLeft;
84 DWORD screenSize;
86 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
88 topLeft.X = 0;
89 topLeft.Y = 0;
90 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
91 SetConsoleCursorPosition(hStdOut, topLeft);
95 /****************************************************************************
96 * WCMD_change_tty
98 * Change the default i/o device (ie redirect STDin/STDout).
101 void WCMD_change_tty (void) {
103 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
107 /****************************************************************************
108 * WCMD_copy
110 * Copy a file or wildcarded set.
111 * FIXME: Add support for a+b+c type syntax
114 void WCMD_copy (void) {
116 WIN32_FIND_DATA fd;
117 HANDLE hff;
118 BOOL force, status;
119 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
120 DWORD len;
121 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122 BOOL copyToDir = FALSE;
123 BOOL copyFromDir = FALSE;
124 WCHAR srcspec[MAX_PATH];
125 DWORD attribs;
126 WCHAR drive[10];
127 WCHAR dir[MAX_PATH];
128 WCHAR fname[MAX_PATH];
129 WCHAR ext[MAX_PATH];
131 if (param1[0] == 0x00) {
132 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
133 return;
136 /* Convert source into full spec */
137 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
138 GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
139 if (srcpath[strlenW(srcpath) - 1] == '\\')
140 srcpath[strlenW(srcpath) - 1] = '\0';
142 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
143 attribs = GetFileAttributes(srcpath);
144 } else {
145 attribs = 0;
147 strcpyW(srcspec, srcpath);
149 /* If a directory, then add \* on the end when searching */
150 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
151 strcatW(srcpath, slashW);
152 copyFromDir = TRUE;
153 strcatW(srcspec, slashW);
154 strcatW(srcspec, starW);
155 } else {
156 WCMD_splitpath(srcpath, drive, dir, fname, ext);
157 strcpyW(srcpath, drive);
158 strcatW(srcpath, dir);
161 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
163 /* If no destination supplied, assume current directory */
164 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
165 if (param2[0] == 0x00) {
166 strcpyW(param2, dotW);
169 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
170 if (outpath[strlenW(outpath) - 1] == '\\')
171 outpath[strlenW(outpath) - 1] = '\0';
172 attribs = GetFileAttributes(outpath);
173 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
174 strcatW (outpath, slashW);
175 copyToDir = TRUE;
177 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
178 wine_dbgstr_w(outpath), copyToDir);
180 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
181 if (strstrW (quals, parmNoY))
182 force = FALSE;
183 else if (strstrW (quals, parmY))
184 force = TRUE;
185 else {
186 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
187 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
190 /* Loop through all source files */
191 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
192 hff = FindFirstFile (srcspec, &fd);
193 if (hff != INVALID_HANDLE_VALUE) {
194 do {
195 WCHAR outname[MAX_PATH];
196 WCHAR srcname[MAX_PATH];
197 BOOL overwrite = force;
199 /* Destination is either supplied filename, or source name in
200 supplied destination directory */
201 strcpyW(outname, outpath);
202 if (copyToDir) strcatW(outname, fd.cFileName);
203 strcpyW(srcname, srcpath);
204 strcatW(srcname, fd.cFileName);
206 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
207 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
209 /* Skip . and .., and directories */
210 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
211 overwrite = FALSE;
212 WINE_TRACE("Skipping directories\n");
215 /* Prompt before overwriting */
216 else if (!overwrite) {
217 attribs = GetFileAttributes(outname);
218 if (attribs != INVALID_FILE_ATTRIBUTES) {
219 WCHAR buffer[MAXSTRING];
220 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
221 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
223 else overwrite = TRUE;
226 /* Do the copy as appropriate */
227 if (overwrite) {
228 status = CopyFile (srcname, outname, FALSE);
229 if (!status) WCMD_print_error ();
232 } while (FindNextFile(hff, &fd) != 0);
233 FindClose (hff);
234 } else {
235 status = ERROR_FILE_NOT_FOUND;
236 WCMD_print_error ();
240 /****************************************************************************
241 * WCMD_create_dir
243 * Create a directory.
245 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
246 * they do not already exist.
249 static BOOL create_full_path(WCHAR* path)
251 int len;
252 WCHAR *new_path;
253 BOOL ret = TRUE;
255 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
256 strcpyW(new_path,path);
258 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
259 new_path[len - 1] = 0;
261 while (!CreateDirectory(new_path,NULL))
263 WCHAR *slash;
264 DWORD last_error = GetLastError();
265 if (last_error == ERROR_ALREADY_EXISTS)
266 break;
268 if (last_error != ERROR_PATH_NOT_FOUND)
270 ret = FALSE;
271 break;
274 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
276 ret = FALSE;
277 break;
280 len = slash - new_path;
281 new_path[len] = 0;
282 if (!create_full_path(new_path))
284 ret = FALSE;
285 break;
287 new_path[len] = '\\';
289 HeapFree(GetProcessHeap(),0,new_path);
290 return ret;
293 void WCMD_create_dir (void) {
295 if (param1[0] == 0x00) {
296 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
297 return;
299 if (!create_full_path(param1)) WCMD_print_error ();
302 /****************************************************************************
303 * WCMD_delete
305 * Delete a file or wildcarded set.
307 * Note on /A:
308 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
309 * - Each set is a pattern, eg /ahr /as-r means
310 * readonly+hidden OR nonreadonly system files
311 * - The '-' applies to a single field, ie /a:-hr means read only
312 * non-hidden files
315 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
317 int argno = 0;
318 int argsProcessed = 0;
319 WCHAR *argN = command;
320 BOOL foundAny = FALSE;
321 static const WCHAR parmA[] = {'/','A','\0'};
322 static const WCHAR parmQ[] = {'/','Q','\0'};
323 static const WCHAR parmP[] = {'/','P','\0'};
324 static const WCHAR parmS[] = {'/','S','\0'};
325 static const WCHAR parmF[] = {'/','F','\0'};
327 /* If not recursing, clear error flag */
328 if (expectDir) errorlevel = 0;
330 /* Loop through all args */
331 while (argN) {
332 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
333 WCHAR argCopy[MAX_PATH];
335 if (argN && argN[0] != '/') {
337 WIN32_FIND_DATA fd;
338 HANDLE hff;
339 WCHAR fpath[MAX_PATH];
340 WCHAR *p;
341 BOOL handleParm = TRUE;
342 BOOL found = FALSE;
343 static const WCHAR anyExt[]= {'.','*','\0'};
345 strcpyW(argCopy, thisArg);
346 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
347 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
348 argsProcessed++;
350 /* If filename part of parameter is * or *.*, prompt unless
351 /Q supplied. */
352 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
354 WCHAR drive[10];
355 WCHAR dir[MAX_PATH];
356 WCHAR fname[MAX_PATH];
357 WCHAR ext[MAX_PATH];
359 /* Convert path into actual directory spec */
360 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
361 WCMD_splitpath(fpath, drive, dir, fname, ext);
363 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
364 if ((strcmpW(fname, starW) == 0) &&
365 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
366 BOOL ok;
367 WCHAR question[MAXSTRING];
368 static const WCHAR fmt[] = {'%','s',' ','\0'};
370 /* Note: Flag as found, to avoid file not found message */
371 found = TRUE;
373 /* Ask for confirmation */
374 wsprintf(question, fmt, fpath);
375 ok = WCMD_ask_confirm(question, TRUE, NULL);
377 /* Abort if answer is 'N' */
378 if (!ok) continue;
382 /* First, try to delete in the current directory */
383 hff = FindFirstFile (argCopy, &fd);
384 if (hff == INVALID_HANDLE_VALUE) {
385 handleParm = FALSE;
386 } else {
387 found = TRUE;
390 /* Support del <dirname> by just deleting all files dirname\* */
391 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
392 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
393 WCHAR modifiedParm[MAX_PATH];
394 static const WCHAR slashStar[] = {'\\','*','\0'};
396 strcpyW(modifiedParm, argCopy);
397 strcatW(modifiedParm, slashStar);
398 FindClose(hff);
399 found = TRUE;
400 WCMD_delete(modifiedParm, FALSE);
402 } else if (handleParm) {
404 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
405 strcpyW (fpath, argCopy);
406 do {
407 p = strrchrW (fpath, '\\');
408 if (p != NULL) {
409 *++p = '\0';
410 strcatW (fpath, fd.cFileName);
412 else strcpyW (fpath, fd.cFileName);
413 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
414 BOOL ok = TRUE;
415 WCHAR *nextA = strstrW (quals, parmA);
417 /* Handle attribute matching (/A) */
418 if (nextA != NULL) {
419 ok = FALSE;
420 while (nextA != NULL && !ok) {
422 WCHAR *thisA = (nextA+2);
423 BOOL stillOK = TRUE;
425 /* Skip optional : */
426 if (*thisA == ':') thisA++;
428 /* Parse each of the /A[:]xxx in turn */
429 while (*thisA && *thisA != '/') {
430 BOOL negate = FALSE;
431 BOOL attribute = FALSE;
433 /* Match negation of attribute first */
434 if (*thisA == '-') {
435 negate=TRUE;
436 thisA++;
439 /* Match attribute */
440 switch (*thisA) {
441 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
442 break;
443 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
444 break;
445 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
446 break;
447 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
448 break;
449 default:
450 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
453 /* Now check result, keeping a running boolean about whether it
454 matches all parsed attribues so far */
455 if (attribute && !negate) {
456 stillOK = stillOK;
457 } else if (!attribute && negate) {
458 stillOK = stillOK;
459 } else {
460 stillOK = FALSE;
462 thisA++;
465 /* Save the running total as the final result */
466 ok = stillOK;
468 /* Step on to next /A set */
469 nextA = strstrW (nextA+1, parmA);
473 /* /P means prompt for each file */
474 if (ok && strstrW (quals, parmP) != NULL) {
475 WCHAR question[MAXSTRING];
477 /* Ask for confirmation */
478 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
479 ok = WCMD_ask_confirm(question, FALSE, NULL);
482 /* Only proceed if ok to */
483 if (ok) {
485 /* If file is read only, and /F supplied, delete it */
486 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
487 strstrW (quals, parmF) != NULL) {
488 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
491 /* Now do the delete */
492 if (!DeleteFile (fpath)) WCMD_print_error ();
496 } while (FindNextFile(hff, &fd) != 0);
497 FindClose (hff);
500 /* Now recurse into all subdirectories handling the parameter in the same way */
501 if (strstrW (quals, parmS) != NULL) {
503 WCHAR thisDir[MAX_PATH];
504 int cPos;
506 WCHAR drive[10];
507 WCHAR dir[MAX_PATH];
508 WCHAR fname[MAX_PATH];
509 WCHAR ext[MAX_PATH];
511 /* Convert path into actual directory spec */
512 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
513 WCMD_splitpath(thisDir, drive, dir, fname, ext);
515 strcpyW(thisDir, drive);
516 strcatW(thisDir, dir);
517 cPos = strlenW(thisDir);
519 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
521 /* Append '*' to the directory */
522 thisDir[cPos] = '*';
523 thisDir[cPos+1] = 0x00;
525 hff = FindFirstFile (thisDir, &fd);
527 /* Remove residual '*' */
528 thisDir[cPos] = 0x00;
530 if (hff != INVALID_HANDLE_VALUE) {
531 DIRECTORY_STACK *allDirs = NULL;
532 DIRECTORY_STACK *lastEntry = NULL;
534 do {
535 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
536 (strcmpW(fd.cFileName, dotdotW) != 0) &&
537 (strcmpW(fd.cFileName, dotW) != 0)) {
539 DIRECTORY_STACK *nextDir;
540 WCHAR subParm[MAX_PATH];
542 /* Work out search parameter in sub dir */
543 strcpyW (subParm, thisDir);
544 strcatW (subParm, fd.cFileName);
545 strcatW (subParm, slashW);
546 strcatW (subParm, fname);
547 strcatW (subParm, ext);
548 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
550 /* Allocate memory, add to list */
551 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
552 if (allDirs == NULL) allDirs = nextDir;
553 if (lastEntry != NULL) lastEntry->next = nextDir;
554 lastEntry = nextDir;
555 nextDir->next = NULL;
556 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
557 (strlenW(subParm)+1) * sizeof(WCHAR));
558 strcpyW(nextDir->dirName, subParm);
560 } while (FindNextFile(hff, &fd) != 0);
561 FindClose (hff);
563 /* Go through each subdir doing the delete */
564 while (allDirs != NULL) {
565 DIRECTORY_STACK *tempDir;
567 tempDir = allDirs->next;
568 found |= WCMD_delete (allDirs->dirName, FALSE);
570 HeapFree(GetProcessHeap(),0,allDirs->dirName);
571 HeapFree(GetProcessHeap(),0,allDirs);
572 allDirs = tempDir;
576 /* Keep running total to see if any found, and if not recursing
577 issue error message */
578 if (expectDir) {
579 if (!found) {
580 errorlevel = 1;
581 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
584 foundAny |= found;
588 /* Handle no valid args */
589 if (argsProcessed == 0) {
590 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
593 return foundAny;
596 /****************************************************************************
597 * WCMD_echo
599 * Echo input to the screen (or not). We don't try to emulate the bugs
600 * in DOS (try typing "ECHO ON AGAIN" for an example).
603 void WCMD_echo (const WCHAR *command) {
605 int count;
607 if ((command[0] == '.') && (command[1] == 0)) {
608 WCMD_output (newline);
609 return;
611 if (command[0]==' ')
612 command++;
613 count = strlenW(command);
614 if (count == 0) {
615 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
616 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
617 return;
619 if (lstrcmpiW(command, onW) == 0) {
620 echo_mode = 1;
621 return;
623 if (lstrcmpiW(command, offW) == 0) {
624 echo_mode = 0;
625 return;
627 WCMD_output_asis (command);
628 WCMD_output (newline);
632 /**************************************************************************
633 * WCMD_for
635 * Batch file loop processing.
637 * On entry: cmdList contains the syntax up to the set
638 * next cmdList and all in that bracket contain the set data
639 * next cmdlist contains the DO cmd
640 * following that is either brackets or && entries (as per if)
644 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
646 WIN32_FIND_DATA fd;
647 HANDLE hff;
648 int i;
649 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
650 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
651 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
652 WCHAR variable[4];
653 WCHAR *firstCmd;
654 int thisDepth;
656 WCHAR *curPos = p;
657 BOOL expandDirs = FALSE;
658 BOOL useNumbers = FALSE;
659 BOOL doRecursive = FALSE;
660 BOOL doFileset = FALSE;
661 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
662 int itemNum;
663 CMD_LIST *thisCmdStart;
666 /* Handle optional qualifiers (multiple are allowed) */
667 while (*curPos && *curPos == '/') {
668 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
669 curPos++;
670 switch (toupperW(*curPos)) {
671 case 'D': curPos++; expandDirs = TRUE; break;
672 case 'L': curPos++; useNumbers = TRUE; break;
674 /* Recursive is special case - /R can have an optional path following it */
675 /* filenamesets are another special case - /F can have an optional options following it */
676 case 'R':
677 case 'F':
679 BOOL isRecursive = (*curPos == 'R');
681 if (isRecursive) doRecursive = TRUE;
682 else doFileset = TRUE;
684 /* Skip whitespace */
685 curPos++;
686 while (*curPos && *curPos==' ') curPos++;
688 /* Next parm is either qualifier, path/options or variable -
689 only care about it if it is the path/options */
690 if (*curPos && *curPos != '/' && *curPos != '%') {
691 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
692 else WINE_FIXME("/F needs to handle options\n");
694 break;
696 default:
697 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
698 curPos++;
701 /* Skip whitespace between qualifiers */
702 while (*curPos && *curPos==' ') curPos++;
705 /* Skip whitespace before variable */
706 while (*curPos && *curPos==' ') curPos++;
708 /* Ensure line continues with variable */
709 if (!*curPos || *curPos != '%') {
710 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
711 return;
714 /* Variable should follow */
715 i = 0;
716 while (curPos[i] && curPos[i]!=' ') i++;
717 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
718 variable[i] = 0x00;
719 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
720 curPos = &curPos[i];
722 /* Skip whitespace before IN */
723 while (*curPos && *curPos==' ') curPos++;
725 /* Ensure line continues with IN */
726 if (!*curPos || lstrcmpiW (curPos, inW)) {
727 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
728 return;
731 /* Save away where the set of data starts and the variable */
732 thisDepth = (*cmdList)->bracketDepth;
733 *cmdList = (*cmdList)->nextcommand;
734 setStart = (*cmdList);
736 /* Skip until the close bracket */
737 WINE_TRACE("Searching %p as the set\n", *cmdList);
738 while (*cmdList &&
739 (*cmdList)->command != NULL &&
740 (*cmdList)->bracketDepth > thisDepth) {
741 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
742 *cmdList = (*cmdList)->nextcommand;
745 /* Skip the close bracket, if there is one */
746 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
748 /* Syntax error if missing close bracket, or nothing following it
749 and once we have the complete set, we expect a DO */
750 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
751 if ((*cmdList == NULL) ||
752 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
753 (*cmdList)->command, 3, doW, -1) != 2)) {
754 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
755 return;
758 /* Save away the starting position for the commands (and offset for the
759 first one */
760 cmdStart = *cmdList;
761 cmdEnd = *cmdList;
762 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
763 itemNum = 0;
765 thisSet = setStart;
766 /* Loop through all set entries */
767 while (thisSet &&
768 thisSet->command != NULL &&
769 thisSet->bracketDepth >= thisDepth) {
771 /* Loop through all entries on the same line */
772 WCHAR *item;
774 WINE_TRACE("Processing for set %p\n", thisSet);
775 i = 0;
776 while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
779 * If the parameter within the set has a wildcard then search for matching files
780 * otherwise do a literal substitution.
782 static const WCHAR wildcards[] = {'*','?','\0'};
783 thisCmdStart = cmdStart;
785 itemNum++;
786 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
788 if (!useNumbers && !doFileset) {
789 if (strpbrkW (item, wildcards)) {
790 hff = FindFirstFile (item, &fd);
791 if (hff != INVALID_HANDLE_VALUE) {
792 do {
793 BOOL isDirectory = FALSE;
795 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
797 /* Handle as files or dirs appropriately, but ignore . and .. */
798 if (isDirectory == expandDirs &&
799 (strcmpW(fd.cFileName, dotdotW) != 0) &&
800 (strcmpW(fd.cFileName, dotW) != 0))
802 thisCmdStart = cmdStart;
803 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
804 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
805 fd.cFileName, FALSE, TRUE);
808 } while (FindNextFile(hff, &fd) != 0);
809 FindClose (hff);
811 } else {
812 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
815 } else if (useNumbers) {
816 /* Convert the first 3 numbers to signed longs and save */
817 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
818 /* else ignore them! */
820 } else if (doFileset) {
822 HANDLE input;
824 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
825 wine_dbgstr_w(item));
827 /* Open the file, read line by line and process */
828 input = CreateFile (item, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
829 FILE_ATTRIBUTE_NORMAL, NULL);
831 if (input == INVALID_HANDLE_VALUE) {
832 WCMD_print_error ();
833 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
834 errorlevel = 1;
835 return; /* FOR loop aborts at first failure here */
837 } else {
839 WCHAR buffer[MAXSTRING] = {'\0'};
840 WCHAR *where, *parm;
842 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
844 /* Skip blank lines*/
845 parm = WCMD_parameter (buffer, 0, &where);
846 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
847 wine_dbgstr_w(buffer));
849 if (where) {
850 thisCmdStart = cmdStart;
851 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
852 cmdEnd = thisCmdStart;
855 buffer[0] = 0x00;
858 CloseHandle (input);
862 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
863 cmdEnd = thisCmdStart;
864 i++;
867 /* Move onto the next set line */
868 thisSet = thisSet->nextcommand;
871 /* If /L is provided, now run the for loop */
872 if (useNumbers) {
873 WCHAR thisNum[20];
874 static const WCHAR fmt[] = {'%','d','\0'};
876 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
877 numbers[0], numbers[2], numbers[1]);
878 for (i=numbers[0];
879 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
880 i=i + numbers[1]) {
882 sprintfW(thisNum, fmt, i);
883 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
885 thisCmdStart = cmdStart;
886 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
887 cmdEnd = thisCmdStart;
891 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
892 all processing, OR it should be pointing to the end of && processing OR
893 it should be pointing at the NULL end of bracket for the DO. The return
894 value needs to be the NEXT command to execute, which it either is, or
895 we need to step over the closing bracket */
896 *cmdList = cmdEnd;
897 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
901 /*****************************************************************************
902 * WCMD_part_execute
904 * Execute a command, and any && or bracketed follow on to the command. The
905 * first command to be executed may not be at the front of the
906 * commands->thiscommand string (eg. it may point after a DO or ELSE)
908 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
909 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
911 CMD_LIST *curPosition = *cmdList;
912 int myDepth = (*cmdList)->bracketDepth;
914 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
915 cmdList, wine_dbgstr_w(firstcmd),
916 wine_dbgstr_w(variable), wine_dbgstr_w(value),
917 conditionTRUE);
919 /* Skip leading whitespace between condition and the command */
920 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
922 /* Process the first command, if there is one */
923 if (conditionTRUE && firstcmd && *firstcmd) {
924 WCHAR *command = WCMD_strdupW(firstcmd);
925 WCMD_execute (firstcmd, variable, value, cmdList);
926 free (command);
930 /* If it didn't move the position, step to next command */
931 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
933 /* Process any other parts of the command */
934 if (*cmdList) {
935 BOOL processThese = TRUE;
937 if (isIF) processThese = conditionTRUE;
939 while (*cmdList) {
940 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
942 /* execute all appropriate commands */
943 curPosition = *cmdList;
945 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
946 *cmdList,
947 (*cmdList)->isAmphersand,
948 (*cmdList)->bracketDepth, myDepth);
950 /* Execute any appended to the statement with &&'s */
951 if ((*cmdList)->isAmphersand) {
952 if (processThese) {
953 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
955 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
957 /* Execute any appended to the statement with (...) */
958 } else if ((*cmdList)->bracketDepth > myDepth) {
959 if (processThese) {
960 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
961 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
963 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
965 /* End of the command - does 'ELSE ' follow as the next command? */
966 } else {
967 if (isIF && CompareString (LOCALE_USER_DEFAULT,
968 NORM_IGNORECASE | SORT_STRINGSORT,
969 (*cmdList)->command, 5, ifElse, -1) == 2) {
971 /* Swap between if and else processing */
972 processThese = !processThese;
974 /* Process the ELSE part */
975 if (processThese) {
976 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
978 /* Skip leading whitespace between condition and the command */
979 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
980 if (*cmd) {
981 WCMD_execute (cmd, variable, value, cmdList);
984 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
985 } else {
986 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
987 break;
992 return;
995 /*****************************************************************************
996 * WCMD_Execute
998 * Execute a command after substituting variable text for the supplied parameter
1001 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
1003 WCHAR *new_cmd, *p, *s, *dup;
1004 int size;
1006 if (param) {
1007 size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
1008 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
1009 dup = s = WCMD_strdupW(orig_cmd);
1011 while ((p = strstrW (s, param))) {
1012 *p = '\0';
1013 size += strlenW (subst) * sizeof(WCHAR);
1014 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
1015 strcatW (new_cmd, s);
1016 strcatW (new_cmd, subst);
1017 s = p + strlenW (param);
1019 strcatW (new_cmd, s);
1020 WCMD_process_command (new_cmd, cmdList);
1021 free (dup);
1022 LocalFree ((HANDLE)new_cmd);
1023 } else {
1024 WCMD_process_command (orig_cmd, cmdList);
1029 /**************************************************************************
1030 * WCMD_give_help
1032 * Simple on-line help. Help text is stored in the resource file.
1035 void WCMD_give_help (WCHAR *command) {
1037 int i;
1039 command = WCMD_strtrim_leading_spaces(command);
1040 if (strlenW(command) == 0) {
1041 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1043 else {
1044 for (i=0; i<=WCMD_EXIT; i++) {
1045 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1046 param1, -1, inbuilt[i], -1) == 2) {
1047 WCMD_output_asis (WCMD_LoadMessage(i));
1048 return;
1051 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
1053 return;
1056 /****************************************************************************
1057 * WCMD_go_to
1059 * Batch file jump instruction. Not the most efficient algorithm ;-)
1060 * Prints error message if the specified label cannot be found - the file pointer is
1061 * then at EOF, effectively stopping the batch file.
1062 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1065 void WCMD_goto (CMD_LIST **cmdList) {
1067 WCHAR string[MAX_PATH];
1069 /* Do not process any more parts of a processed multipart or multilines command */
1070 *cmdList = NULL;
1072 if (param1[0] == 0x00) {
1073 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1074 return;
1076 if (context != NULL) {
1077 WCHAR *paramStart = param1;
1078 static const WCHAR eofW[] = {':','e','o','f','\0'};
1080 /* Handle special :EOF label */
1081 if (lstrcmpiW (eofW, param1) == 0) {
1082 context -> skip_rest = TRUE;
1083 return;
1086 /* Support goto :label as well as goto label */
1087 if (*paramStart == ':') paramStart++;
1089 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1090 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1091 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1093 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1095 return;
1098 /*****************************************************************************
1099 * WCMD_pushd
1101 * Push a directory onto the stack
1104 void WCMD_pushd (WCHAR *command) {
1105 struct env_stack *curdir;
1106 WCHAR *thisdir;
1107 static const WCHAR parmD[] = {'/','D','\0'};
1109 if (strchrW(command, '/') != NULL) {
1110 SetLastError(ERROR_INVALID_PARAMETER);
1111 WCMD_print_error();
1112 return;
1115 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1116 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1117 if( !curdir || !thisdir ) {
1118 LocalFree(curdir);
1119 LocalFree(thisdir);
1120 WINE_ERR ("out of memory\n");
1121 return;
1124 /* Change directory using CD code with /D parameter */
1125 strcpyW(quals, parmD);
1126 GetCurrentDirectoryW (1024, thisdir);
1127 errorlevel = 0;
1128 WCMD_setshow_default(command);
1129 if (errorlevel) {
1130 LocalFree(curdir);
1131 LocalFree(thisdir);
1132 return;
1133 } else {
1134 curdir -> next = pushd_directories;
1135 curdir -> strings = thisdir;
1136 if (pushd_directories == NULL) {
1137 curdir -> u.stackdepth = 1;
1138 } else {
1139 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1141 pushd_directories = curdir;
1146 /*****************************************************************************
1147 * WCMD_popd
1149 * Pop a directory from the stack
1152 void WCMD_popd (void) {
1153 struct env_stack *temp = pushd_directories;
1155 if (!pushd_directories)
1156 return;
1158 /* pop the old environment from the stack, and make it the current dir */
1159 pushd_directories = temp->next;
1160 SetCurrentDirectoryW(temp->strings);
1161 LocalFree (temp->strings);
1162 LocalFree (temp);
1165 /****************************************************************************
1166 * WCMD_if
1168 * Batch file conditional.
1170 * On entry, cmdlist will point to command containing the IF, and optionally
1171 * the first command to execute (if brackets not found)
1172 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1173 * If ('s were found, execute all within that bracket
1174 * Command may optionally be followed by an ELSE - need to skip instructions
1175 * in the else using the same logic
1177 * FIXME: Much more syntax checking needed!
1180 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1182 int negate = 0, test = 0;
1183 WCHAR condition[MAX_PATH], *command, *s;
1184 static const WCHAR notW[] = {'n','o','t','\0'};
1185 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1186 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1187 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1188 static const WCHAR eqeqW[] = {'=','=','\0'};
1190 if (!lstrcmpiW (param1, notW)) {
1191 negate = 1;
1192 strcpyW (condition, param2);
1194 else {
1195 strcpyW (condition, param1);
1197 if (!lstrcmpiW (condition, errlvlW)) {
1198 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1199 WCMD_parameter (p, 2+negate, &command);
1201 else if (!lstrcmpiW (condition, existW)) {
1202 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1203 test = 1;
1205 WCMD_parameter (p, 2+negate, &command);
1207 else if (!lstrcmpiW (condition, defdW)) {
1208 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1209 test = 1;
1211 WCMD_parameter (p, 2+negate, &command);
1213 else if ((s = strstrW (p, eqeqW))) {
1214 s += 2;
1215 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1216 WCMD_parameter (s, 1, &command);
1218 else {
1219 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1220 return;
1223 /* Process rest of IF statement which is on the same line
1224 Note: This may process all or some of the cmdList (eg a GOTO) */
1225 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1228 /****************************************************************************
1229 * WCMD_move
1231 * Move a file, directory tree or wildcarded set of files.
1234 void WCMD_move (void) {
1236 int status;
1237 WIN32_FIND_DATA fd;
1238 HANDLE hff;
1239 WCHAR input[MAX_PATH];
1240 WCHAR output[MAX_PATH];
1241 WCHAR drive[10];
1242 WCHAR dir[MAX_PATH];
1243 WCHAR fname[MAX_PATH];
1244 WCHAR ext[MAX_PATH];
1246 if (param1[0] == 0x00) {
1247 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1248 return;
1251 /* If no destination supplied, assume current directory */
1252 if (param2[0] == 0x00) {
1253 strcpyW(param2, dotW);
1256 /* If 2nd parm is directory, then use original filename */
1257 /* Convert partial path to full path */
1258 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1259 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1260 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1261 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1263 /* Split into components */
1264 WCMD_splitpath(input, drive, dir, fname, ext);
1266 hff = FindFirstFile (input, &fd);
1267 while (hff != INVALID_HANDLE_VALUE) {
1268 WCHAR dest[MAX_PATH];
1269 WCHAR src[MAX_PATH];
1270 DWORD attribs;
1272 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1274 /* Build src & dest name */
1275 strcpyW(src, drive);
1276 strcatW(src, dir);
1278 /* See if dest is an existing directory */
1279 attribs = GetFileAttributes(output);
1280 if (attribs != INVALID_FILE_ATTRIBUTES &&
1281 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1282 strcpyW(dest, output);
1283 strcatW(dest, slashW);
1284 strcatW(dest, fd.cFileName);
1285 } else {
1286 strcpyW(dest, output);
1289 strcatW(src, fd.cFileName);
1291 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1292 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1294 /* Check if file is read only, otherwise move it */
1295 attribs = GetFileAttributes(src);
1296 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1297 (attribs & FILE_ATTRIBUTE_READONLY)) {
1298 SetLastError(ERROR_ACCESS_DENIED);
1299 status = 0;
1300 } else {
1301 BOOL ok = TRUE;
1303 /* If destination exists, prompt unless /Y supplied */
1304 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1305 BOOL force = FALSE;
1306 WCHAR copycmd[MAXSTRING];
1307 int len;
1309 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1310 if (strstrW (quals, parmNoY))
1311 force = FALSE;
1312 else if (strstrW (quals, parmY))
1313 force = TRUE;
1314 else {
1315 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1316 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1317 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1318 && ! lstrcmpiW (copycmd, parmY));
1321 /* Prompt if overwriting */
1322 if (!force) {
1323 WCHAR question[MAXSTRING];
1324 WCHAR yesChar[10];
1326 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1328 /* Ask for confirmation */
1329 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1330 ok = WCMD_ask_confirm(question, FALSE, NULL);
1332 /* So delete the destination prior to the move */
1333 if (ok) {
1334 if (!DeleteFile (dest)) {
1335 WCMD_print_error ();
1336 errorlevel = 1;
1337 ok = FALSE;
1343 if (ok) {
1344 status = MoveFile (src, dest);
1345 } else {
1346 status = 1; /* Anything other than 0 to prevent error msg below */
1350 if (!status) {
1351 WCMD_print_error ();
1352 errorlevel = 1;
1355 /* Step on to next match */
1356 if (FindNextFile(hff, &fd) == 0) {
1357 FindClose(hff);
1358 hff = INVALID_HANDLE_VALUE;
1359 break;
1364 /****************************************************************************
1365 * WCMD_pause
1367 * Wait for keyboard input.
1370 void WCMD_pause (void) {
1372 DWORD count;
1373 WCHAR string[32];
1375 WCMD_output (anykey);
1376 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1377 sizeof(string)/sizeof(WCHAR), &count, NULL);
1380 /****************************************************************************
1381 * WCMD_remove_dir
1383 * Delete a directory.
1386 void WCMD_remove_dir (WCHAR *command) {
1388 int argno = 0;
1389 int argsProcessed = 0;
1390 WCHAR *argN = command;
1391 static const WCHAR parmS[] = {'/','S','\0'};
1392 static const WCHAR parmQ[] = {'/','Q','\0'};
1394 /* Loop through all args */
1395 while (argN) {
1396 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1397 if (argN && argN[0] != '/') {
1398 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1399 wine_dbgstr_w(quals));
1400 argsProcessed++;
1402 /* If subdirectory search not supplied, just try to remove
1403 and report error if it fails (eg if it contains a file) */
1404 if (strstrW (quals, parmS) == NULL) {
1405 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1407 /* Otherwise use ShFileOp to recursively remove a directory */
1408 } else {
1410 SHFILEOPSTRUCT lpDir;
1412 /* Ask first */
1413 if (strstrW (quals, parmQ) == NULL) {
1414 BOOL ok;
1415 WCHAR question[MAXSTRING];
1416 static const WCHAR fmt[] = {'%','s',' ','\0'};
1418 /* Ask for confirmation */
1419 wsprintf(question, fmt, thisArg);
1420 ok = WCMD_ask_confirm(question, TRUE, NULL);
1422 /* Abort if answer is 'N' */
1423 if (!ok) return;
1426 /* Do the delete */
1427 lpDir.hwnd = NULL;
1428 lpDir.pTo = NULL;
1429 lpDir.pFrom = thisArg;
1430 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1431 lpDir.wFunc = FO_DELETE;
1432 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1437 /* Handle no valid args */
1438 if (argsProcessed == 0) {
1439 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1440 return;
1445 /****************************************************************************
1446 * WCMD_rename
1448 * Rename a file.
1451 void WCMD_rename (void) {
1453 int status;
1454 HANDLE hff;
1455 WIN32_FIND_DATA fd;
1456 WCHAR input[MAX_PATH];
1457 WCHAR *dotDst = NULL;
1458 WCHAR drive[10];
1459 WCHAR dir[MAX_PATH];
1460 WCHAR fname[MAX_PATH];
1461 WCHAR ext[MAX_PATH];
1462 DWORD attribs;
1464 errorlevel = 0;
1466 /* Must be at least two args */
1467 if (param1[0] == 0x00 || param2[0] == 0x00) {
1468 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1469 errorlevel = 1;
1470 return;
1473 /* Destination cannot contain a drive letter or directory separator */
1474 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1475 SetLastError(ERROR_INVALID_PARAMETER);
1476 WCMD_print_error();
1477 errorlevel = 1;
1478 return;
1481 /* Convert partial path to full path */
1482 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1483 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1484 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1485 dotDst = strchrW(param2, '.');
1487 /* Split into components */
1488 WCMD_splitpath(input, drive, dir, fname, ext);
1490 hff = FindFirstFile (input, &fd);
1491 while (hff != INVALID_HANDLE_VALUE) {
1492 WCHAR dest[MAX_PATH];
1493 WCHAR src[MAX_PATH];
1494 WCHAR *dotSrc = NULL;
1495 int dirLen;
1497 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1499 /* FIXME: If dest name or extension is *, replace with filename/ext
1500 part otherwise use supplied name. This supports:
1501 ren *.fred *.jim
1502 ren jim.* fred.* etc
1503 However, windows has a more complex algorithum supporting eg
1504 ?'s and *'s mid name */
1505 dotSrc = strchrW(fd.cFileName, '.');
1507 /* Build src & dest name */
1508 strcpyW(src, drive);
1509 strcatW(src, dir);
1510 strcpyW(dest, src);
1511 dirLen = strlenW(src);
1512 strcatW(src, fd.cFileName);
1514 /* Build name */
1515 if (param2[0] == '*') {
1516 strcatW(dest, fd.cFileName);
1517 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1518 } else {
1519 strcatW(dest, param2);
1520 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1523 /* Build Extension */
1524 if (dotDst && (*(dotDst+1)=='*')) {
1525 if (dotSrc) strcatW(dest, dotSrc);
1526 } else if (dotDst) {
1527 if (dotDst) strcatW(dest, dotDst);
1530 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1531 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1533 /* Check if file is read only, otherwise move it */
1534 attribs = GetFileAttributes(src);
1535 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1536 (attribs & FILE_ATTRIBUTE_READONLY)) {
1537 SetLastError(ERROR_ACCESS_DENIED);
1538 status = 0;
1539 } else {
1540 status = MoveFile (src, dest);
1543 if (!status) {
1544 WCMD_print_error ();
1545 errorlevel = 1;
1548 /* Step on to next match */
1549 if (FindNextFile(hff, &fd) == 0) {
1550 FindClose(hff);
1551 hff = INVALID_HANDLE_VALUE;
1552 break;
1557 /*****************************************************************************
1558 * WCMD_dupenv
1560 * Make a copy of the environment.
1562 static WCHAR *WCMD_dupenv( const WCHAR *env )
1564 WCHAR *env_copy;
1565 int len;
1567 if( !env )
1568 return NULL;
1570 len = 0;
1571 while ( env[len] )
1572 len += (strlenW(&env[len]) + 1);
1574 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1575 if (!env_copy)
1577 WINE_ERR("out of memory\n");
1578 return env_copy;
1580 memcpy (env_copy, env, len*sizeof (WCHAR));
1581 env_copy[len] = 0;
1583 return env_copy;
1586 /*****************************************************************************
1587 * WCMD_setlocal
1589 * setlocal pushes the environment onto a stack
1590 * Save the environment as unicode so we don't screw anything up.
1592 void WCMD_setlocal (const WCHAR *s) {
1593 WCHAR *env;
1594 struct env_stack *env_copy;
1595 WCHAR cwd[MAX_PATH];
1597 /* DISABLEEXTENSIONS ignored */
1599 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1600 if( !env_copy )
1602 WINE_ERR ("out of memory\n");
1603 return;
1606 env = GetEnvironmentStringsW ();
1608 env_copy->strings = WCMD_dupenv (env);
1609 if (env_copy->strings)
1611 env_copy->next = saved_environment;
1612 saved_environment = env_copy;
1614 /* Save the current drive letter */
1615 GetCurrentDirectory (MAX_PATH, cwd);
1616 env_copy->u.cwd = cwd[0];
1618 else
1619 LocalFree (env_copy);
1621 FreeEnvironmentStringsW (env);
1625 /*****************************************************************************
1626 * WCMD_endlocal
1628 * endlocal pops the environment off a stack
1629 * Note: When searching for '=', search from WCHAR position 1, to handle
1630 * special internal environment variables =C:, =D: etc
1632 void WCMD_endlocal (void) {
1633 WCHAR *env, *old, *p;
1634 struct env_stack *temp;
1635 int len, n;
1637 if (!saved_environment)
1638 return;
1640 /* pop the old environment from the stack */
1641 temp = saved_environment;
1642 saved_environment = temp->next;
1644 /* delete the current environment, totally */
1645 env = GetEnvironmentStringsW ();
1646 old = WCMD_dupenv (GetEnvironmentStringsW ());
1647 len = 0;
1648 while (old[len]) {
1649 n = strlenW(&old[len]) + 1;
1650 p = strchrW(&old[len] + 1, '=');
1651 if (p)
1653 *p++ = 0;
1654 SetEnvironmentVariableW (&old[len], NULL);
1656 len += n;
1658 LocalFree (old);
1659 FreeEnvironmentStringsW (env);
1661 /* restore old environment */
1662 env = temp->strings;
1663 len = 0;
1664 while (env[len]) {
1665 n = strlenW(&env[len]) + 1;
1666 p = strchrW(&env[len] + 1, '=');
1667 if (p)
1669 *p++ = 0;
1670 SetEnvironmentVariableW (&env[len], p);
1672 len += n;
1675 /* Restore current drive letter */
1676 if (IsCharAlpha(temp->u.cwd)) {
1677 WCHAR envvar[4];
1678 WCHAR cwd[MAX_PATH];
1679 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1681 wsprintf(envvar, fmt, temp->u.cwd);
1682 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1683 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1684 SetCurrentDirectory(cwd);
1688 LocalFree (env);
1689 LocalFree (temp);
1692 /*****************************************************************************
1693 * WCMD_setshow_attrib
1695 * Display and optionally sets DOS attributes on a file or directory
1697 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1698 * As a result only the Readonly flag is correctly reported, the Archive bit
1699 * is always set and the rest are not implemented. We do the Right Thing anyway.
1701 * FIXME: No SET functionality.
1705 void WCMD_setshow_attrib (void) {
1707 DWORD count;
1708 HANDLE hff;
1709 WIN32_FIND_DATA fd;
1710 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1712 if (param1[0] == '-') {
1713 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1714 return;
1717 if (strlenW(param1) == 0) {
1718 static const WCHAR slashStarW[] = {'\\','*','\0'};
1720 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1721 strcatW (param1, slashStarW);
1724 hff = FindFirstFile (param1, &fd);
1725 if (hff == INVALID_HANDLE_VALUE) {
1726 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1728 else {
1729 do {
1730 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1731 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1732 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1733 flags[0] = 'H';
1735 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1736 flags[1] = 'S';
1738 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1739 flags[2] = 'A';
1741 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1742 flags[3] = 'R';
1744 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1745 flags[4] = 'T';
1747 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1748 flags[5] = 'C';
1750 WCMD_output (fmt, flags, fd.cFileName);
1751 for (count=0; count < 8; count++) flags[count] = ' ';
1753 } while (FindNextFile(hff, &fd) != 0);
1755 FindClose (hff);
1758 /*****************************************************************************
1759 * WCMD_setshow_default
1761 * Set/Show the current default directory
1764 void WCMD_setshow_default (WCHAR *command) {
1766 BOOL status;
1767 WCHAR string[1024];
1768 WCHAR cwd[1024];
1769 WCHAR *pos;
1770 WIN32_FIND_DATA fd;
1771 HANDLE hff;
1772 static const WCHAR parmD[] = {'/','D','\0'};
1774 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1776 /* Skip /D and trailing whitespace if on the front of the command line */
1777 if (CompareString (LOCALE_USER_DEFAULT,
1778 NORM_IGNORECASE | SORT_STRINGSORT,
1779 command, 2, parmD, -1) == 2) {
1780 command += 2;
1781 while (*command && *command==' ') command++;
1784 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1785 if (strlenW(command) == 0) {
1786 strcatW (cwd, newline);
1787 WCMD_output (cwd);
1789 else {
1790 /* Remove any double quotes, which may be in the
1791 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1792 pos = string;
1793 while (*command) {
1794 if (*command != '"') *pos++ = *command;
1795 command++;
1797 *pos = 0x00;
1799 /* Search for approprate directory */
1800 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1801 hff = FindFirstFile (string, &fd);
1802 while (hff != INVALID_HANDLE_VALUE) {
1803 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1804 WCHAR fpath[MAX_PATH];
1805 WCHAR drive[10];
1806 WCHAR dir[MAX_PATH];
1807 WCHAR fname[MAX_PATH];
1808 WCHAR ext[MAX_PATH];
1809 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1811 /* Convert path into actual directory spec */
1812 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1813 WCMD_splitpath(fpath, drive, dir, fname, ext);
1815 /* Rebuild path */
1816 wsprintf(string, fmt, drive, dir, fd.cFileName);
1818 FindClose(hff);
1819 hff = INVALID_HANDLE_VALUE;
1820 break;
1823 /* Step on to next match */
1824 if (FindNextFile(hff, &fd) == 0) {
1825 FindClose(hff);
1826 hff = INVALID_HANDLE_VALUE;
1827 break;
1831 /* Change to that directory */
1832 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1834 status = SetCurrentDirectory (string);
1835 if (!status) {
1836 errorlevel = 1;
1837 WCMD_print_error ();
1838 return;
1839 } else {
1841 /* Restore old directory if drive letter would change, and
1842 CD x:\directory /D (or pushd c:\directory) not supplied */
1843 if ((strstrW(quals, parmD) == NULL) &&
1844 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1845 SetCurrentDirectory(cwd);
1849 /* Set special =C: type environment variable, for drive letter of
1850 change of directory, even if path was restored due to missing
1851 /D (allows changing drive letter when not resident on that
1852 drive */
1853 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1854 WCHAR env[4];
1855 strcpyW(env, equalW);
1856 memcpy(env+1, string, 2 * sizeof(WCHAR));
1857 env[3] = 0x00;
1858 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1859 SetEnvironmentVariable(env, string);
1863 return;
1866 /****************************************************************************
1867 * WCMD_setshow_date
1869 * Set/Show the system date
1870 * FIXME: Can't change date yet
1873 void WCMD_setshow_date (void) {
1875 WCHAR curdate[64], buffer[64];
1876 DWORD count;
1877 static const WCHAR parmT[] = {'/','T','\0'};
1879 if (strlenW(param1) == 0) {
1880 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1881 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1882 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1883 if (strstrW (quals, parmT) == NULL) {
1884 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1885 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1886 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1887 if (count > 2) {
1888 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1892 else WCMD_print_error ();
1894 else {
1895 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1899 /****************************************************************************
1900 * WCMD_compare
1902 static int WCMD_compare( const void *a, const void *b )
1904 int r;
1905 const WCHAR * const *str_a = a, * const *str_b = b;
1906 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1907 *str_a, -1, *str_b, -1 );
1908 if( r == CSTR_LESS_THAN ) return -1;
1909 if( r == CSTR_GREATER_THAN ) return 1;
1910 return 0;
1913 /****************************************************************************
1914 * WCMD_setshow_sortenv
1916 * sort variables into order for display
1917 * Optionally only display those who start with a stub
1918 * returns the count displayed
1920 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1922 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1923 const WCHAR **str;
1925 if (stub) stublen = strlenW(stub);
1927 /* count the number of strings, and the total length */
1928 while ( s[len] ) {
1929 len += (strlenW(&s[len]) + 1);
1930 count++;
1933 /* add the strings to an array */
1934 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1935 if( !str )
1936 return 0;
1937 str[0] = s;
1938 for( i=1; i<count; i++ )
1939 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1941 /* sort the array */
1942 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1944 /* print it */
1945 for( i=0; i<count; i++ ) {
1946 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1947 NORM_IGNORECASE | SORT_STRINGSORT,
1948 str[i], stublen, stub, -1) == 2) {
1949 /* Don't display special internal variables */
1950 if (str[i][0] != '=') {
1951 WCMD_output_asis(str[i]);
1952 WCMD_output_asis(newline);
1953 displayedcount++;
1958 LocalFree( str );
1959 return displayedcount;
1962 /****************************************************************************
1963 * WCMD_setshow_env
1965 * Set/Show the environment variables
1968 void WCMD_setshow_env (WCHAR *s) {
1970 LPVOID env;
1971 WCHAR *p;
1972 int status;
1973 static const WCHAR parmP[] = {'/','P','\0'};
1975 errorlevel = 0;
1976 if (param1[0] == 0x00 && quals[0] == 0x00) {
1977 env = GetEnvironmentStrings ();
1978 WCMD_setshow_sortenv( env, NULL );
1979 return;
1982 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1983 if (CompareString (LOCALE_USER_DEFAULT,
1984 NORM_IGNORECASE | SORT_STRINGSORT,
1985 s, 2, parmP, -1) == 2) {
1986 WCHAR string[MAXSTRING];
1987 DWORD count;
1989 s += 2;
1990 while (*s && *s==' ') s++;
1992 /* If no parameter, or no '=' sign, return an error */
1993 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1994 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1995 return;
1998 /* Output the prompt */
1999 *p++ = '\0';
2000 if (strlenW(p) != 0) WCMD_output(p);
2002 /* Read the reply */
2003 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2004 sizeof(string)/sizeof(WCHAR), &count, NULL);
2005 if (count > 1) {
2006 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2007 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2008 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2009 wine_dbgstr_w(string));
2010 status = SetEnvironmentVariable (s, string);
2013 } else {
2014 DWORD gle;
2015 p = strchrW (s, '=');
2016 if (p == NULL) {
2017 env = GetEnvironmentStrings ();
2018 if (WCMD_setshow_sortenv( env, s ) == 0) {
2019 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2020 errorlevel = 1;
2022 return;
2024 *p++ = '\0';
2026 if (strlenW(p) == 0) p = NULL;
2027 status = SetEnvironmentVariable (s, p);
2028 gle = GetLastError();
2029 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2030 errorlevel = 1;
2031 } else if ((!status)) WCMD_print_error();
2035 /****************************************************************************
2036 * WCMD_setshow_path
2038 * Set/Show the path environment variable
2041 void WCMD_setshow_path (WCHAR *command) {
2043 WCHAR string[1024];
2044 DWORD status;
2045 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2046 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2048 if (strlenW(param1) == 0) {
2049 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2050 if (status != 0) {
2051 WCMD_output_asis ( pathEqW);
2052 WCMD_output_asis ( string);
2053 WCMD_output_asis ( newline);
2055 else {
2056 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2059 else {
2060 if (*command == '=') command++; /* Skip leading '=' */
2061 status = SetEnvironmentVariable (pathW, command);
2062 if (!status) WCMD_print_error();
2066 /****************************************************************************
2067 * WCMD_setshow_prompt
2069 * Set or show the command prompt.
2072 void WCMD_setshow_prompt (void) {
2074 WCHAR *s;
2075 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2077 if (strlenW(param1) == 0) {
2078 SetEnvironmentVariable (promptW, NULL);
2080 else {
2081 s = param1;
2082 while ((*s == '=') || (*s == ' ')) s++;
2083 if (strlenW(s) == 0) {
2084 SetEnvironmentVariable (promptW, NULL);
2086 else SetEnvironmentVariable (promptW, s);
2090 /****************************************************************************
2091 * WCMD_setshow_time
2093 * Set/Show the system time
2094 * FIXME: Can't change time yet
2097 void WCMD_setshow_time (void) {
2099 WCHAR curtime[64], buffer[64];
2100 DWORD count;
2101 SYSTEMTIME st;
2102 static const WCHAR parmT[] = {'/','T','\0'};
2104 if (strlenW(param1) == 0) {
2105 GetLocalTime(&st);
2106 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2107 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2108 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
2109 if (strstrW (quals, parmT) == NULL) {
2110 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2111 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2112 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2113 if (count > 2) {
2114 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2118 else WCMD_print_error ();
2120 else {
2121 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2125 /****************************************************************************
2126 * WCMD_shift
2128 * Shift batch parameters.
2129 * Optional /n says where to start shifting (n=0-8)
2132 void WCMD_shift (WCHAR *command) {
2133 int start;
2135 if (context != NULL) {
2136 WCHAR *pos = strchrW(command, '/');
2137 int i;
2139 if (pos == NULL) {
2140 start = 0;
2141 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2142 start = (*(pos+1) - '0');
2143 } else {
2144 SetLastError(ERROR_INVALID_PARAMETER);
2145 WCMD_print_error();
2146 return;
2149 WINE_TRACE("Shifting variables, starting at %d\n", start);
2150 for (i=start;i<=8;i++) {
2151 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2153 context -> shift_count[9] = context -> shift_count[9] + 1;
2158 /****************************************************************************
2159 * WCMD_title
2161 * Set the console title
2163 void WCMD_title (WCHAR *command) {
2164 SetConsoleTitle(command);
2167 /****************************************************************************
2168 * WCMD_type
2170 * Copy a file to standard output.
2173 void WCMD_type (WCHAR *command) {
2175 int argno = 0;
2176 WCHAR *argN = command;
2177 BOOL writeHeaders = FALSE;
2179 if (param1[0] == 0x00) {
2180 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2181 return;
2184 if (param2[0] != 0x00) writeHeaders = TRUE;
2186 /* Loop through all args */
2187 errorlevel = 0;
2188 while (argN) {
2189 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2191 HANDLE h;
2192 WCHAR buffer[512];
2193 DWORD count;
2195 if (!argN) break;
2197 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2198 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2199 FILE_ATTRIBUTE_NORMAL, NULL);
2200 if (h == INVALID_HANDLE_VALUE) {
2201 WCMD_print_error ();
2202 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2203 errorlevel = 1;
2204 } else {
2205 if (writeHeaders) {
2206 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2207 WCMD_output(fmt, thisArg);
2209 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2210 if (count == 0) break; /* ReadFile reports success on EOF! */
2211 buffer[count] = 0;
2212 WCMD_output_asis (buffer);
2214 CloseHandle (h);
2219 /****************************************************************************
2220 * WCMD_more
2222 * Output either a file or stdin to screen in pages
2225 void WCMD_more (WCHAR *command) {
2227 int argno = 0;
2228 WCHAR *argN = command;
2229 BOOL useinput = FALSE;
2230 WCHAR moreStr[100];
2231 WCHAR moreStrPage[100];
2232 WCHAR buffer[512];
2233 DWORD count;
2234 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2235 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2236 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2237 ')',' ','-','-','\n','\0'};
2238 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2240 /* Prefix the NLS more with '-- ', then load the text */
2241 errorlevel = 0;
2242 strcpyW(moreStr, moreStart);
2243 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2244 (sizeof(moreStr)/sizeof(WCHAR))-3);
2246 if (param1[0] == 0x00) {
2248 /* Wine implements pipes via temporary files, and hence stdin is
2249 effectively reading from the file. This means the prompts for
2250 more are satistied by the next line from the input (file). To
2251 avoid this, ensure stdin is to the console */
2252 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2253 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2254 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2255 FILE_ATTRIBUTE_NORMAL, 0);
2256 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2258 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2259 once you get in this bit unless due to a pipe, its going to end badly... */
2260 useinput = TRUE;
2261 wsprintf(moreStrPage, moreFmt, moreStr);
2263 WCMD_enter_paged_mode(moreStrPage);
2264 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2265 if (count == 0) break; /* ReadFile reports success on EOF! */
2266 buffer[count] = 0;
2267 WCMD_output_asis (buffer);
2269 WCMD_leave_paged_mode();
2271 /* Restore stdin to what it was */
2272 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2273 CloseHandle(hConIn);
2275 return;
2276 } else {
2277 BOOL needsPause = FALSE;
2279 /* Loop through all args */
2280 WCMD_enter_paged_mode(moreStrPage);
2282 while (argN) {
2283 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2284 HANDLE h;
2286 if (!argN) break;
2288 if (needsPause) {
2290 /* Wait */
2291 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2292 WCMD_leave_paged_mode();
2293 WCMD_output_asis(moreStrPage);
2294 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2295 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2296 WCMD_enter_paged_mode(moreStrPage);
2300 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2301 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2302 FILE_ATTRIBUTE_NORMAL, NULL);
2303 if (h == INVALID_HANDLE_VALUE) {
2304 WCMD_print_error ();
2305 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2306 errorlevel = 1;
2307 } else {
2308 ULONG64 curPos = 0;
2309 ULONG64 fileLen = 0;
2310 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2312 /* Get the file size */
2313 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2314 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2316 needsPause = TRUE;
2317 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2318 if (count == 0) break; /* ReadFile reports success on EOF! */
2319 buffer[count] = 0;
2320 curPos += count;
2322 /* Update % count (would be used in WCMD_output_asis as prompt) */
2323 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2325 WCMD_output_asis (buffer);
2327 CloseHandle (h);
2331 WCMD_leave_paged_mode();
2335 /****************************************************************************
2336 * WCMD_verify
2338 * Display verify flag.
2339 * FIXME: We don't actually do anything with the verify flag other than toggle
2340 * it...
2343 void WCMD_verify (WCHAR *command) {
2345 int count;
2347 count = strlenW(command);
2348 if (count == 0) {
2349 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2350 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2351 return;
2353 if (lstrcmpiW(command, onW) == 0) {
2354 verify_mode = 1;
2355 return;
2357 else if (lstrcmpiW(command, offW) == 0) {
2358 verify_mode = 0;
2359 return;
2361 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2364 /****************************************************************************
2365 * WCMD_version
2367 * Display version info.
2370 void WCMD_version (void) {
2372 WCMD_output (version_string);
2376 /****************************************************************************
2377 * WCMD_volume
2379 * Display volume info and/or set volume label. Returns 0 if error.
2382 int WCMD_volume (int mode, WCHAR *path) {
2384 DWORD count, serial;
2385 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2386 BOOL status;
2388 if (strlenW(path) == 0) {
2389 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2390 if (!status) {
2391 WCMD_print_error ();
2392 return 0;
2394 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2395 &serial, NULL, NULL, NULL, 0);
2397 else {
2398 static const WCHAR fmt[] = {'%','s','\\','\0'};
2399 if ((path[1] != ':') || (strlenW(path) != 2)) {
2400 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2401 return 0;
2403 wsprintf (curdir, fmt, path);
2404 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2405 &serial, NULL,
2406 NULL, NULL, 0);
2408 if (!status) {
2409 WCMD_print_error ();
2410 return 0;
2412 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2413 curdir[0], label, HIWORD(serial), LOWORD(serial));
2414 if (mode) {
2415 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2416 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2417 sizeof(string)/sizeof(WCHAR), &count, NULL);
2418 if (count > 1) {
2419 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2420 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2422 if (strlenW(path) != 0) {
2423 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2425 else {
2426 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2429 return 1;
2432 /**************************************************************************
2433 * WCMD_exit
2435 * Exit either the process, or just this batch program
2439 void WCMD_exit (CMD_LIST **cmdList) {
2441 static const WCHAR parmB[] = {'/','B','\0'};
2442 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2444 if (context && lstrcmpiW(quals, parmB) == 0) {
2445 errorlevel = rc;
2446 context -> skip_rest = TRUE;
2447 *cmdList = NULL;
2448 } else {
2449 ExitProcess(rc);
2453 /**************************************************************************
2454 * WCMD_ask_confirm
2456 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2457 * answer.
2459 * Returns True if Y (or A) answer is selected
2460 * If optionAll contains a pointer, ALL is allowed, and if answered
2461 * set to TRUE
2464 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2466 WCHAR msgbuffer[MAXSTRING];
2467 WCHAR Ybuffer[MAXSTRING];
2468 WCHAR Nbuffer[MAXSTRING];
2469 WCHAR Abuffer[MAXSTRING];
2470 WCHAR answer[MAX_PATH] = {'\0'};
2471 DWORD count = 0;
2473 /* Load the translated 'Are you sure', plus valid answers */
2474 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2475 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2476 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2477 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2479 /* Loop waiting on a Y or N */
2480 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2481 static const WCHAR startBkt[] = {' ','(','\0'};
2482 static const WCHAR endBkt[] = {')','?','\0'};
2484 WCMD_output_asis (message);
2485 if (showSureText) {
2486 WCMD_output_asis (msgbuffer);
2488 WCMD_output_asis (startBkt);
2489 WCMD_output_asis (Ybuffer);
2490 WCMD_output_asis (fslashW);
2491 WCMD_output_asis (Nbuffer);
2492 if (optionAll) {
2493 WCMD_output_asis (fslashW);
2494 WCMD_output_asis (Abuffer);
2496 WCMD_output_asis (endBkt);
2497 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2498 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2499 answer[0] = toupperW(answer[0]);
2502 /* Return the answer */
2503 return ((answer[0] == Ybuffer[0]) ||
2504 (optionAll && (answer[0] == Abuffer[0])));
2507 /*****************************************************************************
2508 * WCMD_assoc
2510 * Lists or sets file associations (assoc = TRUE)
2511 * Lists or sets file types (assoc = FALSE)
2513 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2515 HKEY key;
2516 DWORD accessOptions = KEY_READ;
2517 WCHAR *newValue;
2518 LONG rc = ERROR_SUCCESS;
2519 WCHAR keyValue[MAXSTRING];
2520 DWORD valueLen = MAXSTRING;
2521 HKEY readKey;
2522 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2523 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2525 /* See if parameter includes '=' */
2526 errorlevel = 0;
2527 newValue = strchrW(command, '=');
2528 if (newValue) accessOptions |= KEY_WRITE;
2530 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2531 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2532 accessOptions, &key) != ERROR_SUCCESS) {
2533 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2534 return;
2537 /* If no parameters then list all associations */
2538 if (*command == 0x00) {
2539 int index = 0;
2541 /* Enumerate all the keys */
2542 while (rc != ERROR_NO_MORE_ITEMS) {
2543 WCHAR keyName[MAXSTRING];
2544 DWORD nameLen;
2546 /* Find the next value */
2547 nameLen = MAXSTRING;
2548 rc = RegEnumKeyEx(key, index++,
2549 keyName, &nameLen,
2550 NULL, NULL, NULL, NULL);
2552 if (rc == ERROR_SUCCESS) {
2554 /* Only interested in extension ones if assoc, or others
2555 if not assoc */
2556 if ((keyName[0] == '.' && assoc) ||
2557 (!(keyName[0] == '.') && (!assoc)))
2559 WCHAR subkey[MAXSTRING];
2560 strcpyW(subkey, keyName);
2561 if (!assoc) strcatW(subkey, shOpCmdW);
2563 if (RegOpenKeyEx(key, subkey, 0,
2564 accessOptions, &readKey) == ERROR_SUCCESS) {
2566 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2567 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2568 (LPBYTE)keyValue, &valueLen);
2569 WCMD_output_asis(keyName);
2570 WCMD_output_asis(equalW);
2571 /* If no default value found, leave line empty after '=' */
2572 if (rc == ERROR_SUCCESS) {
2573 WCMD_output_asis(keyValue);
2575 WCMD_output_asis(newline);
2580 RegCloseKey(readKey);
2582 } else {
2584 /* Parameter supplied - if no '=' on command line, its a query */
2585 if (newValue == NULL) {
2586 WCHAR *space;
2587 WCHAR subkey[MAXSTRING];
2589 /* Query terminates the parameter at the first space */
2590 strcpyW(keyValue, command);
2591 space = strchrW(keyValue, ' ');
2592 if (space) *space=0x00;
2594 /* Set up key name */
2595 strcpyW(subkey, keyValue);
2596 if (!assoc) strcatW(subkey, shOpCmdW);
2598 if (RegOpenKeyEx(key, subkey, 0,
2599 accessOptions, &readKey) == ERROR_SUCCESS) {
2601 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2602 (LPBYTE)keyValue, &valueLen);
2603 WCMD_output_asis(command);
2604 WCMD_output_asis(equalW);
2605 /* If no default value found, leave line empty after '=' */
2606 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2607 WCMD_output_asis(newline);
2608 RegCloseKey(readKey);
2610 } else {
2611 WCHAR msgbuffer[MAXSTRING];
2612 WCHAR outbuffer[MAXSTRING];
2614 /* Load the translated 'File association not found' */
2615 if (assoc) {
2616 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2617 } else {
2618 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2620 wsprintf(outbuffer, msgbuffer, keyValue);
2621 WCMD_output_asis(outbuffer);
2622 errorlevel = 2;
2625 /* Not a query - its a set or clear of a value */
2626 } else {
2628 WCHAR subkey[MAXSTRING];
2630 /* Get pointer to new value */
2631 *newValue = 0x00;
2632 newValue++;
2634 /* Set up key name */
2635 strcpyW(subkey, command);
2636 if (!assoc) strcatW(subkey, shOpCmdW);
2638 /* If nothing after '=' then clear value - only valid for ASSOC */
2639 if (*newValue == 0x00) {
2641 if (assoc) rc = RegDeleteKey(key, command);
2642 if (assoc && rc == ERROR_SUCCESS) {
2643 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2645 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2646 WCMD_print_error();
2647 errorlevel = 2;
2649 } else {
2650 WCHAR msgbuffer[MAXSTRING];
2651 WCHAR outbuffer[MAXSTRING];
2653 /* Load the translated 'File association not found' */
2654 if (assoc) {
2655 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2656 sizeof(msgbuffer)/sizeof(WCHAR));
2657 } else {
2658 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2659 sizeof(msgbuffer)/sizeof(WCHAR));
2661 wsprintf(outbuffer, msgbuffer, keyValue);
2662 WCMD_output_asis(outbuffer);
2663 errorlevel = 2;
2666 /* It really is a set value = contents */
2667 } else {
2668 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2669 accessOptions, NULL, &readKey, NULL);
2670 if (rc == ERROR_SUCCESS) {
2671 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2672 (LPBYTE)newValue, strlenW(newValue));
2673 RegCloseKey(readKey);
2676 if (rc != ERROR_SUCCESS) {
2677 WCMD_print_error();
2678 errorlevel = 2;
2679 } else {
2680 WCMD_output_asis(command);
2681 WCMD_output_asis(equalW);
2682 WCMD_output_asis(newValue);
2683 WCMD_output_asis(newline);
2689 /* Clean up */
2690 RegCloseKey(key);
2693 /****************************************************************************
2694 * WCMD_color
2696 * Clear the terminal screen.
2699 void WCMD_color (void) {
2701 /* Emulate by filling the screen from the top left to bottom right with
2702 spaces, then moving the cursor to the top left afterwards */
2703 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2704 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2706 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2707 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2708 return;
2711 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2713 COORD topLeft;
2714 DWORD screenSize;
2715 DWORD color = 0;
2717 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2719 topLeft.X = 0;
2720 topLeft.Y = 0;
2722 /* Convert the color hex digits */
2723 if (param1[0] == 0x00) {
2724 color = defaultColor;
2725 } else {
2726 color = strtoulW(param1, NULL, 16);
2729 /* Fail if fg == bg color */
2730 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2731 errorlevel = 1;
2732 return;
2735 /* Set the current screen contents and ensure all future writes
2736 remain this color */
2737 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2738 SetConsoleTextAttribute(hStdOut, color);