push 43f03fe87c2254c6df67b2de3c08b5b20fd64327
[wine/hacks.git] / programs / cmd / builtins.c
blobbcdf840e7bc1931b6108ad8dc3c2fb9be32a2a43
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_ask_confirm
72 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
73 * answer.
75 * Returns True if Y (or A) answer is selected
76 * If optionAll contains a pointer, ALL is allowed, and if answered
77 * set to TRUE
80 static BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
82 WCHAR msgbuffer[MAXSTRING];
83 WCHAR Ybuffer[MAXSTRING];
84 WCHAR Nbuffer[MAXSTRING];
85 WCHAR Abuffer[MAXSTRING];
86 WCHAR answer[MAX_PATH] = {'\0'};
87 DWORD count = 0;
89 /* Load the translated 'Are you sure', plus valid answers */
90 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
91 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
92 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
93 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
95 /* Loop waiting on a Y or N */
96 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
97 static const WCHAR startBkt[] = {' ','(','\0'};
98 static const WCHAR endBkt[] = {')','?','\0'};
100 WCMD_output_asis (message);
101 if (showSureText) {
102 WCMD_output_asis (msgbuffer);
104 WCMD_output_asis (startBkt);
105 WCMD_output_asis (Ybuffer);
106 WCMD_output_asis (fslashW);
107 WCMD_output_asis (Nbuffer);
108 if (optionAll) {
109 WCMD_output_asis (fslashW);
110 WCMD_output_asis (Abuffer);
112 WCMD_output_asis (endBkt);
113 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
114 sizeof(answer)/sizeof(WCHAR), &count, NULL);
115 answer[0] = toupperW(answer[0]);
118 /* Return the answer */
119 return ((answer[0] == Ybuffer[0]) ||
120 (optionAll && (answer[0] == Abuffer[0])));
123 /****************************************************************************
124 * WCMD_clear_screen
126 * Clear the terminal screen.
129 void WCMD_clear_screen (void) {
131 /* Emulate by filling the screen from the top left to bottom right with
132 spaces, then moving the cursor to the top left afterwards */
133 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
134 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
136 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
138 COORD topLeft;
139 DWORD screenSize;
141 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
143 topLeft.X = 0;
144 topLeft.Y = 0;
145 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
146 SetConsoleCursorPosition(hStdOut, topLeft);
150 /****************************************************************************
151 * WCMD_change_tty
153 * Change the default i/o device (ie redirect STDin/STDout).
156 void WCMD_change_tty (void) {
158 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
162 /****************************************************************************
163 * WCMD_copy
165 * Copy a file or wildcarded set.
166 * FIXME: Add support for a+b+c type syntax
169 void WCMD_copy (void) {
171 WIN32_FIND_DATA fd;
172 HANDLE hff;
173 BOOL force, status;
174 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
175 DWORD len;
176 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
177 BOOL copyToDir = FALSE;
178 BOOL copyFromDir = FALSE;
179 WCHAR srcspec[MAX_PATH];
180 DWORD attribs;
181 WCHAR drive[10];
182 WCHAR dir[MAX_PATH];
183 WCHAR fname[MAX_PATH];
184 WCHAR ext[MAX_PATH];
186 if (param1[0] == 0x00) {
187 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
188 return;
191 /* Convert source into full spec */
192 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
193 GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
194 if (srcpath[strlenW(srcpath) - 1] == '\\')
195 srcpath[strlenW(srcpath) - 1] = '\0';
197 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
198 attribs = GetFileAttributes(srcpath);
199 } else {
200 attribs = 0;
202 strcpyW(srcspec, srcpath);
204 /* If a directory, then add \* on the end when searching */
205 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
206 strcatW(srcpath, slashW);
207 copyFromDir = TRUE;
208 strcatW(srcspec, slashW);
209 strcatW(srcspec, starW);
210 } else {
211 WCMD_splitpath(srcpath, drive, dir, fname, ext);
212 strcpyW(srcpath, drive);
213 strcatW(srcpath, dir);
216 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
218 /* If no destination supplied, assume current directory */
219 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
220 if (param2[0] == 0x00) {
221 strcpyW(param2, dotW);
224 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
225 if (outpath[strlenW(outpath) - 1] == '\\')
226 outpath[strlenW(outpath) - 1] = '\0';
227 attribs = GetFileAttributes(outpath);
228 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
229 strcatW (outpath, slashW);
230 copyToDir = TRUE;
232 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
233 wine_dbgstr_w(outpath), copyToDir);
235 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
236 if (strstrW (quals, parmNoY))
237 force = FALSE;
238 else if (strstrW (quals, parmY))
239 force = TRUE;
240 else {
241 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
242 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
245 /* Loop through all source files */
246 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
247 hff = FindFirstFile (srcspec, &fd);
248 if (hff != INVALID_HANDLE_VALUE) {
249 do {
250 WCHAR outname[MAX_PATH];
251 WCHAR srcname[MAX_PATH];
252 BOOL overwrite = force;
254 /* Destination is either supplied filename, or source name in
255 supplied destination directory */
256 strcpyW(outname, outpath);
257 if (copyToDir) strcatW(outname, fd.cFileName);
258 strcpyW(srcname, srcpath);
259 strcatW(srcname, fd.cFileName);
261 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
262 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
264 /* Skip . and .., and directories */
265 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
266 overwrite = FALSE;
267 WINE_TRACE("Skipping directories\n");
270 /* Prompt before overwriting */
271 else if (!overwrite) {
272 attribs = GetFileAttributes(outname);
273 if (attribs != INVALID_FILE_ATTRIBUTES) {
274 WCHAR buffer[MAXSTRING];
275 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
276 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
278 else overwrite = TRUE;
281 /* Do the copy as appropriate */
282 if (overwrite) {
283 status = CopyFile (srcname, outname, FALSE);
284 if (!status) WCMD_print_error ();
287 } while (FindNextFile(hff, &fd) != 0);
288 FindClose (hff);
289 } else {
290 status = ERROR_FILE_NOT_FOUND;
291 WCMD_print_error ();
295 /****************************************************************************
296 * WCMD_create_dir
298 * Create a directory.
300 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
301 * they do not already exist.
304 static BOOL create_full_path(WCHAR* path)
306 int len;
307 WCHAR *new_path;
308 BOOL ret = TRUE;
310 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
311 strcpyW(new_path,path);
313 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
314 new_path[len - 1] = 0;
316 while (!CreateDirectory(new_path,NULL))
318 WCHAR *slash;
319 DWORD last_error = GetLastError();
320 if (last_error == ERROR_ALREADY_EXISTS)
321 break;
323 if (last_error != ERROR_PATH_NOT_FOUND)
325 ret = FALSE;
326 break;
329 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
331 ret = FALSE;
332 break;
335 len = slash - new_path;
336 new_path[len] = 0;
337 if (!create_full_path(new_path))
339 ret = FALSE;
340 break;
342 new_path[len] = '\\';
344 HeapFree(GetProcessHeap(),0,new_path);
345 return ret;
348 void WCMD_create_dir (void) {
350 if (param1[0] == 0x00) {
351 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
352 return;
354 if (!create_full_path(param1)) WCMD_print_error ();
357 /****************************************************************************
358 * WCMD_delete
360 * Delete a file or wildcarded set.
362 * Note on /A:
363 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
364 * - Each set is a pattern, eg /ahr /as-r means
365 * readonly+hidden OR nonreadonly system files
366 * - The '-' applies to a single field, ie /a:-hr means read only
367 * non-hidden files
370 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
372 int argno = 0;
373 int argsProcessed = 0;
374 WCHAR *argN = command;
375 BOOL foundAny = FALSE;
376 static const WCHAR parmA[] = {'/','A','\0'};
377 static const WCHAR parmQ[] = {'/','Q','\0'};
378 static const WCHAR parmP[] = {'/','P','\0'};
379 static const WCHAR parmS[] = {'/','S','\0'};
380 static const WCHAR parmF[] = {'/','F','\0'};
382 /* If not recursing, clear error flag */
383 if (expectDir) errorlevel = 0;
385 /* Loop through all args */
386 while (argN) {
387 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
388 WCHAR argCopy[MAX_PATH];
390 if (argN && argN[0] != '/') {
392 WIN32_FIND_DATA fd;
393 HANDLE hff;
394 WCHAR fpath[MAX_PATH];
395 WCHAR *p;
396 BOOL handleParm = TRUE;
397 BOOL found = FALSE;
398 static const WCHAR anyExt[]= {'.','*','\0'};
400 strcpyW(argCopy, thisArg);
401 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
402 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
403 argsProcessed++;
405 /* If filename part of parameter is * or *.*, prompt unless
406 /Q supplied. */
407 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
409 WCHAR drive[10];
410 WCHAR dir[MAX_PATH];
411 WCHAR fname[MAX_PATH];
412 WCHAR ext[MAX_PATH];
414 /* Convert path into actual directory spec */
415 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
416 WCMD_splitpath(fpath, drive, dir, fname, ext);
418 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
419 if ((strcmpW(fname, starW) == 0) &&
420 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
421 BOOL ok;
422 WCHAR question[MAXSTRING];
423 static const WCHAR fmt[] = {'%','s',' ','\0'};
425 /* Note: Flag as found, to avoid file not found message */
426 found = TRUE;
428 /* Ask for confirmation */
429 wsprintf(question, fmt, fpath);
430 ok = WCMD_ask_confirm(question, TRUE, NULL);
432 /* Abort if answer is 'N' */
433 if (!ok) continue;
437 /* First, try to delete in the current directory */
438 hff = FindFirstFile (argCopy, &fd);
439 if (hff == INVALID_HANDLE_VALUE) {
440 handleParm = FALSE;
441 } else {
442 found = TRUE;
445 /* Support del <dirname> by just deleting all files dirname\* */
446 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
447 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
448 WCHAR modifiedParm[MAX_PATH];
449 static const WCHAR slashStar[] = {'\\','*','\0'};
451 strcpyW(modifiedParm, argCopy);
452 strcatW(modifiedParm, slashStar);
453 FindClose(hff);
454 found = TRUE;
455 WCMD_delete(modifiedParm, FALSE);
457 } else if (handleParm) {
459 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
460 strcpyW (fpath, argCopy);
461 do {
462 p = strrchrW (fpath, '\\');
463 if (p != NULL) {
464 *++p = '\0';
465 strcatW (fpath, fd.cFileName);
467 else strcpyW (fpath, fd.cFileName);
468 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
469 BOOL ok = TRUE;
470 WCHAR *nextA = strstrW (quals, parmA);
472 /* Handle attribute matching (/A) */
473 if (nextA != NULL) {
474 ok = FALSE;
475 while (nextA != NULL && !ok) {
477 WCHAR *thisA = (nextA+2);
478 BOOL stillOK = TRUE;
480 /* Skip optional : */
481 if (*thisA == ':') thisA++;
483 /* Parse each of the /A[:]xxx in turn */
484 while (*thisA && *thisA != '/') {
485 BOOL negate = FALSE;
486 BOOL attribute = FALSE;
488 /* Match negation of attribute first */
489 if (*thisA == '-') {
490 negate=TRUE;
491 thisA++;
494 /* Match attribute */
495 switch (*thisA) {
496 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
497 break;
498 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
499 break;
500 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
501 break;
502 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
503 break;
504 default:
505 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
508 /* Now check result, keeping a running boolean about whether it
509 matches all parsed attributes so far */
510 if (attribute && !negate) {
511 stillOK = stillOK;
512 } else if (!attribute && negate) {
513 stillOK = stillOK;
514 } else {
515 stillOK = FALSE;
517 thisA++;
520 /* Save the running total as the final result */
521 ok = stillOK;
523 /* Step on to next /A set */
524 nextA = strstrW (nextA+1, parmA);
528 /* /P means prompt for each file */
529 if (ok && strstrW (quals, parmP) != NULL) {
530 WCHAR question[MAXSTRING];
532 /* Ask for confirmation */
533 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
534 ok = WCMD_ask_confirm(question, FALSE, NULL);
537 /* Only proceed if ok to */
538 if (ok) {
540 /* If file is read only, and /F supplied, delete it */
541 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
542 strstrW (quals, parmF) != NULL) {
543 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
546 /* Now do the delete */
547 if (!DeleteFile (fpath)) WCMD_print_error ();
551 } while (FindNextFile(hff, &fd) != 0);
552 FindClose (hff);
555 /* Now recurse into all subdirectories handling the parameter in the same way */
556 if (strstrW (quals, parmS) != NULL) {
558 WCHAR thisDir[MAX_PATH];
559 int cPos;
561 WCHAR drive[10];
562 WCHAR dir[MAX_PATH];
563 WCHAR fname[MAX_PATH];
564 WCHAR ext[MAX_PATH];
566 /* Convert path into actual directory spec */
567 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
568 WCMD_splitpath(thisDir, drive, dir, fname, ext);
570 strcpyW(thisDir, drive);
571 strcatW(thisDir, dir);
572 cPos = strlenW(thisDir);
574 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
576 /* Append '*' to the directory */
577 thisDir[cPos] = '*';
578 thisDir[cPos+1] = 0x00;
580 hff = FindFirstFile (thisDir, &fd);
582 /* Remove residual '*' */
583 thisDir[cPos] = 0x00;
585 if (hff != INVALID_HANDLE_VALUE) {
586 DIRECTORY_STACK *allDirs = NULL;
587 DIRECTORY_STACK *lastEntry = NULL;
589 do {
590 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
591 (strcmpW(fd.cFileName, dotdotW) != 0) &&
592 (strcmpW(fd.cFileName, dotW) != 0)) {
594 DIRECTORY_STACK *nextDir;
595 WCHAR subParm[MAX_PATH];
597 /* Work out search parameter in sub dir */
598 strcpyW (subParm, thisDir);
599 strcatW (subParm, fd.cFileName);
600 strcatW (subParm, slashW);
601 strcatW (subParm, fname);
602 strcatW (subParm, ext);
603 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
605 /* Allocate memory, add to list */
606 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
607 if (allDirs == NULL) allDirs = nextDir;
608 if (lastEntry != NULL) lastEntry->next = nextDir;
609 lastEntry = nextDir;
610 nextDir->next = NULL;
611 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
612 (strlenW(subParm)+1) * sizeof(WCHAR));
613 strcpyW(nextDir->dirName, subParm);
615 } while (FindNextFile(hff, &fd) != 0);
616 FindClose (hff);
618 /* Go through each subdir doing the delete */
619 while (allDirs != NULL) {
620 DIRECTORY_STACK *tempDir;
622 tempDir = allDirs->next;
623 found |= WCMD_delete (allDirs->dirName, FALSE);
625 HeapFree(GetProcessHeap(),0,allDirs->dirName);
626 HeapFree(GetProcessHeap(),0,allDirs);
627 allDirs = tempDir;
631 /* Keep running total to see if any found, and if not recursing
632 issue error message */
633 if (expectDir) {
634 if (!found) {
635 errorlevel = 1;
636 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
639 foundAny |= found;
643 /* Handle no valid args */
644 if (argsProcessed == 0) {
645 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
648 return foundAny;
651 /****************************************************************************
652 * WCMD_echo
654 * Echo input to the screen (or not). We don't try to emulate the bugs
655 * in DOS (try typing "ECHO ON AGAIN" for an example).
658 void WCMD_echo (const WCHAR *command) {
660 int count;
662 if ((command[0] == '.') && (command[1] == 0)) {
663 WCMD_output (newline);
664 return;
666 if (command[0]==' ')
667 command++;
668 count = strlenW(command);
669 if (count == 0) {
670 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
671 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
672 return;
674 if (lstrcmpiW(command, onW) == 0) {
675 echo_mode = 1;
676 return;
678 if (lstrcmpiW(command, offW) == 0) {
679 echo_mode = 0;
680 return;
682 WCMD_output_asis (command);
683 WCMD_output (newline);
687 /**************************************************************************
688 * WCMD_for
690 * Batch file loop processing.
692 * On entry: cmdList contains the syntax up to the set
693 * next cmdList and all in that bracket contain the set data
694 * next cmdlist contains the DO cmd
695 * following that is either brackets or && entries (as per if)
699 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
701 WIN32_FIND_DATA fd;
702 HANDLE hff;
703 int i;
704 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
705 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
706 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
707 WCHAR variable[4];
708 WCHAR *firstCmd;
709 int thisDepth;
711 WCHAR *curPos = p;
712 BOOL expandDirs = FALSE;
713 BOOL useNumbers = FALSE;
714 BOOL doRecursive = FALSE;
715 BOOL doFileset = FALSE;
716 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
717 int itemNum;
718 CMD_LIST *thisCmdStart;
721 /* Handle optional qualifiers (multiple are allowed) */
722 while (*curPos && *curPos == '/') {
723 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
724 curPos++;
725 switch (toupperW(*curPos)) {
726 case 'D': curPos++; expandDirs = TRUE; break;
727 case 'L': curPos++; useNumbers = TRUE; break;
729 /* Recursive is special case - /R can have an optional path following it */
730 /* filenamesets are another special case - /F can have an optional options following it */
731 case 'R':
732 case 'F':
734 BOOL isRecursive = (*curPos == 'R');
736 if (isRecursive) doRecursive = TRUE;
737 else doFileset = TRUE;
739 /* Skip whitespace */
740 curPos++;
741 while (*curPos && *curPos==' ') curPos++;
743 /* Next parm is either qualifier, path/options or variable -
744 only care about it if it is the path/options */
745 if (*curPos && *curPos != '/' && *curPos != '%') {
746 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
747 else WINE_FIXME("/F needs to handle options\n");
749 break;
751 default:
752 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
753 curPos++;
756 /* Skip whitespace between qualifiers */
757 while (*curPos && *curPos==' ') curPos++;
760 /* Skip whitespace before variable */
761 while (*curPos && *curPos==' ') curPos++;
763 /* Ensure line continues with variable */
764 if (!*curPos || *curPos != '%') {
765 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
766 return;
769 /* Variable should follow */
770 i = 0;
771 while (curPos[i] && curPos[i]!=' ') i++;
772 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
773 variable[i] = 0x00;
774 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
775 curPos = &curPos[i];
777 /* Skip whitespace before IN */
778 while (*curPos && *curPos==' ') curPos++;
780 /* Ensure line continues with IN */
781 if (!*curPos || lstrcmpiW (curPos, inW)) {
782 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
783 return;
786 /* Save away where the set of data starts and the variable */
787 thisDepth = (*cmdList)->bracketDepth;
788 *cmdList = (*cmdList)->nextcommand;
789 setStart = (*cmdList);
791 /* Skip until the close bracket */
792 WINE_TRACE("Searching %p as the set\n", *cmdList);
793 while (*cmdList &&
794 (*cmdList)->command != NULL &&
795 (*cmdList)->bracketDepth > thisDepth) {
796 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
797 *cmdList = (*cmdList)->nextcommand;
800 /* Skip the close bracket, if there is one */
801 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
803 /* Syntax error if missing close bracket, or nothing following it
804 and once we have the complete set, we expect a DO */
805 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
806 if ((*cmdList == NULL) ||
807 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
808 (*cmdList)->command, 3, doW, -1) != 2)) {
809 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
810 return;
813 /* Save away the starting position for the commands (and offset for the
814 first one */
815 cmdStart = *cmdList;
816 cmdEnd = *cmdList;
817 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
818 itemNum = 0;
820 thisSet = setStart;
821 /* Loop through all set entries */
822 while (thisSet &&
823 thisSet->command != NULL &&
824 thisSet->bracketDepth >= thisDepth) {
826 /* Loop through all entries on the same line */
827 WCHAR *item;
828 WCHAR *itemStart;
830 WINE_TRACE("Processing for set %p\n", thisSet);
831 i = 0;
832 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
835 * If the parameter within the set has a wildcard then search for matching files
836 * otherwise do a literal substitution.
838 static const WCHAR wildcards[] = {'*','?','\0'};
839 thisCmdStart = cmdStart;
841 itemNum++;
842 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
844 if (!useNumbers && !doFileset) {
845 if (strpbrkW (item, wildcards)) {
846 hff = FindFirstFile (item, &fd);
847 if (hff != INVALID_HANDLE_VALUE) {
848 do {
849 BOOL isDirectory = FALSE;
851 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
853 /* Handle as files or dirs appropriately, but ignore . and .. */
854 if (isDirectory == expandDirs &&
855 (strcmpW(fd.cFileName, dotdotW) != 0) &&
856 (strcmpW(fd.cFileName, dotW) != 0))
858 thisCmdStart = cmdStart;
859 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
860 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
861 fd.cFileName, FALSE, TRUE);
864 } while (FindNextFile(hff, &fd) != 0);
865 FindClose (hff);
867 } else {
868 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
871 } else if (useNumbers) {
872 /* Convert the first 3 numbers to signed longs and save */
873 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
874 /* else ignore them! */
876 /* Filesets - either a list of files, or a command to run and parse the output */
877 } else if (doFileset && *itemStart != '"') {
879 HANDLE input;
880 WCHAR temp_file[MAX_PATH];
882 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
883 wine_dbgstr_w(item));
885 /* If backquote or single quote, we need to launch that command
886 and parse the results - use a temporary file */
887 if (*itemStart == '`' || *itemStart == '\'') {
889 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
890 static const WCHAR redirOut[] = {'>','%','s','\0'};
891 static const WCHAR cmdW[] = {'C','M','D','\0'};
893 /* Remove trailing character */
894 itemStart[strlenW(itemStart)-1] = 0x00;
896 /* Get temp filename */
897 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
898 GetTempFileName (temp_path, cmdW, 0, temp_file);
900 /* Execute program and redirect output */
901 wsprintf (temp_cmd, redirOut, (itemStart+1), temp_file);
902 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
904 /* Open the file, read line by line and process */
905 input = CreateFile (temp_file, GENERIC_READ, FILE_SHARE_READ,
906 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
907 } else {
909 /* Open the file, read line by line and process */
910 input = CreateFile (item, GENERIC_READ, FILE_SHARE_READ,
911 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
914 /* Process the input file */
915 if (input == INVALID_HANDLE_VALUE) {
916 WCMD_print_error ();
917 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
918 errorlevel = 1;
919 return; /* FOR loop aborts at first failure here */
921 } else {
923 WCHAR buffer[MAXSTRING] = {'\0'};
924 WCHAR *where, *parm;
926 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
928 /* Skip blank lines*/
929 parm = WCMD_parameter (buffer, 0, &where);
930 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
931 wine_dbgstr_w(buffer));
933 if (where) {
934 /* FIXME: The following should be moved into its own routine and
935 reused for the string literal parsing below */
936 thisCmdStart = cmdStart;
937 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
938 cmdEnd = thisCmdStart;
941 buffer[0] = 0x00;
944 CloseHandle (input);
947 /* Delete the temporary file */
948 if (*itemStart == '`' || *itemStart == '\'') {
949 DeleteFile (temp_file);
952 /* Filesets - A string literal */
953 } else if (doFileset && *itemStart == '"') {
954 WCHAR buffer[MAXSTRING] = {'\0'};
955 WCHAR *where, *parm;
957 /* Skip blank lines, and re-extract parameter now string has quotes removed */
958 strcpyW(buffer, item);
959 parm = WCMD_parameter (buffer, 0, &where);
960 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
961 wine_dbgstr_w(buffer));
963 if (where) {
964 /* FIXME: The following should be moved into its own routine and
965 reused for the string literal parsing below */
966 thisCmdStart = cmdStart;
967 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
968 cmdEnd = thisCmdStart;
972 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
973 cmdEnd = thisCmdStart;
974 i++;
977 /* Move onto the next set line */
978 thisSet = thisSet->nextcommand;
981 /* If /L is provided, now run the for loop */
982 if (useNumbers) {
983 WCHAR thisNum[20];
984 static const WCHAR fmt[] = {'%','d','\0'};
986 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
987 numbers[0], numbers[2], numbers[1]);
988 for (i=numbers[0];
989 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
990 i=i + numbers[1]) {
992 sprintfW(thisNum, fmt, i);
993 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
995 thisCmdStart = cmdStart;
996 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
997 cmdEnd = thisCmdStart;
1001 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1002 all processing, OR it should be pointing to the end of && processing OR
1003 it should be pointing at the NULL end of bracket for the DO. The return
1004 value needs to be the NEXT command to execute, which it either is, or
1005 we need to step over the closing bracket */
1006 *cmdList = cmdEnd;
1007 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1011 /*****************************************************************************
1012 * WCMD_part_execute
1014 * Execute a command, and any && or bracketed follow on to the command. The
1015 * first command to be executed may not be at the front of the
1016 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1018 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1019 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1021 CMD_LIST *curPosition = *cmdList;
1022 int myDepth = (*cmdList)->bracketDepth;
1024 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1025 cmdList, wine_dbgstr_w(firstcmd),
1026 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1027 conditionTRUE);
1029 /* Skip leading whitespace between condition and the command */
1030 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1032 /* Process the first command, if there is one */
1033 if (conditionTRUE && firstcmd && *firstcmd) {
1034 WCHAR *command = WCMD_strdupW(firstcmd);
1035 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1036 free (command);
1040 /* If it didn't move the position, step to next command */
1041 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1043 /* Process any other parts of the command */
1044 if (*cmdList) {
1045 BOOL processThese = TRUE;
1047 if (isIF) processThese = conditionTRUE;
1049 while (*cmdList) {
1050 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1052 /* execute all appropriate commands */
1053 curPosition = *cmdList;
1055 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1056 *cmdList,
1057 (*cmdList)->prevDelim,
1058 (*cmdList)->bracketDepth, myDepth);
1060 /* Execute any statements appended to the line */
1061 /* FIXME: Only if previous call worked for && or failed for || */
1062 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1063 (*cmdList)->prevDelim != CMD_ONSUCCESS) {
1064 if (processThese) {
1065 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1066 value, cmdList);
1068 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1070 /* Execute any appended to the statement with (...) */
1071 } else if ((*cmdList)->bracketDepth > myDepth) {
1072 if (processThese) {
1073 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1074 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1076 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1078 /* End of the command - does 'ELSE ' follow as the next command? */
1079 } else {
1080 if (isIF && CompareString (LOCALE_USER_DEFAULT,
1081 NORM_IGNORECASE | SORT_STRINGSORT,
1082 (*cmdList)->command, 5, ifElse, -1) == 2) {
1084 /* Swap between if and else processing */
1085 processThese = !processThese;
1087 /* Process the ELSE part */
1088 if (processThese) {
1089 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1091 /* Skip leading whitespace between condition and the command */
1092 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1093 if (*cmd) {
1094 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1097 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1098 } else {
1099 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1100 break;
1105 return;
1108 /**************************************************************************
1109 * WCMD_give_help
1111 * Simple on-line help. Help text is stored in the resource file.
1114 void WCMD_give_help (WCHAR *command) {
1116 int i;
1118 command = WCMD_strtrim_leading_spaces(command);
1119 if (strlenW(command) == 0) {
1120 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1122 else {
1123 for (i=0; i<=WCMD_EXIT; i++) {
1124 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1125 param1, -1, inbuilt[i], -1) == 2) {
1126 WCMD_output_asis (WCMD_LoadMessage(i));
1127 return;
1130 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
1132 return;
1135 /****************************************************************************
1136 * WCMD_go_to
1138 * Batch file jump instruction. Not the most efficient algorithm ;-)
1139 * Prints error message if the specified label cannot be found - the file pointer is
1140 * then at EOF, effectively stopping the batch file.
1141 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1144 void WCMD_goto (CMD_LIST **cmdList) {
1146 WCHAR string[MAX_PATH];
1148 /* Do not process any more parts of a processed multipart or multilines command */
1149 if (cmdList) *cmdList = NULL;
1151 if (param1[0] == 0x00) {
1152 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1153 return;
1155 if (context != NULL) {
1156 WCHAR *paramStart = param1;
1157 static const WCHAR eofW[] = {':','e','o','f','\0'};
1159 /* Handle special :EOF label */
1160 if (lstrcmpiW (eofW, param1) == 0) {
1161 context -> skip_rest = TRUE;
1162 return;
1165 /* Support goto :label as well as goto label */
1166 if (*paramStart == ':') paramStart++;
1168 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1169 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1170 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1172 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1174 return;
1177 /*****************************************************************************
1178 * WCMD_pushd
1180 * Push a directory onto the stack
1183 void WCMD_pushd (WCHAR *command) {
1184 struct env_stack *curdir;
1185 WCHAR *thisdir;
1186 static const WCHAR parmD[] = {'/','D','\0'};
1188 if (strchrW(command, '/') != NULL) {
1189 SetLastError(ERROR_INVALID_PARAMETER);
1190 WCMD_print_error();
1191 return;
1194 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1195 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1196 if( !curdir || !thisdir ) {
1197 LocalFree(curdir);
1198 LocalFree(thisdir);
1199 WINE_ERR ("out of memory\n");
1200 return;
1203 /* Change directory using CD code with /D parameter */
1204 strcpyW(quals, parmD);
1205 GetCurrentDirectoryW (1024, thisdir);
1206 errorlevel = 0;
1207 WCMD_setshow_default(command);
1208 if (errorlevel) {
1209 LocalFree(curdir);
1210 LocalFree(thisdir);
1211 return;
1212 } else {
1213 curdir -> next = pushd_directories;
1214 curdir -> strings = thisdir;
1215 if (pushd_directories == NULL) {
1216 curdir -> u.stackdepth = 1;
1217 } else {
1218 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1220 pushd_directories = curdir;
1225 /*****************************************************************************
1226 * WCMD_popd
1228 * Pop a directory from the stack
1231 void WCMD_popd (void) {
1232 struct env_stack *temp = pushd_directories;
1234 if (!pushd_directories)
1235 return;
1237 /* pop the old environment from the stack, and make it the current dir */
1238 pushd_directories = temp->next;
1239 SetCurrentDirectoryW(temp->strings);
1240 LocalFree (temp->strings);
1241 LocalFree (temp);
1244 /****************************************************************************
1245 * WCMD_if
1247 * Batch file conditional.
1249 * On entry, cmdlist will point to command containing the IF, and optionally
1250 * the first command to execute (if brackets not found)
1251 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1252 * If ('s were found, execute all within that bracket
1253 * Command may optionally be followed by an ELSE - need to skip instructions
1254 * in the else using the same logic
1256 * FIXME: Much more syntax checking needed!
1259 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1261 int negate = 0, test = 0;
1262 WCHAR condition[MAX_PATH], *command, *s;
1263 static const WCHAR notW[] = {'n','o','t','\0'};
1264 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1265 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1266 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1267 static const WCHAR eqeqW[] = {'=','=','\0'};
1269 if (!lstrcmpiW (param1, notW)) {
1270 negate = 1;
1271 strcpyW (condition, param2);
1273 else {
1274 strcpyW (condition, param1);
1276 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1278 if (!lstrcmpiW (condition, errlvlW)) {
1279 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1280 WCMD_parameter (p, 2+negate, &command);
1282 else if (!lstrcmpiW (condition, existW)) {
1283 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1284 test = 1;
1286 WCMD_parameter (p, 2+negate, &command);
1288 else if (!lstrcmpiW (condition, defdW)) {
1289 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1290 test = 1;
1292 WCMD_parameter (p, 2+negate, &command);
1294 else if ((s = strstrW (p, eqeqW))) {
1295 s += 2;
1296 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1297 WCMD_parameter (s, 1, &command);
1299 else {
1300 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1301 return;
1304 /* Process rest of IF statement which is on the same line
1305 Note: This may process all or some of the cmdList (eg a GOTO) */
1306 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1309 /****************************************************************************
1310 * WCMD_move
1312 * Move a file, directory tree or wildcarded set of files.
1315 void WCMD_move (void) {
1317 int status;
1318 WIN32_FIND_DATA fd;
1319 HANDLE hff;
1320 WCHAR input[MAX_PATH];
1321 WCHAR output[MAX_PATH];
1322 WCHAR drive[10];
1323 WCHAR dir[MAX_PATH];
1324 WCHAR fname[MAX_PATH];
1325 WCHAR ext[MAX_PATH];
1327 if (param1[0] == 0x00) {
1328 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1329 return;
1332 /* If no destination supplied, assume current directory */
1333 if (param2[0] == 0x00) {
1334 strcpyW(param2, dotW);
1337 /* If 2nd parm is directory, then use original filename */
1338 /* Convert partial path to full path */
1339 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1340 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1341 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1342 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1344 /* Split into components */
1345 WCMD_splitpath(input, drive, dir, fname, ext);
1347 hff = FindFirstFile (input, &fd);
1348 while (hff != INVALID_HANDLE_VALUE) {
1349 WCHAR dest[MAX_PATH];
1350 WCHAR src[MAX_PATH];
1351 DWORD attribs;
1353 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1355 /* Build src & dest name */
1356 strcpyW(src, drive);
1357 strcatW(src, dir);
1359 /* See if dest is an existing directory */
1360 attribs = GetFileAttributes(output);
1361 if (attribs != INVALID_FILE_ATTRIBUTES &&
1362 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1363 strcpyW(dest, output);
1364 strcatW(dest, slashW);
1365 strcatW(dest, fd.cFileName);
1366 } else {
1367 strcpyW(dest, output);
1370 strcatW(src, fd.cFileName);
1372 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1373 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1375 /* Check if file is read only, otherwise move it */
1376 attribs = GetFileAttributes(src);
1377 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1378 (attribs & FILE_ATTRIBUTE_READONLY)) {
1379 SetLastError(ERROR_ACCESS_DENIED);
1380 status = 0;
1381 } else {
1382 BOOL ok = TRUE;
1384 /* If destination exists, prompt unless /Y supplied */
1385 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1386 BOOL force = FALSE;
1387 WCHAR copycmd[MAXSTRING];
1388 int len;
1390 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1391 if (strstrW (quals, parmNoY))
1392 force = FALSE;
1393 else if (strstrW (quals, parmY))
1394 force = TRUE;
1395 else {
1396 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1397 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1398 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1399 && ! lstrcmpiW (copycmd, parmY));
1402 /* Prompt if overwriting */
1403 if (!force) {
1404 WCHAR question[MAXSTRING];
1405 WCHAR yesChar[10];
1407 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1409 /* Ask for confirmation */
1410 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1411 ok = WCMD_ask_confirm(question, FALSE, NULL);
1413 /* So delete the destination prior to the move */
1414 if (ok) {
1415 if (!DeleteFile (dest)) {
1416 WCMD_print_error ();
1417 errorlevel = 1;
1418 ok = FALSE;
1424 if (ok) {
1425 status = MoveFile (src, dest);
1426 } else {
1427 status = 1; /* Anything other than 0 to prevent error msg below */
1431 if (!status) {
1432 WCMD_print_error ();
1433 errorlevel = 1;
1436 /* Step on to next match */
1437 if (FindNextFile(hff, &fd) == 0) {
1438 FindClose(hff);
1439 hff = INVALID_HANDLE_VALUE;
1440 break;
1445 /****************************************************************************
1446 * WCMD_pause
1448 * Wait for keyboard input.
1451 void WCMD_pause (void) {
1453 DWORD count;
1454 WCHAR string[32];
1456 WCMD_output (anykey);
1457 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1458 sizeof(string)/sizeof(WCHAR), &count, NULL);
1461 /****************************************************************************
1462 * WCMD_remove_dir
1464 * Delete a directory.
1467 void WCMD_remove_dir (WCHAR *command) {
1469 int argno = 0;
1470 int argsProcessed = 0;
1471 WCHAR *argN = command;
1472 static const WCHAR parmS[] = {'/','S','\0'};
1473 static const WCHAR parmQ[] = {'/','Q','\0'};
1475 /* Loop through all args */
1476 while (argN) {
1477 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1478 if (argN && argN[0] != '/') {
1479 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1480 wine_dbgstr_w(quals));
1481 argsProcessed++;
1483 /* If subdirectory search not supplied, just try to remove
1484 and report error if it fails (eg if it contains a file) */
1485 if (strstrW (quals, parmS) == NULL) {
1486 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1488 /* Otherwise use ShFileOp to recursively remove a directory */
1489 } else {
1491 SHFILEOPSTRUCT lpDir;
1493 /* Ask first */
1494 if (strstrW (quals, parmQ) == NULL) {
1495 BOOL ok;
1496 WCHAR question[MAXSTRING];
1497 static const WCHAR fmt[] = {'%','s',' ','\0'};
1499 /* Ask for confirmation */
1500 wsprintf(question, fmt, thisArg);
1501 ok = WCMD_ask_confirm(question, TRUE, NULL);
1503 /* Abort if answer is 'N' */
1504 if (!ok) return;
1507 /* Do the delete */
1508 lpDir.hwnd = NULL;
1509 lpDir.pTo = NULL;
1510 lpDir.pFrom = thisArg;
1511 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1512 lpDir.wFunc = FO_DELETE;
1513 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1518 /* Handle no valid args */
1519 if (argsProcessed == 0) {
1520 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1521 return;
1526 /****************************************************************************
1527 * WCMD_rename
1529 * Rename a file.
1532 void WCMD_rename (void) {
1534 int status;
1535 HANDLE hff;
1536 WIN32_FIND_DATA fd;
1537 WCHAR input[MAX_PATH];
1538 WCHAR *dotDst = NULL;
1539 WCHAR drive[10];
1540 WCHAR dir[MAX_PATH];
1541 WCHAR fname[MAX_PATH];
1542 WCHAR ext[MAX_PATH];
1543 DWORD attribs;
1545 errorlevel = 0;
1547 /* Must be at least two args */
1548 if (param1[0] == 0x00 || param2[0] == 0x00) {
1549 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1550 errorlevel = 1;
1551 return;
1554 /* Destination cannot contain a drive letter or directory separator */
1555 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1556 SetLastError(ERROR_INVALID_PARAMETER);
1557 WCMD_print_error();
1558 errorlevel = 1;
1559 return;
1562 /* Convert partial path to full path */
1563 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1564 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1565 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1566 dotDst = strchrW(param2, '.');
1568 /* Split into components */
1569 WCMD_splitpath(input, drive, dir, fname, ext);
1571 hff = FindFirstFile (input, &fd);
1572 while (hff != INVALID_HANDLE_VALUE) {
1573 WCHAR dest[MAX_PATH];
1574 WCHAR src[MAX_PATH];
1575 WCHAR *dotSrc = NULL;
1576 int dirLen;
1578 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1580 /* FIXME: If dest name or extension is *, replace with filename/ext
1581 part otherwise use supplied name. This supports:
1582 ren *.fred *.jim
1583 ren jim.* fred.* etc
1584 However, windows has a more complex algorithm supporting eg
1585 ?'s and *'s mid name */
1586 dotSrc = strchrW(fd.cFileName, '.');
1588 /* Build src & dest name */
1589 strcpyW(src, drive);
1590 strcatW(src, dir);
1591 strcpyW(dest, src);
1592 dirLen = strlenW(src);
1593 strcatW(src, fd.cFileName);
1595 /* Build name */
1596 if (param2[0] == '*') {
1597 strcatW(dest, fd.cFileName);
1598 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1599 } else {
1600 strcatW(dest, param2);
1601 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1604 /* Build Extension */
1605 if (dotDst && (*(dotDst+1)=='*')) {
1606 if (dotSrc) strcatW(dest, dotSrc);
1607 } else if (dotDst) {
1608 if (dotDst) strcatW(dest, dotDst);
1611 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1612 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1614 /* Check if file is read only, otherwise move it */
1615 attribs = GetFileAttributes(src);
1616 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1617 (attribs & FILE_ATTRIBUTE_READONLY)) {
1618 SetLastError(ERROR_ACCESS_DENIED);
1619 status = 0;
1620 } else {
1621 status = MoveFile (src, dest);
1624 if (!status) {
1625 WCMD_print_error ();
1626 errorlevel = 1;
1629 /* Step on to next match */
1630 if (FindNextFile(hff, &fd) == 0) {
1631 FindClose(hff);
1632 hff = INVALID_HANDLE_VALUE;
1633 break;
1638 /*****************************************************************************
1639 * WCMD_dupenv
1641 * Make a copy of the environment.
1643 static WCHAR *WCMD_dupenv( const WCHAR *env )
1645 WCHAR *env_copy;
1646 int len;
1648 if( !env )
1649 return NULL;
1651 len = 0;
1652 while ( env[len] )
1653 len += (strlenW(&env[len]) + 1);
1655 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1656 if (!env_copy)
1658 WINE_ERR("out of memory\n");
1659 return env_copy;
1661 memcpy (env_copy, env, len*sizeof (WCHAR));
1662 env_copy[len] = 0;
1664 return env_copy;
1667 /*****************************************************************************
1668 * WCMD_setlocal
1670 * setlocal pushes the environment onto a stack
1671 * Save the environment as unicode so we don't screw anything up.
1673 void WCMD_setlocal (const WCHAR *s) {
1674 WCHAR *env;
1675 struct env_stack *env_copy;
1676 WCHAR cwd[MAX_PATH];
1678 /* DISABLEEXTENSIONS ignored */
1680 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1681 if( !env_copy )
1683 WINE_ERR ("out of memory\n");
1684 return;
1687 env = GetEnvironmentStringsW ();
1689 env_copy->strings = WCMD_dupenv (env);
1690 if (env_copy->strings)
1692 env_copy->next = saved_environment;
1693 saved_environment = env_copy;
1695 /* Save the current drive letter */
1696 GetCurrentDirectory (MAX_PATH, cwd);
1697 env_copy->u.cwd = cwd[0];
1699 else
1700 LocalFree (env_copy);
1702 FreeEnvironmentStringsW (env);
1706 /*****************************************************************************
1707 * WCMD_endlocal
1709 * endlocal pops the environment off a stack
1710 * Note: When searching for '=', search from WCHAR position 1, to handle
1711 * special internal environment variables =C:, =D: etc
1713 void WCMD_endlocal (void) {
1714 WCHAR *env, *old, *p;
1715 struct env_stack *temp;
1716 int len, n;
1718 if (!saved_environment)
1719 return;
1721 /* pop the old environment from the stack */
1722 temp = saved_environment;
1723 saved_environment = temp->next;
1725 /* delete the current environment, totally */
1726 env = GetEnvironmentStringsW ();
1727 old = WCMD_dupenv (GetEnvironmentStringsW ());
1728 len = 0;
1729 while (old[len]) {
1730 n = strlenW(&old[len]) + 1;
1731 p = strchrW(&old[len] + 1, '=');
1732 if (p)
1734 *p++ = 0;
1735 SetEnvironmentVariableW (&old[len], NULL);
1737 len += n;
1739 LocalFree (old);
1740 FreeEnvironmentStringsW (env);
1742 /* restore old environment */
1743 env = temp->strings;
1744 len = 0;
1745 while (env[len]) {
1746 n = strlenW(&env[len]) + 1;
1747 p = strchrW(&env[len] + 1, '=');
1748 if (p)
1750 *p++ = 0;
1751 SetEnvironmentVariableW (&env[len], p);
1753 len += n;
1756 /* Restore current drive letter */
1757 if (IsCharAlpha(temp->u.cwd)) {
1758 WCHAR envvar[4];
1759 WCHAR cwd[MAX_PATH];
1760 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1762 wsprintf(envvar, fmt, temp->u.cwd);
1763 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1764 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1765 SetCurrentDirectory(cwd);
1769 LocalFree (env);
1770 LocalFree (temp);
1773 /*****************************************************************************
1774 * WCMD_setshow_attrib
1776 * Display and optionally sets DOS attributes on a file or directory
1778 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1779 * As a result only the Readonly flag is correctly reported, the Archive bit
1780 * is always set and the rest are not implemented. We do the Right Thing anyway.
1782 * FIXME: No SET functionality.
1786 void WCMD_setshow_attrib (void) {
1788 DWORD count;
1789 HANDLE hff;
1790 WIN32_FIND_DATA fd;
1791 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1793 if (param1[0] == '-') {
1794 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1795 return;
1798 if (strlenW(param1) == 0) {
1799 static const WCHAR slashStarW[] = {'\\','*','\0'};
1801 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1802 strcatW (param1, slashStarW);
1805 hff = FindFirstFile (param1, &fd);
1806 if (hff == INVALID_HANDLE_VALUE) {
1807 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1809 else {
1810 do {
1811 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1812 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1813 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1814 flags[0] = 'H';
1816 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1817 flags[1] = 'S';
1819 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1820 flags[2] = 'A';
1822 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1823 flags[3] = 'R';
1825 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1826 flags[4] = 'T';
1828 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1829 flags[5] = 'C';
1831 WCMD_output (fmt, flags, fd.cFileName);
1832 for (count=0; count < 8; count++) flags[count] = ' ';
1834 } while (FindNextFile(hff, &fd) != 0);
1836 FindClose (hff);
1839 /*****************************************************************************
1840 * WCMD_setshow_default
1842 * Set/Show the current default directory
1845 void WCMD_setshow_default (WCHAR *command) {
1847 BOOL status;
1848 WCHAR string[1024];
1849 WCHAR cwd[1024];
1850 WCHAR *pos;
1851 WIN32_FIND_DATA fd;
1852 HANDLE hff;
1853 static const WCHAR parmD[] = {'/','D','\0'};
1855 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1857 /* Skip /D and trailing whitespace if on the front of the command line */
1858 if (CompareString (LOCALE_USER_DEFAULT,
1859 NORM_IGNORECASE | SORT_STRINGSORT,
1860 command, 2, parmD, -1) == 2) {
1861 command += 2;
1862 while (*command && *command==' ') command++;
1865 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1866 if (strlenW(command) == 0) {
1867 strcatW (cwd, newline);
1868 WCMD_output (cwd);
1870 else {
1871 /* Remove any double quotes, which may be in the
1872 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1873 pos = string;
1874 while (*command) {
1875 if (*command != '"') *pos++ = *command;
1876 command++;
1878 *pos = 0x00;
1880 /* Search for appropriate directory */
1881 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1882 hff = FindFirstFile (string, &fd);
1883 while (hff != INVALID_HANDLE_VALUE) {
1884 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1885 WCHAR fpath[MAX_PATH];
1886 WCHAR drive[10];
1887 WCHAR dir[MAX_PATH];
1888 WCHAR fname[MAX_PATH];
1889 WCHAR ext[MAX_PATH];
1890 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1892 /* Convert path into actual directory spec */
1893 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1894 WCMD_splitpath(fpath, drive, dir, fname, ext);
1896 /* Rebuild path */
1897 wsprintf(string, fmt, drive, dir, fd.cFileName);
1899 FindClose(hff);
1900 hff = INVALID_HANDLE_VALUE;
1901 break;
1904 /* Step on to next match */
1905 if (FindNextFile(hff, &fd) == 0) {
1906 FindClose(hff);
1907 hff = INVALID_HANDLE_VALUE;
1908 break;
1912 /* Change to that directory */
1913 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1915 status = SetCurrentDirectory (string);
1916 if (!status) {
1917 errorlevel = 1;
1918 WCMD_print_error ();
1919 return;
1920 } else {
1922 /* Save away the actual new directory, to store as current location */
1923 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1925 /* Restore old directory if drive letter would change, and
1926 CD x:\directory /D (or pushd c:\directory) not supplied */
1927 if ((strstrW(quals, parmD) == NULL) &&
1928 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1929 SetCurrentDirectory(cwd);
1933 /* Set special =C: type environment variable, for drive letter of
1934 change of directory, even if path was restored due to missing
1935 /D (allows changing drive letter when not resident on that
1936 drive */
1937 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1938 WCHAR env[4];
1939 strcpyW(env, equalW);
1940 memcpy(env+1, string, 2 * sizeof(WCHAR));
1941 env[3] = 0x00;
1942 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1943 SetEnvironmentVariable(env, string);
1947 return;
1950 /****************************************************************************
1951 * WCMD_setshow_date
1953 * Set/Show the system date
1954 * FIXME: Can't change date yet
1957 void WCMD_setshow_date (void) {
1959 WCHAR curdate[64], buffer[64];
1960 DWORD count;
1961 static const WCHAR parmT[] = {'/','T','\0'};
1963 if (strlenW(param1) == 0) {
1964 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1965 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1966 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1967 if (strstrW (quals, parmT) == NULL) {
1968 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1969 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1970 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1971 if (count > 2) {
1972 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1976 else WCMD_print_error ();
1978 else {
1979 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1983 /****************************************************************************
1984 * WCMD_compare
1986 static int WCMD_compare( const void *a, const void *b )
1988 int r;
1989 const WCHAR * const *str_a = a, * const *str_b = b;
1990 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1991 *str_a, -1, *str_b, -1 );
1992 if( r == CSTR_LESS_THAN ) return -1;
1993 if( r == CSTR_GREATER_THAN ) return 1;
1994 return 0;
1997 /****************************************************************************
1998 * WCMD_setshow_sortenv
2000 * sort variables into order for display
2001 * Optionally only display those who start with a stub
2002 * returns the count displayed
2004 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2006 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2007 const WCHAR **str;
2009 if (stub) stublen = strlenW(stub);
2011 /* count the number of strings, and the total length */
2012 while ( s[len] ) {
2013 len += (strlenW(&s[len]) + 1);
2014 count++;
2017 /* add the strings to an array */
2018 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2019 if( !str )
2020 return 0;
2021 str[0] = s;
2022 for( i=1; i<count; i++ )
2023 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2025 /* sort the array */
2026 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2028 /* print it */
2029 for( i=0; i<count; i++ ) {
2030 if (!stub || CompareString (LOCALE_USER_DEFAULT,
2031 NORM_IGNORECASE | SORT_STRINGSORT,
2032 str[i], stublen, stub, -1) == 2) {
2033 /* Don't display special internal variables */
2034 if (str[i][0] != '=') {
2035 WCMD_output_asis(str[i]);
2036 WCMD_output_asis(newline);
2037 displayedcount++;
2042 LocalFree( str );
2043 return displayedcount;
2046 /****************************************************************************
2047 * WCMD_setshow_env
2049 * Set/Show the environment variables
2052 void WCMD_setshow_env (WCHAR *s) {
2054 LPVOID env;
2055 WCHAR *p;
2056 int status;
2057 static const WCHAR parmP[] = {'/','P','\0'};
2059 errorlevel = 0;
2060 if (param1[0] == 0x00 && quals[0] == 0x00) {
2061 env = GetEnvironmentStrings ();
2062 WCMD_setshow_sortenv( env, NULL );
2063 return;
2066 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2067 if (CompareString (LOCALE_USER_DEFAULT,
2068 NORM_IGNORECASE | SORT_STRINGSORT,
2069 s, 2, parmP, -1) == 2) {
2070 WCHAR string[MAXSTRING];
2071 DWORD count;
2073 s += 2;
2074 while (*s && *s==' ') s++;
2076 /* If no parameter, or no '=' sign, return an error */
2077 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2078 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2079 return;
2082 /* Output the prompt */
2083 *p++ = '\0';
2084 if (strlenW(p) != 0) WCMD_output(p);
2086 /* Read the reply */
2087 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2088 sizeof(string)/sizeof(WCHAR), &count, NULL);
2089 if (count > 1) {
2090 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2091 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2092 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2093 wine_dbgstr_w(string));
2094 status = SetEnvironmentVariable (s, string);
2097 } else {
2098 DWORD gle;
2099 p = strchrW (s, '=');
2100 if (p == NULL) {
2101 env = GetEnvironmentStrings ();
2102 if (WCMD_setshow_sortenv( env, s ) == 0) {
2103 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2104 errorlevel = 1;
2106 return;
2108 *p++ = '\0';
2110 if (strlenW(p) == 0) p = NULL;
2111 status = SetEnvironmentVariable (s, p);
2112 gle = GetLastError();
2113 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2114 errorlevel = 1;
2115 } else if ((!status)) WCMD_print_error();
2119 /****************************************************************************
2120 * WCMD_setshow_path
2122 * Set/Show the path environment variable
2125 void WCMD_setshow_path (WCHAR *command) {
2127 WCHAR string[1024];
2128 DWORD status;
2129 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2130 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2132 if (strlenW(param1) == 0) {
2133 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2134 if (status != 0) {
2135 WCMD_output_asis ( pathEqW);
2136 WCMD_output_asis ( string);
2137 WCMD_output_asis ( newline);
2139 else {
2140 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2143 else {
2144 if (*command == '=') command++; /* Skip leading '=' */
2145 status = SetEnvironmentVariable (pathW, command);
2146 if (!status) WCMD_print_error();
2150 /****************************************************************************
2151 * WCMD_setshow_prompt
2153 * Set or show the command prompt.
2156 void WCMD_setshow_prompt (void) {
2158 WCHAR *s;
2159 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2161 if (strlenW(param1) == 0) {
2162 SetEnvironmentVariable (promptW, NULL);
2164 else {
2165 s = param1;
2166 while ((*s == '=') || (*s == ' ')) s++;
2167 if (strlenW(s) == 0) {
2168 SetEnvironmentVariable (promptW, NULL);
2170 else SetEnvironmentVariable (promptW, s);
2174 /****************************************************************************
2175 * WCMD_setshow_time
2177 * Set/Show the system time
2178 * FIXME: Can't change time yet
2181 void WCMD_setshow_time (void) {
2183 WCHAR curtime[64], buffer[64];
2184 DWORD count;
2185 SYSTEMTIME st;
2186 static const WCHAR parmT[] = {'/','T','\0'};
2188 if (strlenW(param1) == 0) {
2189 GetLocalTime(&st);
2190 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2191 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2192 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2193 if (strstrW (quals, parmT) == NULL) {
2194 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2195 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2196 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2197 if (count > 2) {
2198 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2202 else WCMD_print_error ();
2204 else {
2205 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2209 /****************************************************************************
2210 * WCMD_shift
2212 * Shift batch parameters.
2213 * Optional /n says where to start shifting (n=0-8)
2216 void WCMD_shift (WCHAR *command) {
2217 int start;
2219 if (context != NULL) {
2220 WCHAR *pos = strchrW(command, '/');
2221 int i;
2223 if (pos == NULL) {
2224 start = 0;
2225 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2226 start = (*(pos+1) - '0');
2227 } else {
2228 SetLastError(ERROR_INVALID_PARAMETER);
2229 WCMD_print_error();
2230 return;
2233 WINE_TRACE("Shifting variables, starting at %d\n", start);
2234 for (i=start;i<=8;i++) {
2235 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2237 context -> shift_count[9] = context -> shift_count[9] + 1;
2242 /****************************************************************************
2243 * WCMD_title
2245 * Set the console title
2247 void WCMD_title (WCHAR *command) {
2248 SetConsoleTitle(command);
2251 /****************************************************************************
2252 * WCMD_type
2254 * Copy a file to standard output.
2257 void WCMD_type (WCHAR *command) {
2259 int argno = 0;
2260 WCHAR *argN = command;
2261 BOOL writeHeaders = FALSE;
2263 if (param1[0] == 0x00) {
2264 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2265 return;
2268 if (param2[0] != 0x00) writeHeaders = TRUE;
2270 /* Loop through all args */
2271 errorlevel = 0;
2272 while (argN) {
2273 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2275 HANDLE h;
2276 WCHAR buffer[512];
2277 DWORD count;
2279 if (!argN) break;
2281 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2282 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2283 FILE_ATTRIBUTE_NORMAL, NULL);
2284 if (h == INVALID_HANDLE_VALUE) {
2285 WCMD_print_error ();
2286 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2287 errorlevel = 1;
2288 } else {
2289 if (writeHeaders) {
2290 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2291 WCMD_output(fmt, thisArg);
2293 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2294 if (count == 0) break; /* ReadFile reports success on EOF! */
2295 buffer[count] = 0;
2296 WCMD_output_asis (buffer);
2298 CloseHandle (h);
2299 if (!writeHeaders)
2300 WCMD_output_asis (newline);
2305 /****************************************************************************
2306 * WCMD_more
2308 * Output either a file or stdin to screen in pages
2311 void WCMD_more (WCHAR *command) {
2313 int argno = 0;
2314 WCHAR *argN = command;
2315 BOOL useinput = FALSE;
2316 WCHAR moreStr[100];
2317 WCHAR moreStrPage[100];
2318 WCHAR buffer[512];
2319 DWORD count;
2320 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2321 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2322 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2323 ')',' ','-','-','\n','\0'};
2324 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2326 /* Prefix the NLS more with '-- ', then load the text */
2327 errorlevel = 0;
2328 strcpyW(moreStr, moreStart);
2329 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2330 (sizeof(moreStr)/sizeof(WCHAR))-3);
2332 if (param1[0] == 0x00) {
2334 /* Wine implements pipes via temporary files, and hence stdin is
2335 effectively reading from the file. This means the prompts for
2336 more are satisfied by the next line from the input (file). To
2337 avoid this, ensure stdin is to the console */
2338 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2339 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2340 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2341 FILE_ATTRIBUTE_NORMAL, 0);
2342 WINE_TRACE("No parms - working probably in pipe mode\n");
2343 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2345 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2346 once you get in this bit unless due to a pipe, its going to end badly... */
2347 useinput = TRUE;
2348 wsprintf(moreStrPage, moreFmt, moreStr);
2350 WCMD_enter_paged_mode(moreStrPage);
2351 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2352 if (count == 0) break; /* ReadFile reports success on EOF! */
2353 buffer[count] = 0;
2354 WCMD_output_asis (buffer);
2356 WCMD_leave_paged_mode();
2358 /* Restore stdin to what it was */
2359 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2360 CloseHandle(hConIn);
2362 return;
2363 } else {
2364 BOOL needsPause = FALSE;
2366 /* Loop through all args */
2367 WINE_TRACE("Parms supplied - working through each file\n");
2368 WCMD_enter_paged_mode(moreStrPage);
2370 while (argN) {
2371 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2372 HANDLE h;
2374 if (!argN) break;
2376 if (needsPause) {
2378 /* Wait */
2379 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2380 WCMD_leave_paged_mode();
2381 WCMD_output_asis(moreStrPage);
2382 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2383 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2384 WCMD_enter_paged_mode(moreStrPage);
2388 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2389 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2390 FILE_ATTRIBUTE_NORMAL, NULL);
2391 if (h == INVALID_HANDLE_VALUE) {
2392 WCMD_print_error ();
2393 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2394 errorlevel = 1;
2395 } else {
2396 ULONG64 curPos = 0;
2397 ULONG64 fileLen = 0;
2398 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2400 /* Get the file size */
2401 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2402 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2404 needsPause = TRUE;
2405 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2406 if (count == 0) break; /* ReadFile reports success on EOF! */
2407 buffer[count] = 0;
2408 curPos += count;
2410 /* Update % count (would be used in WCMD_output_asis as prompt) */
2411 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2413 WCMD_output_asis (buffer);
2415 CloseHandle (h);
2419 WCMD_leave_paged_mode();
2423 /****************************************************************************
2424 * WCMD_verify
2426 * Display verify flag.
2427 * FIXME: We don't actually do anything with the verify flag other than toggle
2428 * it...
2431 void WCMD_verify (WCHAR *command) {
2433 int count;
2435 count = strlenW(command);
2436 if (count == 0) {
2437 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2438 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2439 return;
2441 if (lstrcmpiW(command, onW) == 0) {
2442 verify_mode = 1;
2443 return;
2445 else if (lstrcmpiW(command, offW) == 0) {
2446 verify_mode = 0;
2447 return;
2449 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2452 /****************************************************************************
2453 * WCMD_version
2455 * Display version info.
2458 void WCMD_version (void) {
2460 WCMD_output (version_string);
2464 /****************************************************************************
2465 * WCMD_volume
2467 * Display volume info and/or set volume label. Returns 0 if error.
2470 int WCMD_volume (int mode, WCHAR *path) {
2472 DWORD count, serial;
2473 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2474 BOOL status;
2476 if (strlenW(path) == 0) {
2477 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2478 if (!status) {
2479 WCMD_print_error ();
2480 return 0;
2482 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2483 &serial, NULL, NULL, NULL, 0);
2485 else {
2486 static const WCHAR fmt[] = {'%','s','\\','\0'};
2487 if ((path[1] != ':') || (strlenW(path) != 2)) {
2488 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2489 return 0;
2491 wsprintf (curdir, fmt, path);
2492 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2493 &serial, NULL,
2494 NULL, NULL, 0);
2496 if (!status) {
2497 WCMD_print_error ();
2498 return 0;
2500 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2501 curdir[0], label, HIWORD(serial), LOWORD(serial));
2502 if (mode) {
2503 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2504 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2505 sizeof(string)/sizeof(WCHAR), &count, NULL);
2506 if (count > 1) {
2507 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2508 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2510 if (strlenW(path) != 0) {
2511 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2513 else {
2514 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2517 return 1;
2520 /**************************************************************************
2521 * WCMD_exit
2523 * Exit either the process, or just this batch program
2527 void WCMD_exit (CMD_LIST **cmdList) {
2529 static const WCHAR parmB[] = {'/','B','\0'};
2530 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2532 if (context && lstrcmpiW(quals, parmB) == 0) {
2533 errorlevel = rc;
2534 context -> skip_rest = TRUE;
2535 *cmdList = NULL;
2536 } else {
2537 ExitProcess(rc);
2542 /*****************************************************************************
2543 * WCMD_assoc
2545 * Lists or sets file associations (assoc = TRUE)
2546 * Lists or sets file types (assoc = FALSE)
2548 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2550 HKEY key;
2551 DWORD accessOptions = KEY_READ;
2552 WCHAR *newValue;
2553 LONG rc = ERROR_SUCCESS;
2554 WCHAR keyValue[MAXSTRING];
2555 DWORD valueLen = MAXSTRING;
2556 HKEY readKey;
2557 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2558 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2560 /* See if parameter includes '=' */
2561 errorlevel = 0;
2562 newValue = strchrW(command, '=');
2563 if (newValue) accessOptions |= KEY_WRITE;
2565 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2566 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2567 accessOptions, &key) != ERROR_SUCCESS) {
2568 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2569 return;
2572 /* If no parameters then list all associations */
2573 if (*command == 0x00) {
2574 int index = 0;
2576 /* Enumerate all the keys */
2577 while (rc != ERROR_NO_MORE_ITEMS) {
2578 WCHAR keyName[MAXSTRING];
2579 DWORD nameLen;
2581 /* Find the next value */
2582 nameLen = MAXSTRING;
2583 rc = RegEnumKeyEx(key, index++,
2584 keyName, &nameLen,
2585 NULL, NULL, NULL, NULL);
2587 if (rc == ERROR_SUCCESS) {
2589 /* Only interested in extension ones if assoc, or others
2590 if not assoc */
2591 if ((keyName[0] == '.' && assoc) ||
2592 (!(keyName[0] == '.') && (!assoc)))
2594 WCHAR subkey[MAXSTRING];
2595 strcpyW(subkey, keyName);
2596 if (!assoc) strcatW(subkey, shOpCmdW);
2598 if (RegOpenKeyEx(key, subkey, 0,
2599 accessOptions, &readKey) == ERROR_SUCCESS) {
2601 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2602 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2603 (LPBYTE)keyValue, &valueLen);
2604 WCMD_output_asis(keyName);
2605 WCMD_output_asis(equalW);
2606 /* If no default value found, leave line empty after '=' */
2607 if (rc == ERROR_SUCCESS) {
2608 WCMD_output_asis(keyValue);
2610 WCMD_output_asis(newline);
2611 RegCloseKey(readKey);
2617 } else {
2619 /* Parameter supplied - if no '=' on command line, its a query */
2620 if (newValue == NULL) {
2621 WCHAR *space;
2622 WCHAR subkey[MAXSTRING];
2624 /* Query terminates the parameter at the first space */
2625 strcpyW(keyValue, command);
2626 space = strchrW(keyValue, ' ');
2627 if (space) *space=0x00;
2629 /* Set up key name */
2630 strcpyW(subkey, keyValue);
2631 if (!assoc) strcatW(subkey, shOpCmdW);
2633 if (RegOpenKeyEx(key, subkey, 0,
2634 accessOptions, &readKey) == ERROR_SUCCESS) {
2636 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2637 (LPBYTE)keyValue, &valueLen);
2638 WCMD_output_asis(command);
2639 WCMD_output_asis(equalW);
2640 /* If no default value found, leave line empty after '=' */
2641 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2642 WCMD_output_asis(newline);
2643 RegCloseKey(readKey);
2645 } else {
2646 WCHAR msgbuffer[MAXSTRING];
2647 WCHAR outbuffer[MAXSTRING];
2649 /* Load the translated 'File association not found' */
2650 if (assoc) {
2651 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2652 } else {
2653 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2655 wsprintf(outbuffer, msgbuffer, keyValue);
2656 WCMD_output_asis(outbuffer);
2657 errorlevel = 2;
2660 /* Not a query - its a set or clear of a value */
2661 } else {
2663 WCHAR subkey[MAXSTRING];
2665 /* Get pointer to new value */
2666 *newValue = 0x00;
2667 newValue++;
2669 /* Set up key name */
2670 strcpyW(subkey, command);
2671 if (!assoc) strcatW(subkey, shOpCmdW);
2673 /* If nothing after '=' then clear value - only valid for ASSOC */
2674 if (*newValue == 0x00) {
2676 if (assoc) rc = RegDeleteKey(key, command);
2677 if (assoc && rc == ERROR_SUCCESS) {
2678 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2680 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2681 WCMD_print_error();
2682 errorlevel = 2;
2684 } else {
2685 WCHAR msgbuffer[MAXSTRING];
2686 WCHAR outbuffer[MAXSTRING];
2688 /* Load the translated 'File association not found' */
2689 if (assoc) {
2690 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2691 sizeof(msgbuffer)/sizeof(WCHAR));
2692 } else {
2693 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2694 sizeof(msgbuffer)/sizeof(WCHAR));
2696 wsprintf(outbuffer, msgbuffer, keyValue);
2697 WCMD_output_asis(outbuffer);
2698 errorlevel = 2;
2701 /* It really is a set value = contents */
2702 } else {
2703 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2704 accessOptions, NULL, &readKey, NULL);
2705 if (rc == ERROR_SUCCESS) {
2706 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2707 (LPBYTE)newValue, strlenW(newValue));
2708 RegCloseKey(readKey);
2711 if (rc != ERROR_SUCCESS) {
2712 WCMD_print_error();
2713 errorlevel = 2;
2714 } else {
2715 WCMD_output_asis(command);
2716 WCMD_output_asis(equalW);
2717 WCMD_output_asis(newValue);
2718 WCMD_output_asis(newline);
2724 /* Clean up */
2725 RegCloseKey(key);
2728 /****************************************************************************
2729 * WCMD_color
2731 * Clear the terminal screen.
2734 void WCMD_color (void) {
2736 /* Emulate by filling the screen from the top left to bottom right with
2737 spaces, then moving the cursor to the top left afterwards */
2738 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2739 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2741 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2742 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2743 return;
2746 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2748 COORD topLeft;
2749 DWORD screenSize;
2750 DWORD color = 0;
2752 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2754 topLeft.X = 0;
2755 topLeft.Y = 0;
2757 /* Convert the color hex digits */
2758 if (param1[0] == 0x00) {
2759 color = defaultColor;
2760 } else {
2761 color = strtoulW(param1, NULL, 16);
2764 /* Fail if fg == bg color */
2765 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2766 errorlevel = 1;
2767 return;
2770 /* Set the current screen contents and ensure all future writes
2771 remain this color */
2772 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2773 SetConsoleTextAttribute(hStdOut, color);