dibdrv: Pass color by value, not pointer
[wine/dibdrv.git] / programs / cmd / builtins.c
blob43f63f64da8fd6bac5db297021cc0aa8003ecbcc
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 recursivly. 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 attribues 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) - &(%d) bd(%d / %d)\n",
1002 *cmdList,
1003 (*cmdList)->isAmphersand,
1004 (*cmdList)->bracketDepth, myDepth);
1006 /* Execute any appended to the statement with &&'s */
1007 if ((*cmdList)->isAmphersand) {
1008 if (processThese) {
1009 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1010 value, cmdList);
1012 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1014 /* Execute any appended to the statement with (...) */
1015 } else if ((*cmdList)->bracketDepth > myDepth) {
1016 if (processThese) {
1017 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1018 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1020 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1022 /* End of the command - does 'ELSE ' follow as the next command? */
1023 } else {
1024 if (isIF && CompareString (LOCALE_USER_DEFAULT,
1025 NORM_IGNORECASE | SORT_STRINGSORT,
1026 (*cmdList)->command, 5, ifElse, -1) == 2) {
1028 /* Swap between if and else processing */
1029 processThese = !processThese;
1031 /* Process the ELSE part */
1032 if (processThese) {
1033 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1035 /* Skip leading whitespace between condition and the command */
1036 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1037 if (*cmd) {
1038 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1041 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1042 } else {
1043 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1044 break;
1049 return;
1052 /**************************************************************************
1053 * WCMD_give_help
1055 * Simple on-line help. Help text is stored in the resource file.
1058 void WCMD_give_help (WCHAR *command) {
1060 int i;
1062 command = WCMD_strtrim_leading_spaces(command);
1063 if (strlenW(command) == 0) {
1064 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1066 else {
1067 for (i=0; i<=WCMD_EXIT; i++) {
1068 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1069 param1, -1, inbuilt[i], -1) == 2) {
1070 WCMD_output_asis (WCMD_LoadMessage(i));
1071 return;
1074 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
1076 return;
1079 /****************************************************************************
1080 * WCMD_go_to
1082 * Batch file jump instruction. Not the most efficient algorithm ;-)
1083 * Prints error message if the specified label cannot be found - the file pointer is
1084 * then at EOF, effectively stopping the batch file.
1085 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1088 void WCMD_goto (CMD_LIST **cmdList) {
1090 WCHAR string[MAX_PATH];
1092 /* Do not process any more parts of a processed multipart or multilines command */
1093 if (cmdList) *cmdList = NULL;
1095 if (param1[0] == 0x00) {
1096 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1097 return;
1099 if (context != NULL) {
1100 WCHAR *paramStart = param1;
1101 static const WCHAR eofW[] = {':','e','o','f','\0'};
1103 /* Handle special :EOF label */
1104 if (lstrcmpiW (eofW, param1) == 0) {
1105 context -> skip_rest = TRUE;
1106 return;
1109 /* Support goto :label as well as goto label */
1110 if (*paramStart == ':') paramStart++;
1112 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1113 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1114 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1116 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1118 return;
1121 /*****************************************************************************
1122 * WCMD_pushd
1124 * Push a directory onto the stack
1127 void WCMD_pushd (WCHAR *command) {
1128 struct env_stack *curdir;
1129 WCHAR *thisdir;
1130 static const WCHAR parmD[] = {'/','D','\0'};
1132 if (strchrW(command, '/') != NULL) {
1133 SetLastError(ERROR_INVALID_PARAMETER);
1134 WCMD_print_error();
1135 return;
1138 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1139 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1140 if( !curdir || !thisdir ) {
1141 LocalFree(curdir);
1142 LocalFree(thisdir);
1143 WINE_ERR ("out of memory\n");
1144 return;
1147 /* Change directory using CD code with /D parameter */
1148 strcpyW(quals, parmD);
1149 GetCurrentDirectoryW (1024, thisdir);
1150 errorlevel = 0;
1151 WCMD_setshow_default(command);
1152 if (errorlevel) {
1153 LocalFree(curdir);
1154 LocalFree(thisdir);
1155 return;
1156 } else {
1157 curdir -> next = pushd_directories;
1158 curdir -> strings = thisdir;
1159 if (pushd_directories == NULL) {
1160 curdir -> u.stackdepth = 1;
1161 } else {
1162 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1164 pushd_directories = curdir;
1169 /*****************************************************************************
1170 * WCMD_popd
1172 * Pop a directory from the stack
1175 void WCMD_popd (void) {
1176 struct env_stack *temp = pushd_directories;
1178 if (!pushd_directories)
1179 return;
1181 /* pop the old environment from the stack, and make it the current dir */
1182 pushd_directories = temp->next;
1183 SetCurrentDirectoryW(temp->strings);
1184 LocalFree (temp->strings);
1185 LocalFree (temp);
1188 /****************************************************************************
1189 * WCMD_if
1191 * Batch file conditional.
1193 * On entry, cmdlist will point to command containing the IF, and optionally
1194 * the first command to execute (if brackets not found)
1195 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1196 * If ('s were found, execute all within that bracket
1197 * Command may optionally be followed by an ELSE - need to skip instructions
1198 * in the else using the same logic
1200 * FIXME: Much more syntax checking needed!
1203 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1205 int negate = 0, test = 0;
1206 WCHAR condition[MAX_PATH], *command, *s;
1207 static const WCHAR notW[] = {'n','o','t','\0'};
1208 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1209 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1210 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1211 static const WCHAR eqeqW[] = {'=','=','\0'};
1213 if (!lstrcmpiW (param1, notW)) {
1214 negate = 1;
1215 strcpyW (condition, param2);
1217 else {
1218 strcpyW (condition, param1);
1220 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1222 if (!lstrcmpiW (condition, errlvlW)) {
1223 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1224 WCMD_parameter (p, 2+negate, &command);
1226 else if (!lstrcmpiW (condition, existW)) {
1227 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1228 test = 1;
1230 WCMD_parameter (p, 2+negate, &command);
1232 else if (!lstrcmpiW (condition, defdW)) {
1233 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1234 test = 1;
1236 WCMD_parameter (p, 2+negate, &command);
1238 else if ((s = strstrW (p, eqeqW))) {
1239 s += 2;
1240 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1241 WCMD_parameter (s, 1, &command);
1243 else {
1244 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1245 return;
1248 /* Process rest of IF statement which is on the same line
1249 Note: This may process all or some of the cmdList (eg a GOTO) */
1250 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1253 /****************************************************************************
1254 * WCMD_move
1256 * Move a file, directory tree or wildcarded set of files.
1259 void WCMD_move (void) {
1261 int status;
1262 WIN32_FIND_DATA fd;
1263 HANDLE hff;
1264 WCHAR input[MAX_PATH];
1265 WCHAR output[MAX_PATH];
1266 WCHAR drive[10];
1267 WCHAR dir[MAX_PATH];
1268 WCHAR fname[MAX_PATH];
1269 WCHAR ext[MAX_PATH];
1271 if (param1[0] == 0x00) {
1272 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1273 return;
1276 /* If no destination supplied, assume current directory */
1277 if (param2[0] == 0x00) {
1278 strcpyW(param2, dotW);
1281 /* If 2nd parm is directory, then use original filename */
1282 /* Convert partial path to full path */
1283 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1284 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1285 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1286 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1288 /* Split into components */
1289 WCMD_splitpath(input, drive, dir, fname, ext);
1291 hff = FindFirstFile (input, &fd);
1292 while (hff != INVALID_HANDLE_VALUE) {
1293 WCHAR dest[MAX_PATH];
1294 WCHAR src[MAX_PATH];
1295 DWORD attribs;
1297 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1299 /* Build src & dest name */
1300 strcpyW(src, drive);
1301 strcatW(src, dir);
1303 /* See if dest is an existing directory */
1304 attribs = GetFileAttributes(output);
1305 if (attribs != INVALID_FILE_ATTRIBUTES &&
1306 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1307 strcpyW(dest, output);
1308 strcatW(dest, slashW);
1309 strcatW(dest, fd.cFileName);
1310 } else {
1311 strcpyW(dest, output);
1314 strcatW(src, fd.cFileName);
1316 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1317 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1319 /* Check if file is read only, otherwise move it */
1320 attribs = GetFileAttributes(src);
1321 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1322 (attribs & FILE_ATTRIBUTE_READONLY)) {
1323 SetLastError(ERROR_ACCESS_DENIED);
1324 status = 0;
1325 } else {
1326 BOOL ok = TRUE;
1328 /* If destination exists, prompt unless /Y supplied */
1329 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1330 BOOL force = FALSE;
1331 WCHAR copycmd[MAXSTRING];
1332 int len;
1334 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1335 if (strstrW (quals, parmNoY))
1336 force = FALSE;
1337 else if (strstrW (quals, parmY))
1338 force = TRUE;
1339 else {
1340 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1341 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1342 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1343 && ! lstrcmpiW (copycmd, parmY));
1346 /* Prompt if overwriting */
1347 if (!force) {
1348 WCHAR question[MAXSTRING];
1349 WCHAR yesChar[10];
1351 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1353 /* Ask for confirmation */
1354 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1355 ok = WCMD_ask_confirm(question, FALSE, NULL);
1357 /* So delete the destination prior to the move */
1358 if (ok) {
1359 if (!DeleteFile (dest)) {
1360 WCMD_print_error ();
1361 errorlevel = 1;
1362 ok = FALSE;
1368 if (ok) {
1369 status = MoveFile (src, dest);
1370 } else {
1371 status = 1; /* Anything other than 0 to prevent error msg below */
1375 if (!status) {
1376 WCMD_print_error ();
1377 errorlevel = 1;
1380 /* Step on to next match */
1381 if (FindNextFile(hff, &fd) == 0) {
1382 FindClose(hff);
1383 hff = INVALID_HANDLE_VALUE;
1384 break;
1389 /****************************************************************************
1390 * WCMD_pause
1392 * Wait for keyboard input.
1395 void WCMD_pause (void) {
1397 DWORD count;
1398 WCHAR string[32];
1400 WCMD_output (anykey);
1401 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1402 sizeof(string)/sizeof(WCHAR), &count, NULL);
1405 /****************************************************************************
1406 * WCMD_remove_dir
1408 * Delete a directory.
1411 void WCMD_remove_dir (WCHAR *command) {
1413 int argno = 0;
1414 int argsProcessed = 0;
1415 WCHAR *argN = command;
1416 static const WCHAR parmS[] = {'/','S','\0'};
1417 static const WCHAR parmQ[] = {'/','Q','\0'};
1419 /* Loop through all args */
1420 while (argN) {
1421 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1422 if (argN && argN[0] != '/') {
1423 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1424 wine_dbgstr_w(quals));
1425 argsProcessed++;
1427 /* If subdirectory search not supplied, just try to remove
1428 and report error if it fails (eg if it contains a file) */
1429 if (strstrW (quals, parmS) == NULL) {
1430 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1432 /* Otherwise use ShFileOp to recursively remove a directory */
1433 } else {
1435 SHFILEOPSTRUCT lpDir;
1437 /* Ask first */
1438 if (strstrW (quals, parmQ) == NULL) {
1439 BOOL ok;
1440 WCHAR question[MAXSTRING];
1441 static const WCHAR fmt[] = {'%','s',' ','\0'};
1443 /* Ask for confirmation */
1444 wsprintf(question, fmt, thisArg);
1445 ok = WCMD_ask_confirm(question, TRUE, NULL);
1447 /* Abort if answer is 'N' */
1448 if (!ok) return;
1451 /* Do the delete */
1452 lpDir.hwnd = NULL;
1453 lpDir.pTo = NULL;
1454 lpDir.pFrom = thisArg;
1455 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1456 lpDir.wFunc = FO_DELETE;
1457 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1462 /* Handle no valid args */
1463 if (argsProcessed == 0) {
1464 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1465 return;
1470 /****************************************************************************
1471 * WCMD_rename
1473 * Rename a file.
1476 void WCMD_rename (void) {
1478 int status;
1479 HANDLE hff;
1480 WIN32_FIND_DATA fd;
1481 WCHAR input[MAX_PATH];
1482 WCHAR *dotDst = NULL;
1483 WCHAR drive[10];
1484 WCHAR dir[MAX_PATH];
1485 WCHAR fname[MAX_PATH];
1486 WCHAR ext[MAX_PATH];
1487 DWORD attribs;
1489 errorlevel = 0;
1491 /* Must be at least two args */
1492 if (param1[0] == 0x00 || param2[0] == 0x00) {
1493 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1494 errorlevel = 1;
1495 return;
1498 /* Destination cannot contain a drive letter or directory separator */
1499 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1500 SetLastError(ERROR_INVALID_PARAMETER);
1501 WCMD_print_error();
1502 errorlevel = 1;
1503 return;
1506 /* Convert partial path to full path */
1507 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1508 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1509 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1510 dotDst = strchrW(param2, '.');
1512 /* Split into components */
1513 WCMD_splitpath(input, drive, dir, fname, ext);
1515 hff = FindFirstFile (input, &fd);
1516 while (hff != INVALID_HANDLE_VALUE) {
1517 WCHAR dest[MAX_PATH];
1518 WCHAR src[MAX_PATH];
1519 WCHAR *dotSrc = NULL;
1520 int dirLen;
1522 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1524 /* FIXME: If dest name or extension is *, replace with filename/ext
1525 part otherwise use supplied name. This supports:
1526 ren *.fred *.jim
1527 ren jim.* fred.* etc
1528 However, windows has a more complex algorithum supporting eg
1529 ?'s and *'s mid name */
1530 dotSrc = strchrW(fd.cFileName, '.');
1532 /* Build src & dest name */
1533 strcpyW(src, drive);
1534 strcatW(src, dir);
1535 strcpyW(dest, src);
1536 dirLen = strlenW(src);
1537 strcatW(src, fd.cFileName);
1539 /* Build name */
1540 if (param2[0] == '*') {
1541 strcatW(dest, fd.cFileName);
1542 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1543 } else {
1544 strcatW(dest, param2);
1545 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1548 /* Build Extension */
1549 if (dotDst && (*(dotDst+1)=='*')) {
1550 if (dotSrc) strcatW(dest, dotSrc);
1551 } else if (dotDst) {
1552 if (dotDst) strcatW(dest, dotDst);
1555 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1556 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1558 /* Check if file is read only, otherwise move it */
1559 attribs = GetFileAttributes(src);
1560 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1561 (attribs & FILE_ATTRIBUTE_READONLY)) {
1562 SetLastError(ERROR_ACCESS_DENIED);
1563 status = 0;
1564 } else {
1565 status = MoveFile (src, dest);
1568 if (!status) {
1569 WCMD_print_error ();
1570 errorlevel = 1;
1573 /* Step on to next match */
1574 if (FindNextFile(hff, &fd) == 0) {
1575 FindClose(hff);
1576 hff = INVALID_HANDLE_VALUE;
1577 break;
1582 /*****************************************************************************
1583 * WCMD_dupenv
1585 * Make a copy of the environment.
1587 static WCHAR *WCMD_dupenv( const WCHAR *env )
1589 WCHAR *env_copy;
1590 int len;
1592 if( !env )
1593 return NULL;
1595 len = 0;
1596 while ( env[len] )
1597 len += (strlenW(&env[len]) + 1);
1599 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1600 if (!env_copy)
1602 WINE_ERR("out of memory\n");
1603 return env_copy;
1605 memcpy (env_copy, env, len*sizeof (WCHAR));
1606 env_copy[len] = 0;
1608 return env_copy;
1611 /*****************************************************************************
1612 * WCMD_setlocal
1614 * setlocal pushes the environment onto a stack
1615 * Save the environment as unicode so we don't screw anything up.
1617 void WCMD_setlocal (const WCHAR *s) {
1618 WCHAR *env;
1619 struct env_stack *env_copy;
1620 WCHAR cwd[MAX_PATH];
1622 /* DISABLEEXTENSIONS ignored */
1624 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1625 if( !env_copy )
1627 WINE_ERR ("out of memory\n");
1628 return;
1631 env = GetEnvironmentStringsW ();
1633 env_copy->strings = WCMD_dupenv (env);
1634 if (env_copy->strings)
1636 env_copy->next = saved_environment;
1637 saved_environment = env_copy;
1639 /* Save the current drive letter */
1640 GetCurrentDirectory (MAX_PATH, cwd);
1641 env_copy->u.cwd = cwd[0];
1643 else
1644 LocalFree (env_copy);
1646 FreeEnvironmentStringsW (env);
1650 /*****************************************************************************
1651 * WCMD_endlocal
1653 * endlocal pops the environment off a stack
1654 * Note: When searching for '=', search from WCHAR position 1, to handle
1655 * special internal environment variables =C:, =D: etc
1657 void WCMD_endlocal (void) {
1658 WCHAR *env, *old, *p;
1659 struct env_stack *temp;
1660 int len, n;
1662 if (!saved_environment)
1663 return;
1665 /* pop the old environment from the stack */
1666 temp = saved_environment;
1667 saved_environment = temp->next;
1669 /* delete the current environment, totally */
1670 env = GetEnvironmentStringsW ();
1671 old = WCMD_dupenv (GetEnvironmentStringsW ());
1672 len = 0;
1673 while (old[len]) {
1674 n = strlenW(&old[len]) + 1;
1675 p = strchrW(&old[len] + 1, '=');
1676 if (p)
1678 *p++ = 0;
1679 SetEnvironmentVariableW (&old[len], NULL);
1681 len += n;
1683 LocalFree (old);
1684 FreeEnvironmentStringsW (env);
1686 /* restore old environment */
1687 env = temp->strings;
1688 len = 0;
1689 while (env[len]) {
1690 n = strlenW(&env[len]) + 1;
1691 p = strchrW(&env[len] + 1, '=');
1692 if (p)
1694 *p++ = 0;
1695 SetEnvironmentVariableW (&env[len], p);
1697 len += n;
1700 /* Restore current drive letter */
1701 if (IsCharAlpha(temp->u.cwd)) {
1702 WCHAR envvar[4];
1703 WCHAR cwd[MAX_PATH];
1704 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1706 wsprintf(envvar, fmt, temp->u.cwd);
1707 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1708 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1709 SetCurrentDirectory(cwd);
1713 LocalFree (env);
1714 LocalFree (temp);
1717 /*****************************************************************************
1718 * WCMD_setshow_attrib
1720 * Display and optionally sets DOS attributes on a file or directory
1722 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1723 * As a result only the Readonly flag is correctly reported, the Archive bit
1724 * is always set and the rest are not implemented. We do the Right Thing anyway.
1726 * FIXME: No SET functionality.
1730 void WCMD_setshow_attrib (void) {
1732 DWORD count;
1733 HANDLE hff;
1734 WIN32_FIND_DATA fd;
1735 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1737 if (param1[0] == '-') {
1738 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1739 return;
1742 if (strlenW(param1) == 0) {
1743 static const WCHAR slashStarW[] = {'\\','*','\0'};
1745 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1746 strcatW (param1, slashStarW);
1749 hff = FindFirstFile (param1, &fd);
1750 if (hff == INVALID_HANDLE_VALUE) {
1751 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1753 else {
1754 do {
1755 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1756 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1757 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1758 flags[0] = 'H';
1760 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1761 flags[1] = 'S';
1763 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1764 flags[2] = 'A';
1766 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1767 flags[3] = 'R';
1769 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1770 flags[4] = 'T';
1772 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1773 flags[5] = 'C';
1775 WCMD_output (fmt, flags, fd.cFileName);
1776 for (count=0; count < 8; count++) flags[count] = ' ';
1778 } while (FindNextFile(hff, &fd) != 0);
1780 FindClose (hff);
1783 /*****************************************************************************
1784 * WCMD_setshow_default
1786 * Set/Show the current default directory
1789 void WCMD_setshow_default (WCHAR *command) {
1791 BOOL status;
1792 WCHAR string[1024];
1793 WCHAR cwd[1024];
1794 WCHAR *pos;
1795 WIN32_FIND_DATA fd;
1796 HANDLE hff;
1797 static const WCHAR parmD[] = {'/','D','\0'};
1799 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1801 /* Skip /D and trailing whitespace if on the front of the command line */
1802 if (CompareString (LOCALE_USER_DEFAULT,
1803 NORM_IGNORECASE | SORT_STRINGSORT,
1804 command, 2, parmD, -1) == 2) {
1805 command += 2;
1806 while (*command && *command==' ') command++;
1809 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1810 if (strlenW(command) == 0) {
1811 strcatW (cwd, newline);
1812 WCMD_output (cwd);
1814 else {
1815 /* Remove any double quotes, which may be in the
1816 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1817 pos = string;
1818 while (*command) {
1819 if (*command != '"') *pos++ = *command;
1820 command++;
1822 *pos = 0x00;
1824 /* Search for approprate directory */
1825 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1826 hff = FindFirstFile (string, &fd);
1827 while (hff != INVALID_HANDLE_VALUE) {
1828 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1829 WCHAR fpath[MAX_PATH];
1830 WCHAR drive[10];
1831 WCHAR dir[MAX_PATH];
1832 WCHAR fname[MAX_PATH];
1833 WCHAR ext[MAX_PATH];
1834 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1836 /* Convert path into actual directory spec */
1837 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1838 WCMD_splitpath(fpath, drive, dir, fname, ext);
1840 /* Rebuild path */
1841 wsprintf(string, fmt, drive, dir, fd.cFileName);
1843 FindClose(hff);
1844 hff = INVALID_HANDLE_VALUE;
1845 break;
1848 /* Step on to next match */
1849 if (FindNextFile(hff, &fd) == 0) {
1850 FindClose(hff);
1851 hff = INVALID_HANDLE_VALUE;
1852 break;
1856 /* Change to that directory */
1857 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1859 status = SetCurrentDirectory (string);
1860 if (!status) {
1861 errorlevel = 1;
1862 WCMD_print_error ();
1863 return;
1864 } else {
1866 /* Restore old directory if drive letter would change, and
1867 CD x:\directory /D (or pushd c:\directory) not supplied */
1868 if ((strstrW(quals, parmD) == NULL) &&
1869 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1870 SetCurrentDirectory(cwd);
1874 /* Set special =C: type environment variable, for drive letter of
1875 change of directory, even if path was restored due to missing
1876 /D (allows changing drive letter when not resident on that
1877 drive */
1878 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1879 WCHAR env[4];
1880 strcpyW(env, equalW);
1881 memcpy(env+1, string, 2 * sizeof(WCHAR));
1882 env[3] = 0x00;
1883 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1884 SetEnvironmentVariable(env, string);
1888 return;
1891 /****************************************************************************
1892 * WCMD_setshow_date
1894 * Set/Show the system date
1895 * FIXME: Can't change date yet
1898 void WCMD_setshow_date (void) {
1900 WCHAR curdate[64], buffer[64];
1901 DWORD count;
1902 static const WCHAR parmT[] = {'/','T','\0'};
1904 if (strlenW(param1) == 0) {
1905 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1906 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1907 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1908 if (strstrW (quals, parmT) == NULL) {
1909 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1910 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1911 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1912 if (count > 2) {
1913 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1917 else WCMD_print_error ();
1919 else {
1920 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1924 /****************************************************************************
1925 * WCMD_compare
1927 static int WCMD_compare( const void *a, const void *b )
1929 int r;
1930 const WCHAR * const *str_a = a, * const *str_b = b;
1931 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1932 *str_a, -1, *str_b, -1 );
1933 if( r == CSTR_LESS_THAN ) return -1;
1934 if( r == CSTR_GREATER_THAN ) return 1;
1935 return 0;
1938 /****************************************************************************
1939 * WCMD_setshow_sortenv
1941 * sort variables into order for display
1942 * Optionally only display those who start with a stub
1943 * returns the count displayed
1945 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1947 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1948 const WCHAR **str;
1950 if (stub) stublen = strlenW(stub);
1952 /* count the number of strings, and the total length */
1953 while ( s[len] ) {
1954 len += (strlenW(&s[len]) + 1);
1955 count++;
1958 /* add the strings to an array */
1959 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1960 if( !str )
1961 return 0;
1962 str[0] = s;
1963 for( i=1; i<count; i++ )
1964 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1966 /* sort the array */
1967 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1969 /* print it */
1970 for( i=0; i<count; i++ ) {
1971 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1972 NORM_IGNORECASE | SORT_STRINGSORT,
1973 str[i], stublen, stub, -1) == 2) {
1974 /* Don't display special internal variables */
1975 if (str[i][0] != '=') {
1976 WCMD_output_asis(str[i]);
1977 WCMD_output_asis(newline);
1978 displayedcount++;
1983 LocalFree( str );
1984 return displayedcount;
1987 /****************************************************************************
1988 * WCMD_setshow_env
1990 * Set/Show the environment variables
1993 void WCMD_setshow_env (WCHAR *s) {
1995 LPVOID env;
1996 WCHAR *p;
1997 int status;
1998 static const WCHAR parmP[] = {'/','P','\0'};
2000 errorlevel = 0;
2001 if (param1[0] == 0x00 && quals[0] == 0x00) {
2002 env = GetEnvironmentStrings ();
2003 WCMD_setshow_sortenv( env, NULL );
2004 return;
2007 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2008 if (CompareString (LOCALE_USER_DEFAULT,
2009 NORM_IGNORECASE | SORT_STRINGSORT,
2010 s, 2, parmP, -1) == 2) {
2011 WCHAR string[MAXSTRING];
2012 DWORD count;
2014 s += 2;
2015 while (*s && *s==' ') s++;
2017 /* If no parameter, or no '=' sign, return an error */
2018 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2019 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2020 return;
2023 /* Output the prompt */
2024 *p++ = '\0';
2025 if (strlenW(p) != 0) WCMD_output(p);
2027 /* Read the reply */
2028 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2029 sizeof(string)/sizeof(WCHAR), &count, NULL);
2030 if (count > 1) {
2031 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2032 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2033 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2034 wine_dbgstr_w(string));
2035 status = SetEnvironmentVariable (s, string);
2038 } else {
2039 DWORD gle;
2040 p = strchrW (s, '=');
2041 if (p == NULL) {
2042 env = GetEnvironmentStrings ();
2043 if (WCMD_setshow_sortenv( env, s ) == 0) {
2044 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2045 errorlevel = 1;
2047 return;
2049 *p++ = '\0';
2051 if (strlenW(p) == 0) p = NULL;
2052 status = SetEnvironmentVariable (s, p);
2053 gle = GetLastError();
2054 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2055 errorlevel = 1;
2056 } else if ((!status)) WCMD_print_error();
2060 /****************************************************************************
2061 * WCMD_setshow_path
2063 * Set/Show the path environment variable
2066 void WCMD_setshow_path (WCHAR *command) {
2068 WCHAR string[1024];
2069 DWORD status;
2070 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2071 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2073 if (strlenW(param1) == 0) {
2074 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2075 if (status != 0) {
2076 WCMD_output_asis ( pathEqW);
2077 WCMD_output_asis ( string);
2078 WCMD_output_asis ( newline);
2080 else {
2081 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2084 else {
2085 if (*command == '=') command++; /* Skip leading '=' */
2086 status = SetEnvironmentVariable (pathW, command);
2087 if (!status) WCMD_print_error();
2091 /****************************************************************************
2092 * WCMD_setshow_prompt
2094 * Set or show the command prompt.
2097 void WCMD_setshow_prompt (void) {
2099 WCHAR *s;
2100 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2102 if (strlenW(param1) == 0) {
2103 SetEnvironmentVariable (promptW, NULL);
2105 else {
2106 s = param1;
2107 while ((*s == '=') || (*s == ' ')) s++;
2108 if (strlenW(s) == 0) {
2109 SetEnvironmentVariable (promptW, NULL);
2111 else SetEnvironmentVariable (promptW, s);
2115 /****************************************************************************
2116 * WCMD_setshow_time
2118 * Set/Show the system time
2119 * FIXME: Can't change time yet
2122 void WCMD_setshow_time (void) {
2124 WCHAR curtime[64], buffer[64];
2125 DWORD count;
2126 SYSTEMTIME st;
2127 static const WCHAR parmT[] = {'/','T','\0'};
2129 if (strlenW(param1) == 0) {
2130 GetLocalTime(&st);
2131 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2132 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2133 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
2134 if (strstrW (quals, parmT) == NULL) {
2135 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2136 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2137 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2138 if (count > 2) {
2139 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2143 else WCMD_print_error ();
2145 else {
2146 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2150 /****************************************************************************
2151 * WCMD_shift
2153 * Shift batch parameters.
2154 * Optional /n says where to start shifting (n=0-8)
2157 void WCMD_shift (WCHAR *command) {
2158 int start;
2160 if (context != NULL) {
2161 WCHAR *pos = strchrW(command, '/');
2162 int i;
2164 if (pos == NULL) {
2165 start = 0;
2166 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2167 start = (*(pos+1) - '0');
2168 } else {
2169 SetLastError(ERROR_INVALID_PARAMETER);
2170 WCMD_print_error();
2171 return;
2174 WINE_TRACE("Shifting variables, starting at %d\n", start);
2175 for (i=start;i<=8;i++) {
2176 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2178 context -> shift_count[9] = context -> shift_count[9] + 1;
2183 /****************************************************************************
2184 * WCMD_title
2186 * Set the console title
2188 void WCMD_title (WCHAR *command) {
2189 SetConsoleTitle(command);
2192 /****************************************************************************
2193 * WCMD_type
2195 * Copy a file to standard output.
2198 void WCMD_type (WCHAR *command) {
2200 int argno = 0;
2201 WCHAR *argN = command;
2202 BOOL writeHeaders = FALSE;
2204 if (param1[0] == 0x00) {
2205 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2206 return;
2209 if (param2[0] != 0x00) writeHeaders = TRUE;
2211 /* Loop through all args */
2212 errorlevel = 0;
2213 while (argN) {
2214 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2216 HANDLE h;
2217 WCHAR buffer[512];
2218 DWORD count;
2220 if (!argN) break;
2222 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2223 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2224 FILE_ATTRIBUTE_NORMAL, NULL);
2225 if (h == INVALID_HANDLE_VALUE) {
2226 WCMD_print_error ();
2227 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2228 errorlevel = 1;
2229 } else {
2230 if (writeHeaders) {
2231 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2232 WCMD_output(fmt, thisArg);
2234 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2235 if (count == 0) break; /* ReadFile reports success on EOF! */
2236 buffer[count] = 0;
2237 WCMD_output_asis (buffer);
2239 CloseHandle (h);
2244 /****************************************************************************
2245 * WCMD_more
2247 * Output either a file or stdin to screen in pages
2250 void WCMD_more (WCHAR *command) {
2252 int argno = 0;
2253 WCHAR *argN = command;
2254 BOOL useinput = FALSE;
2255 WCHAR moreStr[100];
2256 WCHAR moreStrPage[100];
2257 WCHAR buffer[512];
2258 DWORD count;
2259 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2260 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2261 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2262 ')',' ','-','-','\n','\0'};
2263 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2265 /* Prefix the NLS more with '-- ', then load the text */
2266 errorlevel = 0;
2267 strcpyW(moreStr, moreStart);
2268 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2269 (sizeof(moreStr)/sizeof(WCHAR))-3);
2271 if (param1[0] == 0x00) {
2273 /* Wine implements pipes via temporary files, and hence stdin is
2274 effectively reading from the file. This means the prompts for
2275 more are satistied by the next line from the input (file). To
2276 avoid this, ensure stdin is to the console */
2277 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2278 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2279 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2280 FILE_ATTRIBUTE_NORMAL, 0);
2281 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2283 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2284 once you get in this bit unless due to a pipe, its going to end badly... */
2285 useinput = TRUE;
2286 wsprintf(moreStrPage, moreFmt, moreStr);
2288 WCMD_enter_paged_mode(moreStrPage);
2289 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2290 if (count == 0) break; /* ReadFile reports success on EOF! */
2291 buffer[count] = 0;
2292 WCMD_output_asis (buffer);
2294 WCMD_leave_paged_mode();
2296 /* Restore stdin to what it was */
2297 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2298 CloseHandle(hConIn);
2300 return;
2301 } else {
2302 BOOL needsPause = FALSE;
2304 /* Loop through all args */
2305 WCMD_enter_paged_mode(moreStrPage);
2307 while (argN) {
2308 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2309 HANDLE h;
2311 if (!argN) break;
2313 if (needsPause) {
2315 /* Wait */
2316 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2317 WCMD_leave_paged_mode();
2318 WCMD_output_asis(moreStrPage);
2319 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2320 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2321 WCMD_enter_paged_mode(moreStrPage);
2325 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2326 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2327 FILE_ATTRIBUTE_NORMAL, NULL);
2328 if (h == INVALID_HANDLE_VALUE) {
2329 WCMD_print_error ();
2330 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2331 errorlevel = 1;
2332 } else {
2333 ULONG64 curPos = 0;
2334 ULONG64 fileLen = 0;
2335 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2337 /* Get the file size */
2338 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2339 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2341 needsPause = TRUE;
2342 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2343 if (count == 0) break; /* ReadFile reports success on EOF! */
2344 buffer[count] = 0;
2345 curPos += count;
2347 /* Update % count (would be used in WCMD_output_asis as prompt) */
2348 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2350 WCMD_output_asis (buffer);
2352 CloseHandle (h);
2356 WCMD_leave_paged_mode();
2360 /****************************************************************************
2361 * WCMD_verify
2363 * Display verify flag.
2364 * FIXME: We don't actually do anything with the verify flag other than toggle
2365 * it...
2368 void WCMD_verify (WCHAR *command) {
2370 int count;
2372 count = strlenW(command);
2373 if (count == 0) {
2374 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2375 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2376 return;
2378 if (lstrcmpiW(command, onW) == 0) {
2379 verify_mode = 1;
2380 return;
2382 else if (lstrcmpiW(command, offW) == 0) {
2383 verify_mode = 0;
2384 return;
2386 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2389 /****************************************************************************
2390 * WCMD_version
2392 * Display version info.
2395 void WCMD_version (void) {
2397 WCMD_output (version_string);
2401 /****************************************************************************
2402 * WCMD_volume
2404 * Display volume info and/or set volume label. Returns 0 if error.
2407 int WCMD_volume (int mode, WCHAR *path) {
2409 DWORD count, serial;
2410 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2411 BOOL status;
2413 if (strlenW(path) == 0) {
2414 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2415 if (!status) {
2416 WCMD_print_error ();
2417 return 0;
2419 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2420 &serial, NULL, NULL, NULL, 0);
2422 else {
2423 static const WCHAR fmt[] = {'%','s','\\','\0'};
2424 if ((path[1] != ':') || (strlenW(path) != 2)) {
2425 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2426 return 0;
2428 wsprintf (curdir, fmt, path);
2429 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2430 &serial, NULL,
2431 NULL, NULL, 0);
2433 if (!status) {
2434 WCMD_print_error ();
2435 return 0;
2437 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2438 curdir[0], label, HIWORD(serial), LOWORD(serial));
2439 if (mode) {
2440 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2441 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2442 sizeof(string)/sizeof(WCHAR), &count, NULL);
2443 if (count > 1) {
2444 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2445 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2447 if (strlenW(path) != 0) {
2448 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2450 else {
2451 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2454 return 1;
2457 /**************************************************************************
2458 * WCMD_exit
2460 * Exit either the process, or just this batch program
2464 void WCMD_exit (CMD_LIST **cmdList) {
2466 static const WCHAR parmB[] = {'/','B','\0'};
2467 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2469 if (context && lstrcmpiW(quals, parmB) == 0) {
2470 errorlevel = rc;
2471 context -> skip_rest = TRUE;
2472 *cmdList = NULL;
2473 } else {
2474 ExitProcess(rc);
2478 /**************************************************************************
2479 * WCMD_ask_confirm
2481 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2482 * answer.
2484 * Returns True if Y (or A) answer is selected
2485 * If optionAll contains a pointer, ALL is allowed, and if answered
2486 * set to TRUE
2489 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2491 WCHAR msgbuffer[MAXSTRING];
2492 WCHAR Ybuffer[MAXSTRING];
2493 WCHAR Nbuffer[MAXSTRING];
2494 WCHAR Abuffer[MAXSTRING];
2495 WCHAR answer[MAX_PATH] = {'\0'};
2496 DWORD count = 0;
2498 /* Load the translated 'Are you sure', plus valid answers */
2499 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2500 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2501 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2502 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2504 /* Loop waiting on a Y or N */
2505 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2506 static const WCHAR startBkt[] = {' ','(','\0'};
2507 static const WCHAR endBkt[] = {')','?','\0'};
2509 WCMD_output_asis (message);
2510 if (showSureText) {
2511 WCMD_output_asis (msgbuffer);
2513 WCMD_output_asis (startBkt);
2514 WCMD_output_asis (Ybuffer);
2515 WCMD_output_asis (fslashW);
2516 WCMD_output_asis (Nbuffer);
2517 if (optionAll) {
2518 WCMD_output_asis (fslashW);
2519 WCMD_output_asis (Abuffer);
2521 WCMD_output_asis (endBkt);
2522 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2523 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2524 answer[0] = toupperW(answer[0]);
2527 /* Return the answer */
2528 return ((answer[0] == Ybuffer[0]) ||
2529 (optionAll && (answer[0] == Abuffer[0])));
2532 /*****************************************************************************
2533 * WCMD_assoc
2535 * Lists or sets file associations (assoc = TRUE)
2536 * Lists or sets file types (assoc = FALSE)
2538 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2540 HKEY key;
2541 DWORD accessOptions = KEY_READ;
2542 WCHAR *newValue;
2543 LONG rc = ERROR_SUCCESS;
2544 WCHAR keyValue[MAXSTRING];
2545 DWORD valueLen = MAXSTRING;
2546 HKEY readKey;
2547 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2548 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2550 /* See if parameter includes '=' */
2551 errorlevel = 0;
2552 newValue = strchrW(command, '=');
2553 if (newValue) accessOptions |= KEY_WRITE;
2555 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2556 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2557 accessOptions, &key) != ERROR_SUCCESS) {
2558 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2559 return;
2562 /* If no parameters then list all associations */
2563 if (*command == 0x00) {
2564 int index = 0;
2566 /* Enumerate all the keys */
2567 while (rc != ERROR_NO_MORE_ITEMS) {
2568 WCHAR keyName[MAXSTRING];
2569 DWORD nameLen;
2571 /* Find the next value */
2572 nameLen = MAXSTRING;
2573 rc = RegEnumKeyEx(key, index++,
2574 keyName, &nameLen,
2575 NULL, NULL, NULL, NULL);
2577 if (rc == ERROR_SUCCESS) {
2579 /* Only interested in extension ones if assoc, or others
2580 if not assoc */
2581 if ((keyName[0] == '.' && assoc) ||
2582 (!(keyName[0] == '.') && (!assoc)))
2584 WCHAR subkey[MAXSTRING];
2585 strcpyW(subkey, keyName);
2586 if (!assoc) strcatW(subkey, shOpCmdW);
2588 if (RegOpenKeyEx(key, subkey, 0,
2589 accessOptions, &readKey) == ERROR_SUCCESS) {
2591 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2592 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2593 (LPBYTE)keyValue, &valueLen);
2594 WCMD_output_asis(keyName);
2595 WCMD_output_asis(equalW);
2596 /* If no default value found, leave line empty after '=' */
2597 if (rc == ERROR_SUCCESS) {
2598 WCMD_output_asis(keyValue);
2600 WCMD_output_asis(newline);
2605 RegCloseKey(readKey);
2607 } else {
2609 /* Parameter supplied - if no '=' on command line, its a query */
2610 if (newValue == NULL) {
2611 WCHAR *space;
2612 WCHAR subkey[MAXSTRING];
2614 /* Query terminates the parameter at the first space */
2615 strcpyW(keyValue, command);
2616 space = strchrW(keyValue, ' ');
2617 if (space) *space=0x00;
2619 /* Set up key name */
2620 strcpyW(subkey, keyValue);
2621 if (!assoc) strcatW(subkey, shOpCmdW);
2623 if (RegOpenKeyEx(key, subkey, 0,
2624 accessOptions, &readKey) == ERROR_SUCCESS) {
2626 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2627 (LPBYTE)keyValue, &valueLen);
2628 WCMD_output_asis(command);
2629 WCMD_output_asis(equalW);
2630 /* If no default value found, leave line empty after '=' */
2631 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2632 WCMD_output_asis(newline);
2633 RegCloseKey(readKey);
2635 } else {
2636 WCHAR msgbuffer[MAXSTRING];
2637 WCHAR outbuffer[MAXSTRING];
2639 /* Load the translated 'File association not found' */
2640 if (assoc) {
2641 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2642 } else {
2643 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2645 wsprintf(outbuffer, msgbuffer, keyValue);
2646 WCMD_output_asis(outbuffer);
2647 errorlevel = 2;
2650 /* Not a query - its a set or clear of a value */
2651 } else {
2653 WCHAR subkey[MAXSTRING];
2655 /* Get pointer to new value */
2656 *newValue = 0x00;
2657 newValue++;
2659 /* Set up key name */
2660 strcpyW(subkey, command);
2661 if (!assoc) strcatW(subkey, shOpCmdW);
2663 /* If nothing after '=' then clear value - only valid for ASSOC */
2664 if (*newValue == 0x00) {
2666 if (assoc) rc = RegDeleteKey(key, command);
2667 if (assoc && rc == ERROR_SUCCESS) {
2668 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2670 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2671 WCMD_print_error();
2672 errorlevel = 2;
2674 } else {
2675 WCHAR msgbuffer[MAXSTRING];
2676 WCHAR outbuffer[MAXSTRING];
2678 /* Load the translated 'File association not found' */
2679 if (assoc) {
2680 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2681 sizeof(msgbuffer)/sizeof(WCHAR));
2682 } else {
2683 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2684 sizeof(msgbuffer)/sizeof(WCHAR));
2686 wsprintf(outbuffer, msgbuffer, keyValue);
2687 WCMD_output_asis(outbuffer);
2688 errorlevel = 2;
2691 /* It really is a set value = contents */
2692 } else {
2693 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2694 accessOptions, NULL, &readKey, NULL);
2695 if (rc == ERROR_SUCCESS) {
2696 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2697 (LPBYTE)newValue, strlenW(newValue));
2698 RegCloseKey(readKey);
2701 if (rc != ERROR_SUCCESS) {
2702 WCMD_print_error();
2703 errorlevel = 2;
2704 } else {
2705 WCMD_output_asis(command);
2706 WCMD_output_asis(equalW);
2707 WCMD_output_asis(newValue);
2708 WCMD_output_asis(newline);
2714 /* Clean up */
2715 RegCloseKey(key);
2718 /****************************************************************************
2719 * WCMD_color
2721 * Clear the terminal screen.
2724 void WCMD_color (void) {
2726 /* Emulate by filling the screen from the top left to bottom right with
2727 spaces, then moving the cursor to the top left afterwards */
2728 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2729 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2731 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2732 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2733 return;
2736 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2738 COORD topLeft;
2739 DWORD screenSize;
2740 DWORD color = 0;
2742 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2744 topLeft.X = 0;
2745 topLeft.Y = 0;
2747 /* Convert the color hex digits */
2748 if (param1[0] == 0x00) {
2749 color = defaultColor;
2750 } else {
2751 color = strtoulW(param1, NULL, 16);
2754 /* Fail if fg == bg color */
2755 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2756 errorlevel = 1;
2757 return;
2760 /* Set the current screen contents and ensure all future writes
2761 remain this color */
2762 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2763 SetConsoleTextAttribute(hStdOut, color);