user32: Fix compiler warning
[wine/wine64.git] / programs / cmd / builtins.c
blob564ce452303a21c7060c69f6adf9ca4592c36bb0
1 /*
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
23 * NOTES:
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.
30 * FIXME:
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
38 #include "wcmd.h"
39 #include <shellapi.h>
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 /****************************************************************************
70 * WCMD_clear_screen
72 * Clear the terminal screen.
75 void WCMD_clear_screen (void) {
77 /* Emulate by filling the screen from the top left to bottom right with
78 spaces, then moving the cursor to the top left afterwards */
79 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
80 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
82 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
84 COORD topLeft;
85 DWORD screenSize;
87 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
89 topLeft.X = 0;
90 topLeft.Y = 0;
91 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
92 SetConsoleCursorPosition(hStdOut, topLeft);
96 /****************************************************************************
97 * WCMD_change_tty
99 * Change the default i/o device (ie redirect STDin/STDout).
102 void WCMD_change_tty (void) {
104 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
108 /****************************************************************************
109 * WCMD_copy
111 * Copy a file or wildcarded set.
112 * FIXME: Add support for a+b+c type syntax
115 void WCMD_copy (void) {
117 WIN32_FIND_DATA fd;
118 HANDLE hff;
119 BOOL force, status;
120 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
121 DWORD len;
122 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
123 BOOL copyToDir = FALSE;
124 BOOL copyFromDir = FALSE;
125 WCHAR srcspec[MAX_PATH];
126 DWORD attribs;
127 WCHAR drive[10];
128 WCHAR dir[MAX_PATH];
129 WCHAR fname[MAX_PATH];
130 WCHAR ext[MAX_PATH];
132 if (param1[0] == 0x00) {
133 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
134 return;
137 /* Convert source into full spec */
138 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
139 GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
140 if (srcpath[strlenW(srcpath) - 1] == '\\')
141 srcpath[strlenW(srcpath) - 1] = '\0';
143 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
144 attribs = GetFileAttributes(srcpath);
145 } else {
146 attribs = 0;
148 strcpyW(srcspec, srcpath);
150 /* If a directory, then add \* on the end when searching */
151 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
152 strcatW(srcpath, slashW);
153 copyFromDir = TRUE;
154 strcatW(srcspec, slashW);
155 strcatW(srcspec, starW);
156 } else {
157 WCMD_splitpath(srcpath, drive, dir, fname, ext);
158 strcpyW(srcpath, drive);
159 strcatW(srcpath, dir);
162 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
164 /* If no destination supplied, assume current directory */
165 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
166 if (param2[0] == 0x00) {
167 strcpyW(param2, dotW);
170 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
171 if (outpath[strlenW(outpath) - 1] == '\\')
172 outpath[strlenW(outpath) - 1] = '\0';
173 attribs = GetFileAttributes(outpath);
174 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
175 strcatW (outpath, slashW);
176 copyToDir = TRUE;
178 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
179 wine_dbgstr_w(outpath), copyToDir);
181 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
182 if (strstrW (quals, parmNoY))
183 force = FALSE;
184 else if (strstrW (quals, parmY))
185 force = TRUE;
186 else {
187 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
188 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
191 /* Loop through all source files */
192 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
193 hff = FindFirstFile (srcspec, &fd);
194 if (hff != INVALID_HANDLE_VALUE) {
195 do {
196 WCHAR outname[MAX_PATH];
197 WCHAR srcname[MAX_PATH];
198 BOOL overwrite = force;
200 /* Destination is either supplied filename, or source name in
201 supplied destination directory */
202 strcpyW(outname, outpath);
203 if (copyToDir) strcatW(outname, fd.cFileName);
204 strcpyW(srcname, srcpath);
205 strcatW(srcname, fd.cFileName);
207 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
208 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
210 /* Skip . and .., and directories */
211 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
212 overwrite = FALSE;
213 WINE_TRACE("Skipping directories\n");
216 /* Prompt before overwriting */
217 else if (!overwrite) {
218 attribs = GetFileAttributes(outname);
219 if (attribs != INVALID_FILE_ATTRIBUTES) {
220 WCHAR buffer[MAXSTRING];
221 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
222 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
224 else overwrite = TRUE;
227 /* Do the copy as appropriate */
228 if (overwrite) {
229 status = CopyFile (srcname, outname, FALSE);
230 if (!status) WCMD_print_error ();
233 } while (FindNextFile(hff, &fd) != 0);
234 FindClose (hff);
235 } else {
236 status = ERROR_FILE_NOT_FOUND;
237 WCMD_print_error ();
241 /****************************************************************************
242 * WCMD_create_dir
244 * Create a directory.
246 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
247 * they do not already exist.
250 static BOOL create_full_path(WCHAR* path)
252 int len;
253 WCHAR *new_path;
254 BOOL ret = TRUE;
256 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
257 strcpyW(new_path,path);
259 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
260 new_path[len - 1] = 0;
262 while (!CreateDirectory(new_path,NULL))
264 WCHAR *slash;
265 DWORD last_error = GetLastError();
266 if (last_error == ERROR_ALREADY_EXISTS)
267 break;
269 if (last_error != ERROR_PATH_NOT_FOUND)
271 ret = FALSE;
272 break;
275 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
277 ret = FALSE;
278 break;
281 len = slash - new_path;
282 new_path[len] = 0;
283 if (!create_full_path(new_path))
285 ret = FALSE;
286 break;
288 new_path[len] = '\\';
290 HeapFree(GetProcessHeap(),0,new_path);
291 return ret;
294 void WCMD_create_dir (void) {
296 if (param1[0] == 0x00) {
297 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
298 return;
300 if (!create_full_path(param1)) WCMD_print_error ();
303 /****************************************************************************
304 * WCMD_delete
306 * Delete a file or wildcarded set.
308 * Note on /A:
309 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
310 * - Each set is a pattern, eg /ahr /as-r means
311 * readonly+hidden OR nonreadonly system files
312 * - The '-' applies to a single field, ie /a:-hr means read only
313 * non-hidden files
316 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
318 int argno = 0;
319 int argsProcessed = 0;
320 WCHAR *argN = command;
321 BOOL foundAny = FALSE;
322 static const WCHAR parmA[] = {'/','A','\0'};
323 static const WCHAR parmQ[] = {'/','Q','\0'};
324 static const WCHAR parmP[] = {'/','P','\0'};
325 static const WCHAR parmS[] = {'/','S','\0'};
326 static const WCHAR parmF[] = {'/','F','\0'};
328 /* If not recursing, clear error flag */
329 if (expectDir) errorlevel = 0;
331 /* Loop through all args */
332 while (argN) {
333 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
334 WCHAR argCopy[MAX_PATH];
336 if (argN && argN[0] != '/') {
338 WIN32_FIND_DATA fd;
339 HANDLE hff;
340 WCHAR fpath[MAX_PATH];
341 WCHAR *p;
342 BOOL handleParm = TRUE;
343 BOOL found = FALSE;
344 static const WCHAR anyExt[]= {'.','*','\0'};
346 strcpyW(argCopy, thisArg);
347 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
348 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
349 argsProcessed++;
351 /* If filename part of parameter is * or *.*, prompt unless
352 /Q supplied. */
353 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
355 WCHAR drive[10];
356 WCHAR dir[MAX_PATH];
357 WCHAR fname[MAX_PATH];
358 WCHAR ext[MAX_PATH];
360 /* Convert path into actual directory spec */
361 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
362 WCMD_splitpath(fpath, drive, dir, fname, ext);
364 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
365 if ((strcmpW(fname, starW) == 0) &&
366 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
367 BOOL ok;
368 WCHAR question[MAXSTRING];
369 static const WCHAR fmt[] = {'%','s',' ','\0'};
371 /* Note: Flag as found, to avoid file not found message */
372 found = TRUE;
374 /* Ask for confirmation */
375 wsprintf(question, fmt, fpath);
376 ok = WCMD_ask_confirm(question, TRUE, NULL);
378 /* Abort if answer is 'N' */
379 if (!ok) continue;
383 /* First, try to delete in the current directory */
384 hff = FindFirstFile (argCopy, &fd);
385 if (hff == INVALID_HANDLE_VALUE) {
386 handleParm = FALSE;
387 } else {
388 found = TRUE;
391 /* Support del <dirname> by just deleting all files dirname\* */
392 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
393 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
394 WCHAR modifiedParm[MAX_PATH];
395 static const WCHAR slashStar[] = {'\\','*','\0'};
397 strcpyW(modifiedParm, argCopy);
398 strcatW(modifiedParm, slashStar);
399 FindClose(hff);
400 found = TRUE;
401 WCMD_delete(modifiedParm, FALSE);
403 } else if (handleParm) {
405 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
406 strcpyW (fpath, argCopy);
407 do {
408 p = strrchrW (fpath, '\\');
409 if (p != NULL) {
410 *++p = '\0';
411 strcatW (fpath, fd.cFileName);
413 else strcpyW (fpath, fd.cFileName);
414 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
415 BOOL ok = TRUE;
416 WCHAR *nextA = strstrW (quals, parmA);
418 /* Handle attribute matching (/A) */
419 if (nextA != NULL) {
420 ok = FALSE;
421 while (nextA != NULL && !ok) {
423 WCHAR *thisA = (nextA+2);
424 BOOL stillOK = TRUE;
426 /* Skip optional : */
427 if (*thisA == ':') thisA++;
429 /* Parse each of the /A[:]xxx in turn */
430 while (*thisA && *thisA != '/') {
431 BOOL negate = FALSE;
432 BOOL attribute = FALSE;
434 /* Match negation of attribute first */
435 if (*thisA == '-') {
436 negate=TRUE;
437 thisA++;
440 /* Match attribute */
441 switch (*thisA) {
442 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
443 break;
444 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
445 break;
446 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
447 break;
448 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
449 break;
450 default:
451 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
454 /* Now check result, keeping a running boolean about whether it
455 matches all parsed attributes so far */
456 if (attribute && !negate) {
457 stillOK = stillOK;
458 } else if (!attribute && negate) {
459 stillOK = stillOK;
460 } else {
461 stillOK = FALSE;
463 thisA++;
466 /* Save the running total as the final result */
467 ok = stillOK;
469 /* Step on to next /A set */
470 nextA = strstrW (nextA+1, parmA);
474 /* /P means prompt for each file */
475 if (ok && strstrW (quals, parmP) != NULL) {
476 WCHAR question[MAXSTRING];
478 /* Ask for confirmation */
479 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
480 ok = WCMD_ask_confirm(question, FALSE, NULL);
483 /* Only proceed if ok to */
484 if (ok) {
486 /* If file is read only, and /F supplied, delete it */
487 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
488 strstrW (quals, parmF) != NULL) {
489 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
492 /* Now do the delete */
493 if (!DeleteFile (fpath)) WCMD_print_error ();
497 } while (FindNextFile(hff, &fd) != 0);
498 FindClose (hff);
501 /* Now recurse into all subdirectories handling the parameter in the same way */
502 if (strstrW (quals, parmS) != NULL) {
504 WCHAR thisDir[MAX_PATH];
505 int cPos;
507 WCHAR drive[10];
508 WCHAR dir[MAX_PATH];
509 WCHAR fname[MAX_PATH];
510 WCHAR ext[MAX_PATH];
512 /* Convert path into actual directory spec */
513 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
514 WCMD_splitpath(thisDir, drive, dir, fname, ext);
516 strcpyW(thisDir, drive);
517 strcatW(thisDir, dir);
518 cPos = strlenW(thisDir);
520 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
522 /* Append '*' to the directory */
523 thisDir[cPos] = '*';
524 thisDir[cPos+1] = 0x00;
526 hff = FindFirstFile (thisDir, &fd);
528 /* Remove residual '*' */
529 thisDir[cPos] = 0x00;
531 if (hff != INVALID_HANDLE_VALUE) {
532 DIRECTORY_STACK *allDirs = NULL;
533 DIRECTORY_STACK *lastEntry = NULL;
535 do {
536 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
537 (strcmpW(fd.cFileName, dotdotW) != 0) &&
538 (strcmpW(fd.cFileName, dotW) != 0)) {
540 DIRECTORY_STACK *nextDir;
541 WCHAR subParm[MAX_PATH];
543 /* Work out search parameter in sub dir */
544 strcpyW (subParm, thisDir);
545 strcatW (subParm, fd.cFileName);
546 strcatW (subParm, slashW);
547 strcatW (subParm, fname);
548 strcatW (subParm, ext);
549 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
551 /* Allocate memory, add to list */
552 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
553 if (allDirs == NULL) allDirs = nextDir;
554 if (lastEntry != NULL) lastEntry->next = nextDir;
555 lastEntry = nextDir;
556 nextDir->next = NULL;
557 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
558 (strlenW(subParm)+1) * sizeof(WCHAR));
559 strcpyW(nextDir->dirName, subParm);
561 } while (FindNextFile(hff, &fd) != 0);
562 FindClose (hff);
564 /* Go through each subdir doing the delete */
565 while (allDirs != NULL) {
566 DIRECTORY_STACK *tempDir;
568 tempDir = allDirs->next;
569 found |= WCMD_delete (allDirs->dirName, FALSE);
571 HeapFree(GetProcessHeap(),0,allDirs->dirName);
572 HeapFree(GetProcessHeap(),0,allDirs);
573 allDirs = tempDir;
577 /* Keep running total to see if any found, and if not recursing
578 issue error message */
579 if (expectDir) {
580 if (!found) {
581 errorlevel = 1;
582 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
585 foundAny |= found;
589 /* Handle no valid args */
590 if (argsProcessed == 0) {
591 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
594 return foundAny;
597 /****************************************************************************
598 * WCMD_echo
600 * Echo input to the screen (or not). We don't try to emulate the bugs
601 * in DOS (try typing "ECHO ON AGAIN" for an example).
604 void WCMD_echo (const WCHAR *command) {
606 int count;
608 if ((command[0] == '.') && (command[1] == 0)) {
609 WCMD_output (newline);
610 return;
612 if (command[0]==' ')
613 command++;
614 count = strlenW(command);
615 if (count == 0) {
616 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
617 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
618 return;
620 if (lstrcmpiW(command, onW) == 0) {
621 echo_mode = 1;
622 return;
624 if (lstrcmpiW(command, offW) == 0) {
625 echo_mode = 0;
626 return;
628 WCMD_output_asis (command);
629 WCMD_output (newline);
633 /**************************************************************************
634 * WCMD_for
636 * Batch file loop processing.
638 * On entry: cmdList contains the syntax up to the set
639 * next cmdList and all in that bracket contain the set data
640 * next cmdlist contains the DO cmd
641 * following that is either brackets or && entries (as per if)
645 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
647 WIN32_FIND_DATA fd;
648 HANDLE hff;
649 int i;
650 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
651 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
652 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
653 WCHAR variable[4];
654 WCHAR *firstCmd;
655 int thisDepth;
657 WCHAR *curPos = p;
658 BOOL expandDirs = FALSE;
659 BOOL useNumbers = FALSE;
660 BOOL doRecursive = FALSE;
661 BOOL doFileset = FALSE;
662 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
663 int itemNum;
664 CMD_LIST *thisCmdStart;
667 /* Handle optional qualifiers (multiple are allowed) */
668 while (*curPos && *curPos == '/') {
669 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
670 curPos++;
671 switch (toupperW(*curPos)) {
672 case 'D': curPos++; expandDirs = TRUE; break;
673 case 'L': curPos++; useNumbers = TRUE; break;
675 /* Recursive is special case - /R can have an optional path following it */
676 /* filenamesets are another special case - /F can have an optional options following it */
677 case 'R':
678 case 'F':
680 BOOL isRecursive = (*curPos == 'R');
682 if (isRecursive) doRecursive = TRUE;
683 else doFileset = TRUE;
685 /* Skip whitespace */
686 curPos++;
687 while (*curPos && *curPos==' ') curPos++;
689 /* Next parm is either qualifier, path/options or variable -
690 only care about it if it is the path/options */
691 if (*curPos && *curPos != '/' && *curPos != '%') {
692 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
693 else WINE_FIXME("/F needs to handle options\n");
695 break;
697 default:
698 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
699 curPos++;
702 /* Skip whitespace between qualifiers */
703 while (*curPos && *curPos==' ') curPos++;
706 /* Skip whitespace before variable */
707 while (*curPos && *curPos==' ') curPos++;
709 /* Ensure line continues with variable */
710 if (!*curPos || *curPos != '%') {
711 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
712 return;
715 /* Variable should follow */
716 i = 0;
717 while (curPos[i] && curPos[i]!=' ') i++;
718 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
719 variable[i] = 0x00;
720 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
721 curPos = &curPos[i];
723 /* Skip whitespace before IN */
724 while (*curPos && *curPos==' ') curPos++;
726 /* Ensure line continues with IN */
727 if (!*curPos || lstrcmpiW (curPos, inW)) {
728 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
729 return;
732 /* Save away where the set of data starts and the variable */
733 thisDepth = (*cmdList)->bracketDepth;
734 *cmdList = (*cmdList)->nextcommand;
735 setStart = (*cmdList);
737 /* Skip until the close bracket */
738 WINE_TRACE("Searching %p as the set\n", *cmdList);
739 while (*cmdList &&
740 (*cmdList)->command != NULL &&
741 (*cmdList)->bracketDepth > thisDepth) {
742 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
743 *cmdList = (*cmdList)->nextcommand;
746 /* Skip the close bracket, if there is one */
747 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
749 /* Syntax error if missing close bracket, or nothing following it
750 and once we have the complete set, we expect a DO */
751 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
752 if ((*cmdList == NULL) ||
753 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
754 (*cmdList)->command, 3, doW, -1) != 2)) {
755 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
756 return;
759 /* Save away the starting position for the commands (and offset for the
760 first one */
761 cmdStart = *cmdList;
762 cmdEnd = *cmdList;
763 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
764 itemNum = 0;
766 thisSet = setStart;
767 /* Loop through all set entries */
768 while (thisSet &&
769 thisSet->command != NULL &&
770 thisSet->bracketDepth >= thisDepth) {
772 /* Loop through all entries on the same line */
773 WCHAR *item;
774 WCHAR *itemStart;
776 WINE_TRACE("Processing for set %p\n", thisSet);
777 i = 0;
778 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
781 * If the parameter within the set has a wildcard then search for matching files
782 * otherwise do a literal substitution.
784 static const WCHAR wildcards[] = {'*','?','\0'};
785 thisCmdStart = cmdStart;
787 itemNum++;
788 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
790 if (!useNumbers && !doFileset) {
791 if (strpbrkW (item, wildcards)) {
792 hff = FindFirstFile (item, &fd);
793 if (hff != INVALID_HANDLE_VALUE) {
794 do {
795 BOOL isDirectory = FALSE;
797 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
799 /* Handle as files or dirs appropriately, but ignore . and .. */
800 if (isDirectory == expandDirs &&
801 (strcmpW(fd.cFileName, dotdotW) != 0) &&
802 (strcmpW(fd.cFileName, dotW) != 0))
804 thisCmdStart = cmdStart;
805 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
806 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
807 fd.cFileName, FALSE, TRUE);
810 } while (FindNextFile(hff, &fd) != 0);
811 FindClose (hff);
813 } else {
814 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
817 } else if (useNumbers) {
818 /* Convert the first 3 numbers to signed longs and save */
819 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
820 /* else ignore them! */
822 /* Filesets - either a list of files, or a command to run and parse the output */
823 } else if (doFileset && *itemStart != '"') {
825 HANDLE input;
826 WCHAR temp_file[MAX_PATH];
828 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
829 wine_dbgstr_w(item));
831 /* If backquote or single quote, we need to launch that command
832 and parse the results - use a temporary file */
833 if (*itemStart == '`' || *itemStart == '\'') {
835 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
836 static const WCHAR redirOut[] = {'>','%','s','\0'};
837 static const WCHAR cmdW[] = {'C','M','D','\0'};
839 /* Remove trailing character */
840 itemStart[strlenW(itemStart)-1] = 0x00;
842 /* Get temp filename */
843 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
844 GetTempFileName (temp_path, cmdW, 0, temp_file);
846 /* Execute program and redirect output */
847 wsprintf (temp_cmd, redirOut, (itemStart+1), temp_file);
848 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
850 /* Open the file, read line by line and process */
851 input = CreateFile (temp_file, GENERIC_READ, FILE_SHARE_READ,
852 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
853 } else {
855 /* Open the file, read line by line and process */
856 input = CreateFile (item, GENERIC_READ, FILE_SHARE_READ,
857 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
860 /* Process the input file */
861 if (input == INVALID_HANDLE_VALUE) {
862 WCMD_print_error ();
863 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
864 errorlevel = 1;
865 return; /* FOR loop aborts at first failure here */
867 } else {
869 WCHAR buffer[MAXSTRING] = {'\0'};
870 WCHAR *where, *parm;
872 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
874 /* Skip blank lines*/
875 parm = WCMD_parameter (buffer, 0, &where);
876 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
877 wine_dbgstr_w(buffer));
879 if (where) {
880 /* FIXME: The following should be moved into its own routine and
881 reused for the string literal parsing below */
882 thisCmdStart = cmdStart;
883 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
884 cmdEnd = thisCmdStart;
887 buffer[0] = 0x00;
890 CloseHandle (input);
893 /* Delete the temporary file */
894 if (*itemStart == '`' || *itemStart == '\'') {
895 DeleteFile (temp_file);
898 /* Filesets - A string literal */
899 } else if (doFileset && *itemStart == '"') {
900 WCHAR buffer[MAXSTRING] = {'\0'};
901 WCHAR *where, *parm;
903 /* Skip blank lines, and re-extract parameter now string has quotes removed */
904 strcpyW(buffer, item);
905 parm = WCMD_parameter (buffer, 0, &where);
906 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
907 wine_dbgstr_w(buffer));
909 if (where) {
910 /* FIXME: The following should be moved into its own routine and
911 reused for the string literal parsing below */
912 thisCmdStart = cmdStart;
913 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
914 cmdEnd = thisCmdStart;
918 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
919 cmdEnd = thisCmdStart;
920 i++;
923 /* Move onto the next set line */
924 thisSet = thisSet->nextcommand;
927 /* If /L is provided, now run the for loop */
928 if (useNumbers) {
929 WCHAR thisNum[20];
930 static const WCHAR fmt[] = {'%','d','\0'};
932 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
933 numbers[0], numbers[2], numbers[1]);
934 for (i=numbers[0];
935 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
936 i=i + numbers[1]) {
938 sprintfW(thisNum, fmt, i);
939 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
941 thisCmdStart = cmdStart;
942 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
943 cmdEnd = thisCmdStart;
947 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
948 all processing, OR it should be pointing to the end of && processing OR
949 it should be pointing at the NULL end of bracket for the DO. The return
950 value needs to be the NEXT command to execute, which it either is, or
951 we need to step over the closing bracket */
952 *cmdList = cmdEnd;
953 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
957 /*****************************************************************************
958 * WCMD_part_execute
960 * Execute a command, and any && or bracketed follow on to the command. The
961 * first command to be executed may not be at the front of the
962 * commands->thiscommand string (eg. it may point after a DO or ELSE)
964 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
965 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
967 CMD_LIST *curPosition = *cmdList;
968 int myDepth = (*cmdList)->bracketDepth;
970 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
971 cmdList, wine_dbgstr_w(firstcmd),
972 wine_dbgstr_w(variable), wine_dbgstr_w(value),
973 conditionTRUE);
975 /* Skip leading whitespace between condition and the command */
976 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
978 /* Process the first command, if there is one */
979 if (conditionTRUE && firstcmd && *firstcmd) {
980 WCHAR *command = WCMD_strdupW(firstcmd);
981 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
982 free (command);
986 /* If it didn't move the position, step to next command */
987 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
989 /* Process any other parts of the command */
990 if (*cmdList) {
991 BOOL processThese = TRUE;
993 if (isIF) processThese = conditionTRUE;
995 while (*cmdList) {
996 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
998 /* execute all appropriate commands */
999 curPosition = *cmdList;
1001 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1002 *cmdList,
1003 (*cmdList)->prevDelim,
1004 (*cmdList)->bracketDepth, myDepth);
1006 /* Execute any statements appended to the line */
1007 /* FIXME: Only if previous call worked for && or failed for || */
1008 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1009 (*cmdList)->prevDelim != CMD_ONSUCCESS) {
1010 if (processThese) {
1011 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1012 value, cmdList);
1014 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1016 /* Execute any appended to the statement with (...) */
1017 } else if ((*cmdList)->bracketDepth > myDepth) {
1018 if (processThese) {
1019 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1020 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1022 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1024 /* End of the command - does 'ELSE ' follow as the next command? */
1025 } else {
1026 if (isIF && CompareString (LOCALE_USER_DEFAULT,
1027 NORM_IGNORECASE | SORT_STRINGSORT,
1028 (*cmdList)->command, 5, ifElse, -1) == 2) {
1030 /* Swap between if and else processing */
1031 processThese = !processThese;
1033 /* Process the ELSE part */
1034 if (processThese) {
1035 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1037 /* Skip leading whitespace between condition and the command */
1038 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1039 if (*cmd) {
1040 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1043 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1044 } else {
1045 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1046 break;
1051 return;
1054 /**************************************************************************
1055 * WCMD_give_help
1057 * Simple on-line help. Help text is stored in the resource file.
1060 void WCMD_give_help (WCHAR *command) {
1062 int i;
1064 command = WCMD_strtrim_leading_spaces(command);
1065 if (strlenW(command) == 0) {
1066 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1068 else {
1069 for (i=0; i<=WCMD_EXIT; i++) {
1070 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1071 param1, -1, inbuilt[i], -1) == 2) {
1072 WCMD_output_asis (WCMD_LoadMessage(i));
1073 return;
1076 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
1078 return;
1081 /****************************************************************************
1082 * WCMD_go_to
1084 * Batch file jump instruction. Not the most efficient algorithm ;-)
1085 * Prints error message if the specified label cannot be found - the file pointer is
1086 * then at EOF, effectively stopping the batch file.
1087 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1090 void WCMD_goto (CMD_LIST **cmdList) {
1092 WCHAR string[MAX_PATH];
1094 /* Do not process any more parts of a processed multipart or multilines command */
1095 if (cmdList) *cmdList = NULL;
1097 if (param1[0] == 0x00) {
1098 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1099 return;
1101 if (context != NULL) {
1102 WCHAR *paramStart = param1;
1103 static const WCHAR eofW[] = {':','e','o','f','\0'};
1105 /* Handle special :EOF label */
1106 if (lstrcmpiW (eofW, param1) == 0) {
1107 context -> skip_rest = TRUE;
1108 return;
1111 /* Support goto :label as well as goto label */
1112 if (*paramStart == ':') paramStart++;
1114 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1115 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1116 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1118 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1120 return;
1123 /*****************************************************************************
1124 * WCMD_pushd
1126 * Push a directory onto the stack
1129 void WCMD_pushd (WCHAR *command) {
1130 struct env_stack *curdir;
1131 WCHAR *thisdir;
1132 static const WCHAR parmD[] = {'/','D','\0'};
1134 if (strchrW(command, '/') != NULL) {
1135 SetLastError(ERROR_INVALID_PARAMETER);
1136 WCMD_print_error();
1137 return;
1140 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1141 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1142 if( !curdir || !thisdir ) {
1143 LocalFree(curdir);
1144 LocalFree(thisdir);
1145 WINE_ERR ("out of memory\n");
1146 return;
1149 /* Change directory using CD code with /D parameter */
1150 strcpyW(quals, parmD);
1151 GetCurrentDirectoryW (1024, thisdir);
1152 errorlevel = 0;
1153 WCMD_setshow_default(command);
1154 if (errorlevel) {
1155 LocalFree(curdir);
1156 LocalFree(thisdir);
1157 return;
1158 } else {
1159 curdir -> next = pushd_directories;
1160 curdir -> strings = thisdir;
1161 if (pushd_directories == NULL) {
1162 curdir -> u.stackdepth = 1;
1163 } else {
1164 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1166 pushd_directories = curdir;
1171 /*****************************************************************************
1172 * WCMD_popd
1174 * Pop a directory from the stack
1177 void WCMD_popd (void) {
1178 struct env_stack *temp = pushd_directories;
1180 if (!pushd_directories)
1181 return;
1183 /* pop the old environment from the stack, and make it the current dir */
1184 pushd_directories = temp->next;
1185 SetCurrentDirectoryW(temp->strings);
1186 LocalFree (temp->strings);
1187 LocalFree (temp);
1190 /****************************************************************************
1191 * WCMD_if
1193 * Batch file conditional.
1195 * On entry, cmdlist will point to command containing the IF, and optionally
1196 * the first command to execute (if brackets not found)
1197 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1198 * If ('s were found, execute all within that bracket
1199 * Command may optionally be followed by an ELSE - need to skip instructions
1200 * in the else using the same logic
1202 * FIXME: Much more syntax checking needed!
1205 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1207 int negate = 0, test = 0;
1208 WCHAR condition[MAX_PATH], *command, *s;
1209 static const WCHAR notW[] = {'n','o','t','\0'};
1210 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1211 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1212 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1213 static const WCHAR eqeqW[] = {'=','=','\0'};
1215 if (!lstrcmpiW (param1, notW)) {
1216 negate = 1;
1217 strcpyW (condition, param2);
1219 else {
1220 strcpyW (condition, param1);
1222 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1224 if (!lstrcmpiW (condition, errlvlW)) {
1225 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1226 WCMD_parameter (p, 2+negate, &command);
1228 else if (!lstrcmpiW (condition, existW)) {
1229 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1230 test = 1;
1232 WCMD_parameter (p, 2+negate, &command);
1234 else if (!lstrcmpiW (condition, defdW)) {
1235 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1236 test = 1;
1238 WCMD_parameter (p, 2+negate, &command);
1240 else if ((s = strstrW (p, eqeqW))) {
1241 s += 2;
1242 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1243 WCMD_parameter (s, 1, &command);
1245 else {
1246 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1247 return;
1250 /* Process rest of IF statement which is on the same line
1251 Note: This may process all or some of the cmdList (eg a GOTO) */
1252 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1255 /****************************************************************************
1256 * WCMD_move
1258 * Move a file, directory tree or wildcarded set of files.
1261 void WCMD_move (void) {
1263 int status;
1264 WIN32_FIND_DATA fd;
1265 HANDLE hff;
1266 WCHAR input[MAX_PATH];
1267 WCHAR output[MAX_PATH];
1268 WCHAR drive[10];
1269 WCHAR dir[MAX_PATH];
1270 WCHAR fname[MAX_PATH];
1271 WCHAR ext[MAX_PATH];
1273 if (param1[0] == 0x00) {
1274 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1275 return;
1278 /* If no destination supplied, assume current directory */
1279 if (param2[0] == 0x00) {
1280 strcpyW(param2, dotW);
1283 /* If 2nd parm is directory, then use original filename */
1284 /* Convert partial path to full path */
1285 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1286 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1287 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1288 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1290 /* Split into components */
1291 WCMD_splitpath(input, drive, dir, fname, ext);
1293 hff = FindFirstFile (input, &fd);
1294 while (hff != INVALID_HANDLE_VALUE) {
1295 WCHAR dest[MAX_PATH];
1296 WCHAR src[MAX_PATH];
1297 DWORD attribs;
1299 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1301 /* Build src & dest name */
1302 strcpyW(src, drive);
1303 strcatW(src, dir);
1305 /* See if dest is an existing directory */
1306 attribs = GetFileAttributes(output);
1307 if (attribs != INVALID_FILE_ATTRIBUTES &&
1308 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1309 strcpyW(dest, output);
1310 strcatW(dest, slashW);
1311 strcatW(dest, fd.cFileName);
1312 } else {
1313 strcpyW(dest, output);
1316 strcatW(src, fd.cFileName);
1318 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1319 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1321 /* Check if file is read only, otherwise move it */
1322 attribs = GetFileAttributes(src);
1323 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1324 (attribs & FILE_ATTRIBUTE_READONLY)) {
1325 SetLastError(ERROR_ACCESS_DENIED);
1326 status = 0;
1327 } else {
1328 BOOL ok = TRUE;
1330 /* If destination exists, prompt unless /Y supplied */
1331 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1332 BOOL force = FALSE;
1333 WCHAR copycmd[MAXSTRING];
1334 int len;
1336 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1337 if (strstrW (quals, parmNoY))
1338 force = FALSE;
1339 else if (strstrW (quals, parmY))
1340 force = TRUE;
1341 else {
1342 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1343 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1344 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1345 && ! lstrcmpiW (copycmd, parmY));
1348 /* Prompt if overwriting */
1349 if (!force) {
1350 WCHAR question[MAXSTRING];
1351 WCHAR yesChar[10];
1353 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1355 /* Ask for confirmation */
1356 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1357 ok = WCMD_ask_confirm(question, FALSE, NULL);
1359 /* So delete the destination prior to the move */
1360 if (ok) {
1361 if (!DeleteFile (dest)) {
1362 WCMD_print_error ();
1363 errorlevel = 1;
1364 ok = FALSE;
1370 if (ok) {
1371 status = MoveFile (src, dest);
1372 } else {
1373 status = 1; /* Anything other than 0 to prevent error msg below */
1377 if (!status) {
1378 WCMD_print_error ();
1379 errorlevel = 1;
1382 /* Step on to next match */
1383 if (FindNextFile(hff, &fd) == 0) {
1384 FindClose(hff);
1385 hff = INVALID_HANDLE_VALUE;
1386 break;
1391 /****************************************************************************
1392 * WCMD_pause
1394 * Wait for keyboard input.
1397 void WCMD_pause (void) {
1399 DWORD count;
1400 WCHAR string[32];
1402 WCMD_output (anykey);
1403 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1404 sizeof(string)/sizeof(WCHAR), &count, NULL);
1407 /****************************************************************************
1408 * WCMD_remove_dir
1410 * Delete a directory.
1413 void WCMD_remove_dir (WCHAR *command) {
1415 int argno = 0;
1416 int argsProcessed = 0;
1417 WCHAR *argN = command;
1418 static const WCHAR parmS[] = {'/','S','\0'};
1419 static const WCHAR parmQ[] = {'/','Q','\0'};
1421 /* Loop through all args */
1422 while (argN) {
1423 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1424 if (argN && argN[0] != '/') {
1425 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1426 wine_dbgstr_w(quals));
1427 argsProcessed++;
1429 /* If subdirectory search not supplied, just try to remove
1430 and report error if it fails (eg if it contains a file) */
1431 if (strstrW (quals, parmS) == NULL) {
1432 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1434 /* Otherwise use ShFileOp to recursively remove a directory */
1435 } else {
1437 SHFILEOPSTRUCT lpDir;
1439 /* Ask first */
1440 if (strstrW (quals, parmQ) == NULL) {
1441 BOOL ok;
1442 WCHAR question[MAXSTRING];
1443 static const WCHAR fmt[] = {'%','s',' ','\0'};
1445 /* Ask for confirmation */
1446 wsprintf(question, fmt, thisArg);
1447 ok = WCMD_ask_confirm(question, TRUE, NULL);
1449 /* Abort if answer is 'N' */
1450 if (!ok) return;
1453 /* Do the delete */
1454 lpDir.hwnd = NULL;
1455 lpDir.pTo = NULL;
1456 lpDir.pFrom = thisArg;
1457 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1458 lpDir.wFunc = FO_DELETE;
1459 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1464 /* Handle no valid args */
1465 if (argsProcessed == 0) {
1466 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1467 return;
1472 /****************************************************************************
1473 * WCMD_rename
1475 * Rename a file.
1478 void WCMD_rename (void) {
1480 int status;
1481 HANDLE hff;
1482 WIN32_FIND_DATA fd;
1483 WCHAR input[MAX_PATH];
1484 WCHAR *dotDst = NULL;
1485 WCHAR drive[10];
1486 WCHAR dir[MAX_PATH];
1487 WCHAR fname[MAX_PATH];
1488 WCHAR ext[MAX_PATH];
1489 DWORD attribs;
1491 errorlevel = 0;
1493 /* Must be at least two args */
1494 if (param1[0] == 0x00 || param2[0] == 0x00) {
1495 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1496 errorlevel = 1;
1497 return;
1500 /* Destination cannot contain a drive letter or directory separator */
1501 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1502 SetLastError(ERROR_INVALID_PARAMETER);
1503 WCMD_print_error();
1504 errorlevel = 1;
1505 return;
1508 /* Convert partial path to full path */
1509 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1510 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1511 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1512 dotDst = strchrW(param2, '.');
1514 /* Split into components */
1515 WCMD_splitpath(input, drive, dir, fname, ext);
1517 hff = FindFirstFile (input, &fd);
1518 while (hff != INVALID_HANDLE_VALUE) {
1519 WCHAR dest[MAX_PATH];
1520 WCHAR src[MAX_PATH];
1521 WCHAR *dotSrc = NULL;
1522 int dirLen;
1524 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1526 /* FIXME: If dest name or extension is *, replace with filename/ext
1527 part otherwise use supplied name. This supports:
1528 ren *.fred *.jim
1529 ren jim.* fred.* etc
1530 However, windows has a more complex algorithm supporting eg
1531 ?'s and *'s mid name */
1532 dotSrc = strchrW(fd.cFileName, '.');
1534 /* Build src & dest name */
1535 strcpyW(src, drive);
1536 strcatW(src, dir);
1537 strcpyW(dest, src);
1538 dirLen = strlenW(src);
1539 strcatW(src, fd.cFileName);
1541 /* Build name */
1542 if (param2[0] == '*') {
1543 strcatW(dest, fd.cFileName);
1544 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1545 } else {
1546 strcatW(dest, param2);
1547 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1550 /* Build Extension */
1551 if (dotDst && (*(dotDst+1)=='*')) {
1552 if (dotSrc) strcatW(dest, dotSrc);
1553 } else if (dotDst) {
1554 if (dotDst) strcatW(dest, dotDst);
1557 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1558 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1560 /* Check if file is read only, otherwise move it */
1561 attribs = GetFileAttributes(src);
1562 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1563 (attribs & FILE_ATTRIBUTE_READONLY)) {
1564 SetLastError(ERROR_ACCESS_DENIED);
1565 status = 0;
1566 } else {
1567 status = MoveFile (src, dest);
1570 if (!status) {
1571 WCMD_print_error ();
1572 errorlevel = 1;
1575 /* Step on to next match */
1576 if (FindNextFile(hff, &fd) == 0) {
1577 FindClose(hff);
1578 hff = INVALID_HANDLE_VALUE;
1579 break;
1584 /*****************************************************************************
1585 * WCMD_dupenv
1587 * Make a copy of the environment.
1589 static WCHAR *WCMD_dupenv( const WCHAR *env )
1591 WCHAR *env_copy;
1592 int len;
1594 if( !env )
1595 return NULL;
1597 len = 0;
1598 while ( env[len] )
1599 len += (strlenW(&env[len]) + 1);
1601 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1602 if (!env_copy)
1604 WINE_ERR("out of memory\n");
1605 return env_copy;
1607 memcpy (env_copy, env, len*sizeof (WCHAR));
1608 env_copy[len] = 0;
1610 return env_copy;
1613 /*****************************************************************************
1614 * WCMD_setlocal
1616 * setlocal pushes the environment onto a stack
1617 * Save the environment as unicode so we don't screw anything up.
1619 void WCMD_setlocal (const WCHAR *s) {
1620 WCHAR *env;
1621 struct env_stack *env_copy;
1622 WCHAR cwd[MAX_PATH];
1624 /* DISABLEEXTENSIONS ignored */
1626 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1627 if( !env_copy )
1629 WINE_ERR ("out of memory\n");
1630 return;
1633 env = GetEnvironmentStringsW ();
1635 env_copy->strings = WCMD_dupenv (env);
1636 if (env_copy->strings)
1638 env_copy->next = saved_environment;
1639 saved_environment = env_copy;
1641 /* Save the current drive letter */
1642 GetCurrentDirectory (MAX_PATH, cwd);
1643 env_copy->u.cwd = cwd[0];
1645 else
1646 LocalFree (env_copy);
1648 FreeEnvironmentStringsW (env);
1652 /*****************************************************************************
1653 * WCMD_endlocal
1655 * endlocal pops the environment off a stack
1656 * Note: When searching for '=', search from WCHAR position 1, to handle
1657 * special internal environment variables =C:, =D: etc
1659 void WCMD_endlocal (void) {
1660 WCHAR *env, *old, *p;
1661 struct env_stack *temp;
1662 int len, n;
1664 if (!saved_environment)
1665 return;
1667 /* pop the old environment from the stack */
1668 temp = saved_environment;
1669 saved_environment = temp->next;
1671 /* delete the current environment, totally */
1672 env = GetEnvironmentStringsW ();
1673 old = WCMD_dupenv (GetEnvironmentStringsW ());
1674 len = 0;
1675 while (old[len]) {
1676 n = strlenW(&old[len]) + 1;
1677 p = strchrW(&old[len] + 1, '=');
1678 if (p)
1680 *p++ = 0;
1681 SetEnvironmentVariableW (&old[len], NULL);
1683 len += n;
1685 LocalFree (old);
1686 FreeEnvironmentStringsW (env);
1688 /* restore old environment */
1689 env = temp->strings;
1690 len = 0;
1691 while (env[len]) {
1692 n = strlenW(&env[len]) + 1;
1693 p = strchrW(&env[len] + 1, '=');
1694 if (p)
1696 *p++ = 0;
1697 SetEnvironmentVariableW (&env[len], p);
1699 len += n;
1702 /* Restore current drive letter */
1703 if (IsCharAlpha(temp->u.cwd)) {
1704 WCHAR envvar[4];
1705 WCHAR cwd[MAX_PATH];
1706 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1708 wsprintf(envvar, fmt, temp->u.cwd);
1709 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1710 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1711 SetCurrentDirectory(cwd);
1715 LocalFree (env);
1716 LocalFree (temp);
1719 /*****************************************************************************
1720 * WCMD_setshow_attrib
1722 * Display and optionally sets DOS attributes on a file or directory
1724 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1725 * As a result only the Readonly flag is correctly reported, the Archive bit
1726 * is always set and the rest are not implemented. We do the Right Thing anyway.
1728 * FIXME: No SET functionality.
1732 void WCMD_setshow_attrib (void) {
1734 DWORD count;
1735 HANDLE hff;
1736 WIN32_FIND_DATA fd;
1737 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1739 if (param1[0] == '-') {
1740 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1741 return;
1744 if (strlenW(param1) == 0) {
1745 static const WCHAR slashStarW[] = {'\\','*','\0'};
1747 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1748 strcatW (param1, slashStarW);
1751 hff = FindFirstFile (param1, &fd);
1752 if (hff == INVALID_HANDLE_VALUE) {
1753 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1755 else {
1756 do {
1757 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1758 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1759 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1760 flags[0] = 'H';
1762 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1763 flags[1] = 'S';
1765 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1766 flags[2] = 'A';
1768 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1769 flags[3] = 'R';
1771 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1772 flags[4] = 'T';
1774 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1775 flags[5] = 'C';
1777 WCMD_output (fmt, flags, fd.cFileName);
1778 for (count=0; count < 8; count++) flags[count] = ' ';
1780 } while (FindNextFile(hff, &fd) != 0);
1782 FindClose (hff);
1785 /*****************************************************************************
1786 * WCMD_setshow_default
1788 * Set/Show the current default directory
1791 void WCMD_setshow_default (WCHAR *command) {
1793 BOOL status;
1794 WCHAR string[1024];
1795 WCHAR cwd[1024];
1796 WCHAR *pos;
1797 WIN32_FIND_DATA fd;
1798 HANDLE hff;
1799 static const WCHAR parmD[] = {'/','D','\0'};
1801 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1803 /* Skip /D and trailing whitespace if on the front of the command line */
1804 if (CompareString (LOCALE_USER_DEFAULT,
1805 NORM_IGNORECASE | SORT_STRINGSORT,
1806 command, 2, parmD, -1) == 2) {
1807 command += 2;
1808 while (*command && *command==' ') command++;
1811 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1812 if (strlenW(command) == 0) {
1813 strcatW (cwd, newline);
1814 WCMD_output (cwd);
1816 else {
1817 /* Remove any double quotes, which may be in the
1818 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1819 pos = string;
1820 while (*command) {
1821 if (*command != '"') *pos++ = *command;
1822 command++;
1824 *pos = 0x00;
1826 /* Search for appropriate directory */
1827 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1828 hff = FindFirstFile (string, &fd);
1829 while (hff != INVALID_HANDLE_VALUE) {
1830 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1831 WCHAR fpath[MAX_PATH];
1832 WCHAR drive[10];
1833 WCHAR dir[MAX_PATH];
1834 WCHAR fname[MAX_PATH];
1835 WCHAR ext[MAX_PATH];
1836 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1838 /* Convert path into actual directory spec */
1839 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1840 WCMD_splitpath(fpath, drive, dir, fname, ext);
1842 /* Rebuild path */
1843 wsprintf(string, fmt, drive, dir, fd.cFileName);
1845 FindClose(hff);
1846 hff = INVALID_HANDLE_VALUE;
1847 break;
1850 /* Step on to next match */
1851 if (FindNextFile(hff, &fd) == 0) {
1852 FindClose(hff);
1853 hff = INVALID_HANDLE_VALUE;
1854 break;
1858 /* Change to that directory */
1859 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1861 status = SetCurrentDirectory (string);
1862 if (!status) {
1863 errorlevel = 1;
1864 WCMD_print_error ();
1865 return;
1866 } else {
1868 /* Save away the actual new directory, to store as current location */
1869 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1871 /* Restore old directory if drive letter would change, and
1872 CD x:\directory /D (or pushd c:\directory) not supplied */
1873 if ((strstrW(quals, parmD) == NULL) &&
1874 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1875 SetCurrentDirectory(cwd);
1879 /* Set special =C: type environment variable, for drive letter of
1880 change of directory, even if path was restored due to missing
1881 /D (allows changing drive letter when not resident on that
1882 drive */
1883 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1884 WCHAR env[4];
1885 strcpyW(env, equalW);
1886 memcpy(env+1, string, 2 * sizeof(WCHAR));
1887 env[3] = 0x00;
1888 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1889 SetEnvironmentVariable(env, string);
1893 return;
1896 /****************************************************************************
1897 * WCMD_setshow_date
1899 * Set/Show the system date
1900 * FIXME: Can't change date yet
1903 void WCMD_setshow_date (void) {
1905 WCHAR curdate[64], buffer[64];
1906 DWORD count;
1907 static const WCHAR parmT[] = {'/','T','\0'};
1909 if (strlenW(param1) == 0) {
1910 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1911 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1912 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1913 if (strstrW (quals, parmT) == NULL) {
1914 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1915 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1916 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1917 if (count > 2) {
1918 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1922 else WCMD_print_error ();
1924 else {
1925 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1929 /****************************************************************************
1930 * WCMD_compare
1932 static int WCMD_compare( const void *a, const void *b )
1934 int r;
1935 const WCHAR * const *str_a = a, * const *str_b = b;
1936 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1937 *str_a, -1, *str_b, -1 );
1938 if( r == CSTR_LESS_THAN ) return -1;
1939 if( r == CSTR_GREATER_THAN ) return 1;
1940 return 0;
1943 /****************************************************************************
1944 * WCMD_setshow_sortenv
1946 * sort variables into order for display
1947 * Optionally only display those who start with a stub
1948 * returns the count displayed
1950 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1952 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1953 const WCHAR **str;
1955 if (stub) stublen = strlenW(stub);
1957 /* count the number of strings, and the total length */
1958 while ( s[len] ) {
1959 len += (strlenW(&s[len]) + 1);
1960 count++;
1963 /* add the strings to an array */
1964 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1965 if( !str )
1966 return 0;
1967 str[0] = s;
1968 for( i=1; i<count; i++ )
1969 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1971 /* sort the array */
1972 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1974 /* print it */
1975 for( i=0; i<count; i++ ) {
1976 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1977 NORM_IGNORECASE | SORT_STRINGSORT,
1978 str[i], stublen, stub, -1) == 2) {
1979 /* Don't display special internal variables */
1980 if (str[i][0] != '=') {
1981 WCMD_output_asis(str[i]);
1982 WCMD_output_asis(newline);
1983 displayedcount++;
1988 LocalFree( str );
1989 return displayedcount;
1992 /****************************************************************************
1993 * WCMD_setshow_env
1995 * Set/Show the environment variables
1998 void WCMD_setshow_env (WCHAR *s) {
2000 LPVOID env;
2001 WCHAR *p;
2002 int status;
2003 static const WCHAR parmP[] = {'/','P','\0'};
2005 errorlevel = 0;
2006 if (param1[0] == 0x00 && quals[0] == 0x00) {
2007 env = GetEnvironmentStrings ();
2008 WCMD_setshow_sortenv( env, NULL );
2009 return;
2012 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2013 if (CompareString (LOCALE_USER_DEFAULT,
2014 NORM_IGNORECASE | SORT_STRINGSORT,
2015 s, 2, parmP, -1) == 2) {
2016 WCHAR string[MAXSTRING];
2017 DWORD count;
2019 s += 2;
2020 while (*s && *s==' ') s++;
2022 /* If no parameter, or no '=' sign, return an error */
2023 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2024 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2025 return;
2028 /* Output the prompt */
2029 *p++ = '\0';
2030 if (strlenW(p) != 0) WCMD_output(p);
2032 /* Read the reply */
2033 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2034 sizeof(string)/sizeof(WCHAR), &count, NULL);
2035 if (count > 1) {
2036 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2037 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2038 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2039 wine_dbgstr_w(string));
2040 status = SetEnvironmentVariable (s, string);
2043 } else {
2044 DWORD gle;
2045 p = strchrW (s, '=');
2046 if (p == NULL) {
2047 env = GetEnvironmentStrings ();
2048 if (WCMD_setshow_sortenv( env, s ) == 0) {
2049 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2050 errorlevel = 1;
2052 return;
2054 *p++ = '\0';
2056 if (strlenW(p) == 0) p = NULL;
2057 status = SetEnvironmentVariable (s, p);
2058 gle = GetLastError();
2059 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2060 errorlevel = 1;
2061 } else if ((!status)) WCMD_print_error();
2065 /****************************************************************************
2066 * WCMD_setshow_path
2068 * Set/Show the path environment variable
2071 void WCMD_setshow_path (WCHAR *command) {
2073 WCHAR string[1024];
2074 DWORD status;
2075 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2076 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2078 if (strlenW(param1) == 0) {
2079 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2080 if (status != 0) {
2081 WCMD_output_asis ( pathEqW);
2082 WCMD_output_asis ( string);
2083 WCMD_output_asis ( newline);
2085 else {
2086 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2089 else {
2090 if (*command == '=') command++; /* Skip leading '=' */
2091 status = SetEnvironmentVariable (pathW, command);
2092 if (!status) WCMD_print_error();
2096 /****************************************************************************
2097 * WCMD_setshow_prompt
2099 * Set or show the command prompt.
2102 void WCMD_setshow_prompt (void) {
2104 WCHAR *s;
2105 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2107 if (strlenW(param1) == 0) {
2108 SetEnvironmentVariable (promptW, NULL);
2110 else {
2111 s = param1;
2112 while ((*s == '=') || (*s == ' ')) s++;
2113 if (strlenW(s) == 0) {
2114 SetEnvironmentVariable (promptW, NULL);
2116 else SetEnvironmentVariable (promptW, s);
2120 /****************************************************************************
2121 * WCMD_setshow_time
2123 * Set/Show the system time
2124 * FIXME: Can't change time yet
2127 void WCMD_setshow_time (void) {
2129 WCHAR curtime[64], buffer[64];
2130 DWORD count;
2131 SYSTEMTIME st;
2132 static const WCHAR parmT[] = {'/','T','\0'};
2134 if (strlenW(param1) == 0) {
2135 GetLocalTime(&st);
2136 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2137 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2138 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
2139 if (strstrW (quals, parmT) == NULL) {
2140 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2141 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2142 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2143 if (count > 2) {
2144 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2148 else WCMD_print_error ();
2150 else {
2151 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2155 /****************************************************************************
2156 * WCMD_shift
2158 * Shift batch parameters.
2159 * Optional /n says where to start shifting (n=0-8)
2162 void WCMD_shift (WCHAR *command) {
2163 int start;
2165 if (context != NULL) {
2166 WCHAR *pos = strchrW(command, '/');
2167 int i;
2169 if (pos == NULL) {
2170 start = 0;
2171 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2172 start = (*(pos+1) - '0');
2173 } else {
2174 SetLastError(ERROR_INVALID_PARAMETER);
2175 WCMD_print_error();
2176 return;
2179 WINE_TRACE("Shifting variables, starting at %d\n", start);
2180 for (i=start;i<=8;i++) {
2181 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2183 context -> shift_count[9] = context -> shift_count[9] + 1;
2188 /****************************************************************************
2189 * WCMD_title
2191 * Set the console title
2193 void WCMD_title (WCHAR *command) {
2194 SetConsoleTitle(command);
2197 /****************************************************************************
2198 * WCMD_type
2200 * Copy a file to standard output.
2203 void WCMD_type (WCHAR *command) {
2205 int argno = 0;
2206 WCHAR *argN = command;
2207 BOOL writeHeaders = FALSE;
2209 if (param1[0] == 0x00) {
2210 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2211 return;
2214 if (param2[0] != 0x00) writeHeaders = TRUE;
2216 /* Loop through all args */
2217 errorlevel = 0;
2218 while (argN) {
2219 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2221 HANDLE h;
2222 WCHAR buffer[512];
2223 DWORD count;
2225 if (!argN) break;
2227 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2228 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2229 FILE_ATTRIBUTE_NORMAL, NULL);
2230 if (h == INVALID_HANDLE_VALUE) {
2231 WCMD_print_error ();
2232 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2233 errorlevel = 1;
2234 } else {
2235 if (writeHeaders) {
2236 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2237 WCMD_output(fmt, thisArg);
2239 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2240 if (count == 0) break; /* ReadFile reports success on EOF! */
2241 buffer[count] = 0;
2242 WCMD_output_asis (buffer);
2244 CloseHandle (h);
2249 /****************************************************************************
2250 * WCMD_more
2252 * Output either a file or stdin to screen in pages
2255 void WCMD_more (WCHAR *command) {
2257 int argno = 0;
2258 WCHAR *argN = command;
2259 BOOL useinput = FALSE;
2260 WCHAR moreStr[100];
2261 WCHAR moreStrPage[100];
2262 WCHAR buffer[512];
2263 DWORD count;
2264 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2265 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2266 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2267 ')',' ','-','-','\n','\0'};
2268 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2270 /* Prefix the NLS more with '-- ', then load the text */
2271 errorlevel = 0;
2272 strcpyW(moreStr, moreStart);
2273 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2274 (sizeof(moreStr)/sizeof(WCHAR))-3);
2276 if (param1[0] == 0x00) {
2278 /* Wine implements pipes via temporary files, and hence stdin is
2279 effectively reading from the file. This means the prompts for
2280 more are satisfied by the next line from the input (file). To
2281 avoid this, ensure stdin is to the console */
2282 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2283 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2284 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2285 FILE_ATTRIBUTE_NORMAL, 0);
2286 WINE_TRACE("No parms - working probably in pipe mode\n");
2287 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2289 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2290 once you get in this bit unless due to a pipe, its going to end badly... */
2291 useinput = TRUE;
2292 wsprintf(moreStrPage, moreFmt, moreStr);
2294 WCMD_enter_paged_mode(moreStrPage);
2295 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2296 if (count == 0) break; /* ReadFile reports success on EOF! */
2297 buffer[count] = 0;
2298 WCMD_output_asis (buffer);
2300 WCMD_leave_paged_mode();
2302 /* Restore stdin to what it was */
2303 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2304 CloseHandle(hConIn);
2306 return;
2307 } else {
2308 BOOL needsPause = FALSE;
2310 /* Loop through all args */
2311 WINE_TRACE("Parms supplied - working through each file\n");
2312 WCMD_enter_paged_mode(moreStrPage);
2314 while (argN) {
2315 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2316 HANDLE h;
2318 if (!argN) break;
2320 if (needsPause) {
2322 /* Wait */
2323 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2324 WCMD_leave_paged_mode();
2325 WCMD_output_asis(moreStrPage);
2326 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2327 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2328 WCMD_enter_paged_mode(moreStrPage);
2332 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2333 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2334 FILE_ATTRIBUTE_NORMAL, NULL);
2335 if (h == INVALID_HANDLE_VALUE) {
2336 WCMD_print_error ();
2337 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2338 errorlevel = 1;
2339 } else {
2340 ULONG64 curPos = 0;
2341 ULONG64 fileLen = 0;
2342 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2344 /* Get the file size */
2345 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2346 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2348 needsPause = TRUE;
2349 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2350 if (count == 0) break; /* ReadFile reports success on EOF! */
2351 buffer[count] = 0;
2352 curPos += count;
2354 /* Update % count (would be used in WCMD_output_asis as prompt) */
2355 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2357 WCMD_output_asis (buffer);
2359 CloseHandle (h);
2363 WCMD_leave_paged_mode();
2367 /****************************************************************************
2368 * WCMD_verify
2370 * Display verify flag.
2371 * FIXME: We don't actually do anything with the verify flag other than toggle
2372 * it...
2375 void WCMD_verify (WCHAR *command) {
2377 int count;
2379 count = strlenW(command);
2380 if (count == 0) {
2381 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2382 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2383 return;
2385 if (lstrcmpiW(command, onW) == 0) {
2386 verify_mode = 1;
2387 return;
2389 else if (lstrcmpiW(command, offW) == 0) {
2390 verify_mode = 0;
2391 return;
2393 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2396 /****************************************************************************
2397 * WCMD_version
2399 * Display version info.
2402 void WCMD_version (void) {
2404 WCMD_output (version_string);
2408 /****************************************************************************
2409 * WCMD_volume
2411 * Display volume info and/or set volume label. Returns 0 if error.
2414 int WCMD_volume (int mode, WCHAR *path) {
2416 DWORD count, serial;
2417 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2418 BOOL status;
2420 if (strlenW(path) == 0) {
2421 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2422 if (!status) {
2423 WCMD_print_error ();
2424 return 0;
2426 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2427 &serial, NULL, NULL, NULL, 0);
2429 else {
2430 static const WCHAR fmt[] = {'%','s','\\','\0'};
2431 if ((path[1] != ':') || (strlenW(path) != 2)) {
2432 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2433 return 0;
2435 wsprintf (curdir, fmt, path);
2436 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2437 &serial, NULL,
2438 NULL, NULL, 0);
2440 if (!status) {
2441 WCMD_print_error ();
2442 return 0;
2444 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2445 curdir[0], label, HIWORD(serial), LOWORD(serial));
2446 if (mode) {
2447 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2448 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2449 sizeof(string)/sizeof(WCHAR), &count, NULL);
2450 if (count > 1) {
2451 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2452 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2454 if (strlenW(path) != 0) {
2455 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2457 else {
2458 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2461 return 1;
2464 /**************************************************************************
2465 * WCMD_exit
2467 * Exit either the process, or just this batch program
2471 void WCMD_exit (CMD_LIST **cmdList) {
2473 static const WCHAR parmB[] = {'/','B','\0'};
2474 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2476 if (context && lstrcmpiW(quals, parmB) == 0) {
2477 errorlevel = rc;
2478 context -> skip_rest = TRUE;
2479 *cmdList = NULL;
2480 } else {
2481 ExitProcess(rc);
2485 /**************************************************************************
2486 * WCMD_ask_confirm
2488 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2489 * answer.
2491 * Returns True if Y (or A) answer is selected
2492 * If optionAll contains a pointer, ALL is allowed, and if answered
2493 * set to TRUE
2496 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2498 WCHAR msgbuffer[MAXSTRING];
2499 WCHAR Ybuffer[MAXSTRING];
2500 WCHAR Nbuffer[MAXSTRING];
2501 WCHAR Abuffer[MAXSTRING];
2502 WCHAR answer[MAX_PATH] = {'\0'};
2503 DWORD count = 0;
2505 /* Load the translated 'Are you sure', plus valid answers */
2506 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2507 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2508 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2509 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2511 /* Loop waiting on a Y or N */
2512 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2513 static const WCHAR startBkt[] = {' ','(','\0'};
2514 static const WCHAR endBkt[] = {')','?','\0'};
2516 WCMD_output_asis (message);
2517 if (showSureText) {
2518 WCMD_output_asis (msgbuffer);
2520 WCMD_output_asis (startBkt);
2521 WCMD_output_asis (Ybuffer);
2522 WCMD_output_asis (fslashW);
2523 WCMD_output_asis (Nbuffer);
2524 if (optionAll) {
2525 WCMD_output_asis (fslashW);
2526 WCMD_output_asis (Abuffer);
2528 WCMD_output_asis (endBkt);
2529 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2530 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2531 answer[0] = toupperW(answer[0]);
2534 /* Return the answer */
2535 return ((answer[0] == Ybuffer[0]) ||
2536 (optionAll && (answer[0] == Abuffer[0])));
2539 /*****************************************************************************
2540 * WCMD_assoc
2542 * Lists or sets file associations (assoc = TRUE)
2543 * Lists or sets file types (assoc = FALSE)
2545 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2547 HKEY key;
2548 DWORD accessOptions = KEY_READ;
2549 WCHAR *newValue;
2550 LONG rc = ERROR_SUCCESS;
2551 WCHAR keyValue[MAXSTRING];
2552 DWORD valueLen = MAXSTRING;
2553 HKEY readKey;
2554 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2555 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2557 /* See if parameter includes '=' */
2558 errorlevel = 0;
2559 newValue = strchrW(command, '=');
2560 if (newValue) accessOptions |= KEY_WRITE;
2562 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2563 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2564 accessOptions, &key) != ERROR_SUCCESS) {
2565 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2566 return;
2569 /* If no parameters then list all associations */
2570 if (*command == 0x00) {
2571 int index = 0;
2573 /* Enumerate all the keys */
2574 while (rc != ERROR_NO_MORE_ITEMS) {
2575 WCHAR keyName[MAXSTRING];
2576 DWORD nameLen;
2578 /* Find the next value */
2579 nameLen = MAXSTRING;
2580 rc = RegEnumKeyEx(key, index++,
2581 keyName, &nameLen,
2582 NULL, NULL, NULL, NULL);
2584 if (rc == ERROR_SUCCESS) {
2586 /* Only interested in extension ones if assoc, or others
2587 if not assoc */
2588 if ((keyName[0] == '.' && assoc) ||
2589 (!(keyName[0] == '.') && (!assoc)))
2591 WCHAR subkey[MAXSTRING];
2592 strcpyW(subkey, keyName);
2593 if (!assoc) strcatW(subkey, shOpCmdW);
2595 if (RegOpenKeyEx(key, subkey, 0,
2596 accessOptions, &readKey) == ERROR_SUCCESS) {
2598 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2599 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2600 (LPBYTE)keyValue, &valueLen);
2601 WCMD_output_asis(keyName);
2602 WCMD_output_asis(equalW);
2603 /* If no default value found, leave line empty after '=' */
2604 if (rc == ERROR_SUCCESS) {
2605 WCMD_output_asis(keyValue);
2607 WCMD_output_asis(newline);
2608 RegCloseKey(readKey);
2614 } else {
2616 /* Parameter supplied - if no '=' on command line, its a query */
2617 if (newValue == NULL) {
2618 WCHAR *space;
2619 WCHAR subkey[MAXSTRING];
2621 /* Query terminates the parameter at the first space */
2622 strcpyW(keyValue, command);
2623 space = strchrW(keyValue, ' ');
2624 if (space) *space=0x00;
2626 /* Set up key name */
2627 strcpyW(subkey, keyValue);
2628 if (!assoc) strcatW(subkey, shOpCmdW);
2630 if (RegOpenKeyEx(key, subkey, 0,
2631 accessOptions, &readKey) == ERROR_SUCCESS) {
2633 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2634 (LPBYTE)keyValue, &valueLen);
2635 WCMD_output_asis(command);
2636 WCMD_output_asis(equalW);
2637 /* If no default value found, leave line empty after '=' */
2638 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2639 WCMD_output_asis(newline);
2640 RegCloseKey(readKey);
2642 } else {
2643 WCHAR msgbuffer[MAXSTRING];
2644 WCHAR outbuffer[MAXSTRING];
2646 /* Load the translated 'File association not found' */
2647 if (assoc) {
2648 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2649 } else {
2650 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2652 wsprintf(outbuffer, msgbuffer, keyValue);
2653 WCMD_output_asis(outbuffer);
2654 errorlevel = 2;
2657 /* Not a query - its a set or clear of a value */
2658 } else {
2660 WCHAR subkey[MAXSTRING];
2662 /* Get pointer to new value */
2663 *newValue = 0x00;
2664 newValue++;
2666 /* Set up key name */
2667 strcpyW(subkey, command);
2668 if (!assoc) strcatW(subkey, shOpCmdW);
2670 /* If nothing after '=' then clear value - only valid for ASSOC */
2671 if (*newValue == 0x00) {
2673 if (assoc) rc = RegDeleteKey(key, command);
2674 if (assoc && rc == ERROR_SUCCESS) {
2675 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2677 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2678 WCMD_print_error();
2679 errorlevel = 2;
2681 } else {
2682 WCHAR msgbuffer[MAXSTRING];
2683 WCHAR outbuffer[MAXSTRING];
2685 /* Load the translated 'File association not found' */
2686 if (assoc) {
2687 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2688 sizeof(msgbuffer)/sizeof(WCHAR));
2689 } else {
2690 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2691 sizeof(msgbuffer)/sizeof(WCHAR));
2693 wsprintf(outbuffer, msgbuffer, keyValue);
2694 WCMD_output_asis(outbuffer);
2695 errorlevel = 2;
2698 /* It really is a set value = contents */
2699 } else {
2700 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2701 accessOptions, NULL, &readKey, NULL);
2702 if (rc == ERROR_SUCCESS) {
2703 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2704 (LPBYTE)newValue, strlenW(newValue));
2705 RegCloseKey(readKey);
2708 if (rc != ERROR_SUCCESS) {
2709 WCMD_print_error();
2710 errorlevel = 2;
2711 } else {
2712 WCMD_output_asis(command);
2713 WCMD_output_asis(equalW);
2714 WCMD_output_asis(newValue);
2715 WCMD_output_asis(newline);
2721 /* Clean up */
2722 RegCloseKey(key);
2725 /****************************************************************************
2726 * WCMD_color
2728 * Clear the terminal screen.
2731 void WCMD_color (void) {
2733 /* Emulate by filling the screen from the top left to bottom right with
2734 spaces, then moving the cursor to the top left afterwards */
2735 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2736 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2738 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2739 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2740 return;
2743 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2745 COORD topLeft;
2746 DWORD screenSize;
2747 DWORD color = 0;
2749 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2751 topLeft.X = 0;
2752 topLeft.Y = 0;
2754 /* Convert the color hex digits */
2755 if (param1[0] == 0x00) {
2756 color = defaultColor;
2757 } else {
2758 color = strtoulW(param1, NULL, 16);
2761 /* Fail if fg == bg color */
2762 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2763 errorlevel = 1;
2764 return;
2767 /* Set the current screen contents and ensure all future writes
2768 remain this color */
2769 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2770 SetConsoleTextAttribute(hStdOut, color);