cmd: Add support for wildcards in copy.
[wine.git] / programs / cmd / builtins.c
blob9c34e8f6226f621688c5803d9c67bb2ca1ae1944
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * NOTES:
23 * On entry to each function, global variables quals, param1, param2 contain
24 * the qualifiers (uppercased and concatenated) and parameters entered, with
25 * environment-variable and batch parameter substitution already done.
29 * FIXME:
30 * - No support for pipes, shell parameters
31 * - Lots of functionality missing from builtins
32 * - Messages etc need international support
35 #define WIN32_LEAN_AND_MEAN
37 #include "wcmd.h"
38 #include <shellapi.h>
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
44 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
46 struct env_stack *saved_environment;
47 struct env_stack *pushd_directories;
49 extern HINSTANCE hinst;
50 extern WCHAR inbuilt[][10];
51 extern int echo_mode, verify_mode, defaultColor;
52 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
53 extern BATCH_CONTEXT *context;
54 extern DWORD errorlevel;
56 static const WCHAR dotW[] = {'.','\0'};
57 static const WCHAR dotdotW[] = {'.','.','\0'};
58 static const WCHAR slashW[] = {'\\','\0'};
59 static const WCHAR starW[] = {'*','\0'};
60 static const WCHAR equalW[] = {'=','\0'};
61 static const WCHAR fslashW[] = {'/','\0'};
62 static const WCHAR onW[] = {'O','N','\0'};
63 static const WCHAR offW[] = {'O','F','F','\0'};
64 static const WCHAR parmY[] = {'/','Y','\0'};
65 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
66 static const WCHAR nullW[] = {'\0'};
68 /****************************************************************************
69 * WCMD_clear_screen
71 * Clear the terminal screen.
74 void WCMD_clear_screen (void) {
76 /* Emulate by filling the screen from the top left to bottom right with
77 spaces, then moving the cursor to the top left afterwards */
78 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
79 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
81 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
83 COORD topLeft;
84 DWORD screenSize;
86 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
88 topLeft.X = 0;
89 topLeft.Y = 0;
90 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
91 SetConsoleCursorPosition(hStdOut, topLeft);
95 /****************************************************************************
96 * WCMD_change_tty
98 * Change the default i/o device (ie redirect STDin/STDout).
101 void WCMD_change_tty (void) {
103 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
107 /****************************************************************************
108 * WCMD_copy
110 * Copy a file or wildcarded set.
111 * FIXME: Add support for a+b+c type syntax
114 void WCMD_copy (void) {
116 WIN32_FIND_DATA fd;
117 HANDLE hff;
118 BOOL force, status;
119 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
120 DWORD len;
121 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122 BOOL copyToDir = FALSE;
123 BOOL copyFromDir = FALSE;
124 WCHAR srcspec[MAX_PATH];
125 DWORD attribs;
126 WCHAR drive[10];
127 WCHAR dir[MAX_PATH];
128 WCHAR fname[MAX_PATH];
129 WCHAR ext[MAX_PATH];
131 if (param1[0] == 0x00) {
132 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
133 return;
136 /* Convert source into full spec */
137 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
138 GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
139 if (srcpath[strlenW(srcpath) - 1] == '\\')
140 srcpath[strlenW(srcpath) - 1] = '\0';
142 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
143 attribs = GetFileAttributes(srcpath);
144 } else {
145 attribs = 0;
147 strcpyW(srcspec, srcpath);
149 /* If a directory, then add \* on the end when searching */
150 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
151 strcatW(srcpath, slashW);
152 copyFromDir = TRUE;
153 strcatW(srcspec, slashW);
154 strcatW(srcspec, starW);
155 } else {
156 WCMD_splitpath(srcpath, drive, dir, fname, ext);
157 strcpyW(srcpath, drive);
158 strcatW(srcpath, dir);
161 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
163 /* If no destination supplied, assume current directory */
164 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
165 if (param2[0] == 0x00) {
166 strcpyW(param2, dotW);
169 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
170 if (outpath[strlenW(outpath) - 1] == '\\')
171 outpath[strlenW(outpath) - 1] = '\0';
172 attribs = GetFileAttributes(outpath);
173 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
174 strcatW (outpath, slashW);
175 copyToDir = TRUE;
177 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
178 wine_dbgstr_w(outpath), copyToDir);
180 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
181 if (strstrW (quals, parmNoY))
182 force = FALSE;
183 else if (strstrW (quals, parmY))
184 force = TRUE;
185 else {
186 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
187 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
190 /* Loop through all source files */
191 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
192 hff = FindFirstFile (srcspec, &fd);
193 if (hff != INVALID_HANDLE_VALUE) {
194 do {
195 WCHAR outname[MAX_PATH];
196 WCHAR srcname[MAX_PATH];
197 BOOL overwrite = force;
199 /* Destination is either supplied filename, or source name in
200 supplied destination directory */
201 strcpyW(outname, outpath);
202 if (copyToDir) strcatW(outname, fd.cFileName);
203 strcpyW(srcname, srcpath);
204 strcatW(srcname, fd.cFileName);
206 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
207 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
209 /* Skip . and .., and directories */
210 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
211 overwrite = FALSE;
212 WINE_TRACE("Skipping directories\n");
215 /* Prompt before overwriting */
216 else if (!overwrite) {
217 attribs = GetFileAttributes(outname);
218 if (attribs != INVALID_FILE_ATTRIBUTES) {
219 WCHAR buffer[MAXSTRING];
220 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
221 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
223 else overwrite = TRUE;
226 /* Do the copy as appropriate */
227 if (overwrite) {
228 status = CopyFile (srcname, outname, FALSE);
229 if (!status) WCMD_print_error ();
232 } while (FindNextFile(hff, &fd) != 0);
233 FindClose (hff);
234 } else {
235 status = ERROR_FILE_NOT_FOUND;
236 WCMD_print_error ();
240 /****************************************************************************
241 * WCMD_create_dir
243 * Create a directory.
245 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
246 * they do not already exist.
249 static BOOL create_full_path(WCHAR* path)
251 int len;
252 WCHAR *new_path;
253 BOOL ret = TRUE;
255 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
256 strcpyW(new_path,path);
258 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
259 new_path[len - 1] = 0;
261 while (!CreateDirectory(new_path,NULL))
263 WCHAR *slash;
264 DWORD last_error = GetLastError();
265 if (last_error == ERROR_ALREADY_EXISTS)
266 break;
268 if (last_error != ERROR_PATH_NOT_FOUND)
270 ret = FALSE;
271 break;
274 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
276 ret = FALSE;
277 break;
280 len = slash - new_path;
281 new_path[len] = 0;
282 if (!create_full_path(new_path))
284 ret = FALSE;
285 break;
287 new_path[len] = '\\';
289 HeapFree(GetProcessHeap(),0,new_path);
290 return ret;
293 void WCMD_create_dir (void) {
295 if (param1[0] == 0x00) {
296 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
297 return;
299 if (!create_full_path(param1)) WCMD_print_error ();
302 /****************************************************************************
303 * WCMD_delete
305 * Delete a file or wildcarded set.
307 * Note on /A:
308 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
309 * - Each set is a pattern, eg /ahr /as-r means
310 * readonly+hidden OR nonreadonly system files
311 * - The '-' applies to a single field, ie /a:-hr means read only
312 * non-hidden files
315 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
317 int argno = 0;
318 int argsProcessed = 0;
319 WCHAR *argN = command;
320 BOOL foundAny = FALSE;
321 static const WCHAR parmA[] = {'/','A','\0'};
322 static const WCHAR parmQ[] = {'/','Q','\0'};
323 static const WCHAR parmP[] = {'/','P','\0'};
324 static const WCHAR parmS[] = {'/','S','\0'};
325 static const WCHAR parmF[] = {'/','F','\0'};
327 /* If not recursing, clear error flag */
328 if (expectDir) errorlevel = 0;
330 /* Loop through all args */
331 while (argN) {
332 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
333 WCHAR argCopy[MAX_PATH];
335 if (argN && argN[0] != '/') {
337 WIN32_FIND_DATA fd;
338 HANDLE hff;
339 WCHAR fpath[MAX_PATH];
340 WCHAR *p;
341 BOOL handleParm = TRUE;
342 BOOL found = FALSE;
343 static const WCHAR anyExt[]= {'.','*','\0'};
345 strcpyW(argCopy, thisArg);
346 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
347 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
348 argsProcessed++;
350 /* If filename part of parameter is * or *.*, prompt unless
351 /Q supplied. */
352 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
354 WCHAR drive[10];
355 WCHAR dir[MAX_PATH];
356 WCHAR fname[MAX_PATH];
357 WCHAR ext[MAX_PATH];
359 /* Convert path into actual directory spec */
360 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
361 WCMD_splitpath(fpath, drive, dir, fname, ext);
363 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
364 if ((strcmpW(fname, starW) == 0) &&
365 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
366 BOOL ok;
367 WCHAR question[MAXSTRING];
368 static const WCHAR fmt[] = {'%','s',' ','\0'};
370 /* Note: Flag as found, to avoid file not found message */
371 found = TRUE;
373 /* Ask for confirmation */
374 wsprintf(question, fmt, fpath);
375 ok = WCMD_ask_confirm(question, TRUE, NULL);
377 /* Abort if answer is 'N' */
378 if (!ok) continue;
382 /* First, try to delete in the current directory */
383 hff = FindFirstFile (argCopy, &fd);
384 if (hff == INVALID_HANDLE_VALUE) {
385 handleParm = FALSE;
386 } else {
387 found = TRUE;
390 /* Support del <dirname> by just deleting all files dirname\* */
391 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
392 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
393 WCHAR modifiedParm[MAX_PATH];
394 static const WCHAR slashStar[] = {'\\','*','\0'};
396 strcpyW(modifiedParm, argCopy);
397 strcatW(modifiedParm, slashStar);
398 FindClose(hff);
399 found = TRUE;
400 WCMD_delete(modifiedParm, FALSE);
402 } else if (handleParm) {
404 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
405 strcpyW (fpath, argCopy);
406 do {
407 p = strrchrW (fpath, '\\');
408 if (p != NULL) {
409 *++p = '\0';
410 strcatW (fpath, fd.cFileName);
412 else strcpyW (fpath, fd.cFileName);
413 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
414 BOOL ok = TRUE;
415 WCHAR *nextA = strstrW (quals, parmA);
417 /* Handle attribute matching (/A) */
418 if (nextA != NULL) {
419 ok = FALSE;
420 while (nextA != NULL && !ok) {
422 WCHAR *thisA = (nextA+2);
423 BOOL stillOK = TRUE;
425 /* Skip optional : */
426 if (*thisA == ':') thisA++;
428 /* Parse each of the /A[:]xxx in turn */
429 while (*thisA && *thisA != '/') {
430 BOOL negate = FALSE;
431 BOOL attribute = FALSE;
433 /* Match negation of attribute first */
434 if (*thisA == '-') {
435 negate=TRUE;
436 thisA++;
439 /* Match attribute */
440 switch (*thisA) {
441 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
442 break;
443 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
444 break;
445 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
446 break;
447 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
448 break;
449 default:
450 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
453 /* Now check result, keeping a running boolean about whether it
454 matches all parsed attribues so far */
455 if (attribute && !negate) {
456 stillOK = stillOK;
457 } else if (!attribute && negate) {
458 stillOK = stillOK;
459 } else {
460 stillOK = FALSE;
462 thisA++;
465 /* Save the running total as the final result */
466 ok = stillOK;
468 /* Step on to next /A set */
469 nextA = strstrW (nextA+1, parmA);
473 /* /P means prompt for each file */
474 if (ok && strstrW (quals, parmP) != NULL) {
475 WCHAR question[MAXSTRING];
477 /* Ask for confirmation */
478 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
479 ok = WCMD_ask_confirm(question, FALSE, NULL);
482 /* Only proceed if ok to */
483 if (ok) {
485 /* If file is read only, and /F supplied, delete it */
486 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
487 strstrW (quals, parmF) != NULL) {
488 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
491 /* Now do the delete */
492 if (!DeleteFile (fpath)) WCMD_print_error ();
496 } while (FindNextFile(hff, &fd) != 0);
497 FindClose (hff);
500 /* Now recurse into all subdirectories handling the parameter in the same way */
501 if (strstrW (quals, parmS) != NULL) {
503 WCHAR thisDir[MAX_PATH];
504 int cPos;
506 WCHAR drive[10];
507 WCHAR dir[MAX_PATH];
508 WCHAR fname[MAX_PATH];
509 WCHAR ext[MAX_PATH];
511 /* Convert path into actual directory spec */
512 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
513 WCMD_splitpath(thisDir, drive, dir, fname, ext);
515 strcpyW(thisDir, drive);
516 strcatW(thisDir, dir);
517 cPos = strlenW(thisDir);
519 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
521 /* Append '*' to the directory */
522 thisDir[cPos] = '*';
523 thisDir[cPos+1] = 0x00;
525 hff = FindFirstFile (thisDir, &fd);
527 /* Remove residual '*' */
528 thisDir[cPos] = 0x00;
530 if (hff != INVALID_HANDLE_VALUE) {
531 DIRECTORY_STACK *allDirs = NULL;
532 DIRECTORY_STACK *lastEntry = NULL;
534 do {
535 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
536 (strcmpW(fd.cFileName, dotdotW) != 0) &&
537 (strcmpW(fd.cFileName, dotW) != 0)) {
539 DIRECTORY_STACK *nextDir;
540 WCHAR subParm[MAX_PATH];
542 /* Work out search parameter in sub dir */
543 strcpyW (subParm, thisDir);
544 strcatW (subParm, fd.cFileName);
545 strcatW (subParm, slashW);
546 strcatW (subParm, fname);
547 strcatW (subParm, ext);
548 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
550 /* Allocate memory, add to list */
551 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
552 if (allDirs == NULL) allDirs = nextDir;
553 if (lastEntry != NULL) lastEntry->next = nextDir;
554 lastEntry = nextDir;
555 nextDir->next = NULL;
556 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
557 (strlenW(subParm)+1) * sizeof(WCHAR));
558 strcpyW(nextDir->dirName, subParm);
560 } while (FindNextFile(hff, &fd) != 0);
561 FindClose (hff);
563 /* Go through each subdir doing the delete */
564 while (allDirs != NULL) {
565 DIRECTORY_STACK *tempDir;
567 tempDir = allDirs->next;
568 found |= WCMD_delete (allDirs->dirName, FALSE);
570 HeapFree(GetProcessHeap(),0,allDirs->dirName);
571 HeapFree(GetProcessHeap(),0,allDirs);
572 allDirs = tempDir;
576 /* Keep running total to see if any found, and if not recursing
577 issue error message */
578 if (expectDir) {
579 if (!found) {
580 errorlevel = 1;
581 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
584 foundAny |= found;
588 /* Handle no valid args */
589 if (argsProcessed == 0) {
590 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
593 return foundAny;
596 /****************************************************************************
597 * WCMD_echo
599 * Echo input to the screen (or not). We don't try to emulate the bugs
600 * in DOS (try typing "ECHO ON AGAIN" for an example).
603 void WCMD_echo (const WCHAR *command) {
605 int count;
607 if ((command[0] == '.') && (command[1] == 0)) {
608 WCMD_output (newline);
609 return;
611 if (command[0]==' ')
612 command++;
613 count = strlenW(command);
614 if (count == 0) {
615 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
616 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
617 return;
619 if (lstrcmpiW(command, onW) == 0) {
620 echo_mode = 1;
621 return;
623 if (lstrcmpiW(command, offW) == 0) {
624 echo_mode = 0;
625 return;
627 WCMD_output_asis (command);
628 WCMD_output (newline);
632 /**************************************************************************
633 * WCMD_for
635 * Batch file loop processing.
637 * On entry: cmdList contains the syntax up to the set
638 * next cmdList and all in that bracket contain the set data
639 * next cmdlist contains the DO cmd
640 * following that is either brackets or && entries (as per if)
642 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
643 * will probably work here, but the reverse is not necessarily the case...
646 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
648 WIN32_FIND_DATA fd;
649 HANDLE hff;
650 int i;
651 const WCHAR inW[] = {'i', 'n', '\0'};
652 const WCHAR doW[] = {'d', 'o', ' ','\0'};
653 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
654 WCHAR variable[4];
655 WCHAR *firstCmd;
656 int thisDepth;
657 BOOL isDirs = FALSE;
659 /* Check:
660 the first line includes the % variable name as first parm
661 we have been provided with more parts to the command
662 and there is at least some set data
663 and IN as the one after that */
664 if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
665 || (*cmdList) == NULL
666 || (*cmdList)->nextcommand == NULL
667 || (param1[0] != '%')
668 || (strlenW(param1) > 3)) {
669 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
670 return;
673 /* Save away where the set of data starts and the variable */
674 strcpyW(variable, param1);
675 thisDepth = (*cmdList)->bracketDepth;
676 *cmdList = (*cmdList)->nextcommand;
677 setStart = (*cmdList);
679 /* Skip until the close bracket */
680 WINE_TRACE("Searching %p as the set\n", *cmdList);
681 while (*cmdList &&
682 (*cmdList)->command != NULL &&
683 (*cmdList)->bracketDepth > thisDepth) {
684 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
685 *cmdList = (*cmdList)->nextcommand;
688 /* Skip the close bracket, if there is one */
689 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
691 /* Syntax error if missing close bracket, or nothing following it
692 and once we have the complete set, we expect a DO */
693 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
694 if ((*cmdList == NULL) ||
695 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
696 (*cmdList)->command, 3, doW, -1) != 2)) {
697 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
698 return;
701 /* Save away the starting position for the commands (and offset for the
702 first one */
703 cmdStart = *cmdList;
704 cmdEnd = *cmdList;
705 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
707 thisSet = setStart;
708 /* Loop through all set entries */
709 while (thisSet &&
710 thisSet->command != NULL &&
711 thisSet->bracketDepth >= thisDepth) {
713 /* Loop through all entries on the same line */
714 WCHAR *item;
716 WINE_TRACE("Processing for set %p\n", thisSet);
717 i = 0;
718 while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
721 * If the parameter within the set has a wildcard then search for matching files
722 * otherwise do a literal substitution.
724 static const WCHAR wildcards[] = {'*','?','\0'};
725 CMD_LIST *thisCmdStart = cmdStart;
727 WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item));
728 if (strpbrkW (item, wildcards)) {
729 hff = FindFirstFile (item, &fd);
730 if (hff != INVALID_HANDLE_VALUE) {
731 do {
732 BOOL isDirectory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
733 if ((isDirs && isDirectory) ||
734 (!isDirs && !isDirectory))
736 thisCmdStart = cmdStart;
737 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
738 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
739 fd.cFileName, FALSE, TRUE);
742 } while (FindNextFile(hff, &fd) != 0);
743 FindClose (hff);
745 } else {
746 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
749 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
750 cmdEnd = thisCmdStart;
751 i++;
754 /* Move onto the next set line */
755 thisSet = thisSet->nextcommand;
758 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
759 all processing, OR it should be pointing to the end of && processing OR
760 it should be pointing at the NULL end of bracket for the DO. The return
761 value needs to be the NEXT command to execute, which it either is, or
762 we need to step over the closing bracket */
763 *cmdList = cmdEnd;
764 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
768 /*****************************************************************************
769 * WCMD_part_execute
771 * Execute a command, and any && or bracketed follow on to the command. The
772 * first command to be executed may not be at the front of the
773 * commands->thiscommand string (eg. it may point after a DO or ELSE
774 * Returns TRUE if something like exit or goto has aborted all processing
776 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
777 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
779 CMD_LIST *curPosition = *cmdList;
780 int myDepth = (*cmdList)->bracketDepth;
782 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
783 cmdList, wine_dbgstr_w(firstcmd),
784 wine_dbgstr_w(variable), wine_dbgstr_w(value),
785 conditionTRUE);
787 /* Skip leading whitespace between condition and the command */
788 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
790 /* Process the first command, if there is one */
791 if (conditionTRUE && firstcmd && *firstcmd) {
792 WCHAR *command = WCMD_strdupW(firstcmd);
793 WCMD_execute (firstcmd, variable, value, cmdList);
794 free (command);
798 /* If it didn't move the position, step to next command */
799 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
801 /* Process any other parts of the command */
802 if (*cmdList) {
803 BOOL processThese = TRUE;
805 if (isIF) processThese = conditionTRUE;
807 while (*cmdList) {
808 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
810 /* execute all appropriate commands */
811 curPosition = *cmdList;
813 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
814 *cmdList,
815 (*cmdList)->isAmphersand,
816 (*cmdList)->bracketDepth, myDepth);
818 /* Execute any appended to the statement with &&'s */
819 if ((*cmdList)->isAmphersand) {
820 if (processThese) {
821 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
823 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
825 /* Execute any appended to the statement with (...) */
826 } else if ((*cmdList)->bracketDepth > myDepth) {
827 if (processThese) {
828 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
829 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
831 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
833 /* End of the command - does 'ELSE ' follow as the next command? */
834 } else {
835 if (isIF && CompareString (LOCALE_USER_DEFAULT,
836 NORM_IGNORECASE | SORT_STRINGSORT,
837 (*cmdList)->command, 5, ifElse, -1) == 2) {
839 /* Swap between if and else processing */
840 processThese = !processThese;
842 /* Process the ELSE part */
843 if (processThese) {
844 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
846 /* Skip leading whitespace between condition and the command */
847 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
848 if (*cmd) {
849 WCMD_execute (cmd, variable, value, cmdList);
852 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
853 } else {
854 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
855 break;
860 return;
863 /*****************************************************************************
864 * WCMD_Execute
866 * Execute a command after substituting variable text for the supplied parameter
869 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
871 WCHAR *new_cmd, *p, *s, *dup;
872 int size;
874 if (param) {
875 size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
876 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
877 dup = s = WCMD_strdupW(orig_cmd);
879 while ((p = strstrW (s, param))) {
880 *p = '\0';
881 size += strlenW (subst) * sizeof(WCHAR);
882 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
883 strcatW (new_cmd, s);
884 strcatW (new_cmd, subst);
885 s = p + strlenW (param);
887 strcatW (new_cmd, s);
888 WCMD_process_command (new_cmd, cmdList);
889 free (dup);
890 LocalFree ((HANDLE)new_cmd);
891 } else {
892 WCMD_process_command (orig_cmd, cmdList);
897 /**************************************************************************
898 * WCMD_give_help
900 * Simple on-line help. Help text is stored in the resource file.
903 void WCMD_give_help (WCHAR *command) {
905 int i;
907 command = WCMD_strtrim_leading_spaces(command);
908 if (strlenW(command) == 0) {
909 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
911 else {
912 for (i=0; i<=WCMD_EXIT; i++) {
913 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
914 param1, -1, inbuilt[i], -1) == 2) {
915 WCMD_output_asis (WCMD_LoadMessage(i));
916 return;
919 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
921 return;
924 /****************************************************************************
925 * WCMD_go_to
927 * Batch file jump instruction. Not the most efficient algorithm ;-)
928 * Prints error message if the specified label cannot be found - the file pointer is
929 * then at EOF, effectively stopping the batch file.
930 * FIXME: DOS is supposed to allow labels with spaces - we don't.
933 void WCMD_goto (CMD_LIST **cmdList) {
935 WCHAR string[MAX_PATH];
937 /* Do not process any more parts of a processed multipart or multilines command */
938 *cmdList = NULL;
940 if (param1[0] == 0x00) {
941 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
942 return;
944 if (context != NULL) {
945 WCHAR *paramStart = param1;
946 static const WCHAR eofW[] = {':','e','o','f','\0'};
948 /* Handle special :EOF label */
949 if (lstrcmpiW (eofW, param1) == 0) {
950 context -> skip_rest = TRUE;
951 return;
954 /* Support goto :label as well as goto label */
955 if (*paramStart == ':') paramStart++;
957 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
958 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
959 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
961 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
963 return;
966 /*****************************************************************************
967 * WCMD_pushd
969 * Push a directory onto the stack
972 void WCMD_pushd (WCHAR *command) {
973 struct env_stack *curdir;
974 WCHAR *thisdir;
975 static const WCHAR parmD[] = {'/','D','\0'};
977 if (strchrW(command, '/') != NULL) {
978 SetLastError(ERROR_INVALID_PARAMETER);
979 WCMD_print_error();
980 return;
983 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
984 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
985 if( !curdir || !thisdir ) {
986 LocalFree(curdir);
987 LocalFree(thisdir);
988 WINE_ERR ("out of memory\n");
989 return;
992 /* Change directory using CD code with /D parameter */
993 strcpyW(quals, parmD);
994 GetCurrentDirectoryW (1024, thisdir);
995 errorlevel = 0;
996 WCMD_setshow_default(command);
997 if (errorlevel) {
998 LocalFree(curdir);
999 LocalFree(thisdir);
1000 return;
1001 } else {
1002 curdir -> next = pushd_directories;
1003 curdir -> strings = thisdir;
1004 if (pushd_directories == NULL) {
1005 curdir -> u.stackdepth = 1;
1006 } else {
1007 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1009 pushd_directories = curdir;
1014 /*****************************************************************************
1015 * WCMD_popd
1017 * Pop a directory from the stack
1020 void WCMD_popd (void) {
1021 struct env_stack *temp = pushd_directories;
1023 if (!pushd_directories)
1024 return;
1026 /* pop the old environment from the stack, and make it the current dir */
1027 pushd_directories = temp->next;
1028 SetCurrentDirectoryW(temp->strings);
1029 LocalFree (temp->strings);
1030 LocalFree (temp);
1033 /****************************************************************************
1034 * WCMD_if
1036 * Batch file conditional.
1038 * On entry, cmdlist will point to command containing the IF, and optionally
1039 * the first command to execute (if brackets not found)
1040 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1041 * If ('s were found, execute all within that bracket
1042 * Command may optionally be followed by an ELSE - need to skip instructions
1043 * in the else using the same logic
1045 * FIXME: Much more syntax checking needed!
1048 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1050 int negate = 0, test = 0;
1051 WCHAR condition[MAX_PATH], *command, *s;
1052 static const WCHAR notW[] = {'n','o','t','\0'};
1053 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1054 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1055 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1056 static const WCHAR eqeqW[] = {'=','=','\0'};
1058 if (!lstrcmpiW (param1, notW)) {
1059 negate = 1;
1060 strcpyW (condition, param2);
1062 else {
1063 strcpyW (condition, param1);
1065 if (!lstrcmpiW (condition, errlvlW)) {
1066 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1067 WCMD_parameter (p, 2+negate, &command);
1069 else if (!lstrcmpiW (condition, existW)) {
1070 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1071 test = 1;
1073 WCMD_parameter (p, 2+negate, &command);
1075 else if (!lstrcmpiW (condition, defdW)) {
1076 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1077 test = 1;
1079 WCMD_parameter (p, 2+negate, &command);
1081 else if ((s = strstrW (p, eqeqW))) {
1082 s += 2;
1083 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1084 WCMD_parameter (s, 1, &command);
1086 else {
1087 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1088 return;
1091 /* Process rest of IF statement which is on the same line
1092 Note: This may process all or some of the cmdList (eg a GOTO) */
1093 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1096 /****************************************************************************
1097 * WCMD_move
1099 * Move a file, directory tree or wildcarded set of files.
1102 void WCMD_move (void) {
1104 int status;
1105 WIN32_FIND_DATA fd;
1106 HANDLE hff;
1107 WCHAR input[MAX_PATH];
1108 WCHAR output[MAX_PATH];
1109 WCHAR drive[10];
1110 WCHAR dir[MAX_PATH];
1111 WCHAR fname[MAX_PATH];
1112 WCHAR ext[MAX_PATH];
1114 if (param1[0] == 0x00) {
1115 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1116 return;
1119 /* If no destination supplied, assume current directory */
1120 if (param2[0] == 0x00) {
1121 strcpyW(param2, dotW);
1124 /* If 2nd parm is directory, then use original filename */
1125 /* Convert partial path to full path */
1126 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1127 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1128 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1129 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1131 /* Split into components */
1132 WCMD_splitpath(input, drive, dir, fname, ext);
1134 hff = FindFirstFile (input, &fd);
1135 while (hff != INVALID_HANDLE_VALUE) {
1136 WCHAR dest[MAX_PATH];
1137 WCHAR src[MAX_PATH];
1138 DWORD attribs;
1140 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1142 /* Build src & dest name */
1143 strcpyW(src, drive);
1144 strcatW(src, dir);
1146 /* See if dest is an existing directory */
1147 attribs = GetFileAttributes(output);
1148 if (attribs != INVALID_FILE_ATTRIBUTES &&
1149 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1150 strcpyW(dest, output);
1151 strcatW(dest, slashW);
1152 strcatW(dest, fd.cFileName);
1153 } else {
1154 strcpyW(dest, output);
1157 strcatW(src, fd.cFileName);
1159 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1160 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1162 /* Check if file is read only, otherwise move it */
1163 attribs = GetFileAttributes(src);
1164 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1165 (attribs & FILE_ATTRIBUTE_READONLY)) {
1166 SetLastError(ERROR_ACCESS_DENIED);
1167 status = 0;
1168 } else {
1169 BOOL ok = TRUE;
1171 /* If destination exists, prompt unless /Y supplied */
1172 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1173 BOOL force = FALSE;
1174 WCHAR copycmd[MAXSTRING];
1175 int len;
1177 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1178 if (strstrW (quals, parmNoY))
1179 force = FALSE;
1180 else if (strstrW (quals, parmY))
1181 force = TRUE;
1182 else {
1183 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1184 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1185 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1186 && ! lstrcmpiW (copycmd, parmY));
1189 /* Prompt if overwriting */
1190 if (!force) {
1191 WCHAR question[MAXSTRING];
1192 WCHAR yesChar[10];
1194 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1196 /* Ask for confirmation */
1197 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1198 ok = WCMD_ask_confirm(question, FALSE, NULL);
1200 /* So delete the destination prior to the move */
1201 if (ok) {
1202 if (!DeleteFile (dest)) {
1203 WCMD_print_error ();
1204 errorlevel = 1;
1205 ok = FALSE;
1211 if (ok) {
1212 status = MoveFile (src, dest);
1213 } else {
1214 status = 1; /* Anything other than 0 to prevent error msg below */
1218 if (!status) {
1219 WCMD_print_error ();
1220 errorlevel = 1;
1223 /* Step on to next match */
1224 if (FindNextFile(hff, &fd) == 0) {
1225 FindClose(hff);
1226 hff = INVALID_HANDLE_VALUE;
1227 break;
1232 /****************************************************************************
1233 * WCMD_pause
1235 * Wait for keyboard input.
1238 void WCMD_pause (void) {
1240 DWORD count;
1241 WCHAR string[32];
1243 WCMD_output (anykey);
1244 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1245 sizeof(string)/sizeof(WCHAR), &count, NULL);
1248 /****************************************************************************
1249 * WCMD_remove_dir
1251 * Delete a directory.
1254 void WCMD_remove_dir (WCHAR *command) {
1256 int argno = 0;
1257 int argsProcessed = 0;
1258 WCHAR *argN = command;
1259 static const WCHAR parmS[] = {'/','S','\0'};
1260 static const WCHAR parmQ[] = {'/','Q','\0'};
1262 /* Loop through all args */
1263 while (argN) {
1264 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1265 if (argN && argN[0] != '/') {
1266 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1267 wine_dbgstr_w(quals));
1268 argsProcessed++;
1270 /* If subdirectory search not supplied, just try to remove
1271 and report error if it fails (eg if it contains a file) */
1272 if (strstrW (quals, parmS) == NULL) {
1273 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1275 /* Otherwise use ShFileOp to recursively remove a directory */
1276 } else {
1278 SHFILEOPSTRUCT lpDir;
1280 /* Ask first */
1281 if (strstrW (quals, parmQ) == NULL) {
1282 BOOL ok;
1283 WCHAR question[MAXSTRING];
1284 static const WCHAR fmt[] = {'%','s',' ','\0'};
1286 /* Ask for confirmation */
1287 wsprintf(question, fmt, thisArg);
1288 ok = WCMD_ask_confirm(question, TRUE, NULL);
1290 /* Abort if answer is 'N' */
1291 if (!ok) return;
1294 /* Do the delete */
1295 lpDir.hwnd = NULL;
1296 lpDir.pTo = NULL;
1297 lpDir.pFrom = thisArg;
1298 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1299 lpDir.wFunc = FO_DELETE;
1300 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1305 /* Handle no valid args */
1306 if (argsProcessed == 0) {
1307 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1308 return;
1313 /****************************************************************************
1314 * WCMD_rename
1316 * Rename a file.
1319 void WCMD_rename (void) {
1321 int status;
1322 HANDLE hff;
1323 WIN32_FIND_DATA fd;
1324 WCHAR input[MAX_PATH];
1325 WCHAR *dotDst = NULL;
1326 WCHAR drive[10];
1327 WCHAR dir[MAX_PATH];
1328 WCHAR fname[MAX_PATH];
1329 WCHAR ext[MAX_PATH];
1330 DWORD attribs;
1332 errorlevel = 0;
1334 /* Must be at least two args */
1335 if (param1[0] == 0x00 || param2[0] == 0x00) {
1336 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1337 errorlevel = 1;
1338 return;
1341 /* Destination cannot contain a drive letter or directory separator */
1342 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1343 SetLastError(ERROR_INVALID_PARAMETER);
1344 WCMD_print_error();
1345 errorlevel = 1;
1346 return;
1349 /* Convert partial path to full path */
1350 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1351 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1352 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1353 dotDst = strchrW(param2, '.');
1355 /* Split into components */
1356 WCMD_splitpath(input, drive, dir, fname, ext);
1358 hff = FindFirstFile (input, &fd);
1359 while (hff != INVALID_HANDLE_VALUE) {
1360 WCHAR dest[MAX_PATH];
1361 WCHAR src[MAX_PATH];
1362 WCHAR *dotSrc = NULL;
1363 int dirLen;
1365 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1367 /* FIXME: If dest name or extension is *, replace with filename/ext
1368 part otherwise use supplied name. This supports:
1369 ren *.fred *.jim
1370 ren jim.* fred.* etc
1371 However, windows has a more complex algorithum supporting eg
1372 ?'s and *'s mid name */
1373 dotSrc = strchrW(fd.cFileName, '.');
1375 /* Build src & dest name */
1376 strcpyW(src, drive);
1377 strcatW(src, dir);
1378 strcpyW(dest, src);
1379 dirLen = strlenW(src);
1380 strcatW(src, fd.cFileName);
1382 /* Build name */
1383 if (param2[0] == '*') {
1384 strcatW(dest, fd.cFileName);
1385 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1386 } else {
1387 strcatW(dest, param2);
1388 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1391 /* Build Extension */
1392 if (dotDst && (*(dotDst+1)=='*')) {
1393 if (dotSrc) strcatW(dest, dotSrc);
1394 } else if (dotDst) {
1395 if (dotDst) strcatW(dest, dotDst);
1398 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1399 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1401 /* Check if file is read only, otherwise move it */
1402 attribs = GetFileAttributes(src);
1403 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1404 (attribs & FILE_ATTRIBUTE_READONLY)) {
1405 SetLastError(ERROR_ACCESS_DENIED);
1406 status = 0;
1407 } else {
1408 status = MoveFile (src, dest);
1411 if (!status) {
1412 WCMD_print_error ();
1413 errorlevel = 1;
1416 /* Step on to next match */
1417 if (FindNextFile(hff, &fd) == 0) {
1418 FindClose(hff);
1419 hff = INVALID_HANDLE_VALUE;
1420 break;
1425 /*****************************************************************************
1426 * WCMD_dupenv
1428 * Make a copy of the environment.
1430 static WCHAR *WCMD_dupenv( const WCHAR *env )
1432 WCHAR *env_copy;
1433 int len;
1435 if( !env )
1436 return NULL;
1438 len = 0;
1439 while ( env[len] )
1440 len += (strlenW(&env[len]) + 1);
1442 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1443 if (!env_copy)
1445 WINE_ERR("out of memory\n");
1446 return env_copy;
1448 memcpy (env_copy, env, len*sizeof (WCHAR));
1449 env_copy[len] = 0;
1451 return env_copy;
1454 /*****************************************************************************
1455 * WCMD_setlocal
1457 * setlocal pushes the environment onto a stack
1458 * Save the environment as unicode so we don't screw anything up.
1460 void WCMD_setlocal (const WCHAR *s) {
1461 WCHAR *env;
1462 struct env_stack *env_copy;
1463 WCHAR cwd[MAX_PATH];
1465 /* DISABLEEXTENSIONS ignored */
1467 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1468 if( !env_copy )
1470 WINE_ERR ("out of memory\n");
1471 return;
1474 env = GetEnvironmentStringsW ();
1476 env_copy->strings = WCMD_dupenv (env);
1477 if (env_copy->strings)
1479 env_copy->next = saved_environment;
1480 saved_environment = env_copy;
1482 /* Save the current drive letter */
1483 GetCurrentDirectory (MAX_PATH, cwd);
1484 env_copy->u.cwd = cwd[0];
1486 else
1487 LocalFree (env_copy);
1489 FreeEnvironmentStringsW (env);
1493 /*****************************************************************************
1494 * WCMD_endlocal
1496 * endlocal pops the environment off a stack
1497 * Note: When searching for '=', search from WCHAR position 1, to handle
1498 * special internal environment variables =C:, =D: etc
1500 void WCMD_endlocal (void) {
1501 WCHAR *env, *old, *p;
1502 struct env_stack *temp;
1503 int len, n;
1505 if (!saved_environment)
1506 return;
1508 /* pop the old environment from the stack */
1509 temp = saved_environment;
1510 saved_environment = temp->next;
1512 /* delete the current environment, totally */
1513 env = GetEnvironmentStringsW ();
1514 old = WCMD_dupenv (GetEnvironmentStringsW ());
1515 len = 0;
1516 while (old[len]) {
1517 n = strlenW(&old[len]) + 1;
1518 p = strchrW(&old[len] + 1, '=');
1519 if (p)
1521 *p++ = 0;
1522 SetEnvironmentVariableW (&old[len], NULL);
1524 len += n;
1526 LocalFree (old);
1527 FreeEnvironmentStringsW (env);
1529 /* restore old environment */
1530 env = temp->strings;
1531 len = 0;
1532 while (env[len]) {
1533 n = strlenW(&env[len]) + 1;
1534 p = strchrW(&env[len] + 1, '=');
1535 if (p)
1537 *p++ = 0;
1538 SetEnvironmentVariableW (&env[len], p);
1540 len += n;
1543 /* Restore current drive letter */
1544 if (IsCharAlpha(temp->u.cwd)) {
1545 WCHAR envvar[4];
1546 WCHAR cwd[MAX_PATH];
1547 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1549 wsprintf(envvar, fmt, temp->u.cwd);
1550 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1551 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1552 SetCurrentDirectory(cwd);
1556 LocalFree (env);
1557 LocalFree (temp);
1560 /*****************************************************************************
1561 * WCMD_setshow_attrib
1563 * Display and optionally sets DOS attributes on a file or directory
1565 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1566 * As a result only the Readonly flag is correctly reported, the Archive bit
1567 * is always set and the rest are not implemented. We do the Right Thing anyway.
1569 * FIXME: No SET functionality.
1573 void WCMD_setshow_attrib (void) {
1575 DWORD count;
1576 HANDLE hff;
1577 WIN32_FIND_DATA fd;
1578 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1580 if (param1[0] == '-') {
1581 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1582 return;
1585 if (strlenW(param1) == 0) {
1586 static const WCHAR slashStarW[] = {'\\','*','\0'};
1588 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1589 strcatW (param1, slashStarW);
1592 hff = FindFirstFile (param1, &fd);
1593 if (hff == INVALID_HANDLE_VALUE) {
1594 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1596 else {
1597 do {
1598 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1599 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1600 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1601 flags[0] = 'H';
1603 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1604 flags[1] = 'S';
1606 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1607 flags[2] = 'A';
1609 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1610 flags[3] = 'R';
1612 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1613 flags[4] = 'T';
1615 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1616 flags[5] = 'C';
1618 WCMD_output (fmt, flags, fd.cFileName);
1619 for (count=0; count < 8; count++) flags[count] = ' ';
1621 } while (FindNextFile(hff, &fd) != 0);
1623 FindClose (hff);
1626 /*****************************************************************************
1627 * WCMD_setshow_default
1629 * Set/Show the current default directory
1632 void WCMD_setshow_default (WCHAR *command) {
1634 BOOL status;
1635 WCHAR string[1024];
1636 WCHAR cwd[1024];
1637 WCHAR *pos;
1638 WIN32_FIND_DATA fd;
1639 HANDLE hff;
1640 static const WCHAR parmD[] = {'/','D','\0'};
1642 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1644 /* Skip /D and trailing whitespace if on the front of the command line */
1645 if (CompareString (LOCALE_USER_DEFAULT,
1646 NORM_IGNORECASE | SORT_STRINGSORT,
1647 command, 2, parmD, -1) == 2) {
1648 command += 2;
1649 while (*command && *command==' ') command++;
1652 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1653 if (strlenW(command) == 0) {
1654 strcatW (cwd, newline);
1655 WCMD_output (cwd);
1657 else {
1658 /* Remove any double quotes, which may be in the
1659 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1660 pos = string;
1661 while (*command) {
1662 if (*command != '"') *pos++ = *command;
1663 command++;
1665 *pos = 0x00;
1667 /* Search for approprate directory */
1668 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1669 hff = FindFirstFile (string, &fd);
1670 while (hff != INVALID_HANDLE_VALUE) {
1671 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1672 WCHAR fpath[MAX_PATH];
1673 WCHAR drive[10];
1674 WCHAR dir[MAX_PATH];
1675 WCHAR fname[MAX_PATH];
1676 WCHAR ext[MAX_PATH];
1677 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1679 /* Convert path into actual directory spec */
1680 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1681 WCMD_splitpath(fpath, drive, dir, fname, ext);
1683 /* Rebuild path */
1684 wsprintf(string, fmt, drive, dir, fd.cFileName);
1686 FindClose(hff);
1687 hff = INVALID_HANDLE_VALUE;
1688 break;
1691 /* Step on to next match */
1692 if (FindNextFile(hff, &fd) == 0) {
1693 FindClose(hff);
1694 hff = INVALID_HANDLE_VALUE;
1695 break;
1699 /* Change to that directory */
1700 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1702 status = SetCurrentDirectory (string);
1703 if (!status) {
1704 errorlevel = 1;
1705 WCMD_print_error ();
1706 return;
1707 } else {
1709 /* Restore old directory if drive letter would change, and
1710 CD x:\directory /D (or pushd c:\directory) not supplied */
1711 if ((strstrW(quals, parmD) == NULL) &&
1712 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1713 SetCurrentDirectory(cwd);
1717 /* Set special =C: type environment variable, for drive letter of
1718 change of directory, even if path was restored due to missing
1719 /D (allows changing drive letter when not resident on that
1720 drive */
1721 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1722 WCHAR env[4];
1723 strcpyW(env, equalW);
1724 memcpy(env+1, string, 2 * sizeof(WCHAR));
1725 env[3] = 0x00;
1726 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1727 SetEnvironmentVariable(env, string);
1731 return;
1734 /****************************************************************************
1735 * WCMD_setshow_date
1737 * Set/Show the system date
1738 * FIXME: Can't change date yet
1741 void WCMD_setshow_date (void) {
1743 WCHAR curdate[64], buffer[64];
1744 DWORD count;
1745 static const WCHAR parmT[] = {'/','T','\0'};
1747 if (strlenW(param1) == 0) {
1748 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1749 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1750 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1751 if (strstrW (quals, parmT) == NULL) {
1752 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1753 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1754 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1755 if (count > 2) {
1756 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1760 else WCMD_print_error ();
1762 else {
1763 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1767 /****************************************************************************
1768 * WCMD_compare
1770 static int WCMD_compare( const void *a, const void *b )
1772 int r;
1773 const WCHAR * const *str_a = a, * const *str_b = b;
1774 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1775 *str_a, -1, *str_b, -1 );
1776 if( r == CSTR_LESS_THAN ) return -1;
1777 if( r == CSTR_GREATER_THAN ) return 1;
1778 return 0;
1781 /****************************************************************************
1782 * WCMD_setshow_sortenv
1784 * sort variables into order for display
1785 * Optionally only display those who start with a stub
1786 * returns the count displayed
1788 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1790 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1791 const WCHAR **str;
1793 if (stub) stublen = strlenW(stub);
1795 /* count the number of strings, and the total length */
1796 while ( s[len] ) {
1797 len += (strlenW(&s[len]) + 1);
1798 count++;
1801 /* add the strings to an array */
1802 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1803 if( !str )
1804 return 0;
1805 str[0] = s;
1806 for( i=1; i<count; i++ )
1807 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1809 /* sort the array */
1810 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1812 /* print it */
1813 for( i=0; i<count; i++ ) {
1814 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1815 NORM_IGNORECASE | SORT_STRINGSORT,
1816 str[i], stublen, stub, -1) == 2) {
1817 /* Don't display special internal variables */
1818 if (str[i][0] != '=') {
1819 WCMD_output_asis(str[i]);
1820 WCMD_output_asis(newline);
1821 displayedcount++;
1826 LocalFree( str );
1827 return displayedcount;
1830 /****************************************************************************
1831 * WCMD_setshow_env
1833 * Set/Show the environment variables
1836 void WCMD_setshow_env (WCHAR *s) {
1838 LPVOID env;
1839 WCHAR *p;
1840 int status;
1841 static const WCHAR parmP[] = {'/','P','\0'};
1843 errorlevel = 0;
1844 if (param1[0] == 0x00 && quals[0] == 0x00) {
1845 env = GetEnvironmentStrings ();
1846 WCMD_setshow_sortenv( env, NULL );
1847 return;
1850 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1851 if (CompareString (LOCALE_USER_DEFAULT,
1852 NORM_IGNORECASE | SORT_STRINGSORT,
1853 s, 2, parmP, -1) == 2) {
1854 WCHAR string[MAXSTRING];
1855 DWORD count;
1857 s += 2;
1858 while (*s && *s==' ') s++;
1860 /* If no parameter, or no '=' sign, return an error */
1861 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1862 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1863 return;
1866 /* Output the prompt */
1867 *p++ = '\0';
1868 if (strlenW(p) != 0) WCMD_output(p);
1870 /* Read the reply */
1871 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1872 sizeof(string)/sizeof(WCHAR), &count, NULL);
1873 if (count > 1) {
1874 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1875 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1876 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1877 wine_dbgstr_w(string));
1878 status = SetEnvironmentVariable (s, string);
1881 } else {
1882 DWORD gle;
1883 p = strchrW (s, '=');
1884 if (p == NULL) {
1885 env = GetEnvironmentStrings ();
1886 if (WCMD_setshow_sortenv( env, s ) == 0) {
1887 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1888 errorlevel = 1;
1890 return;
1892 *p++ = '\0';
1894 if (strlenW(p) == 0) p = NULL;
1895 status = SetEnvironmentVariable (s, p);
1896 gle = GetLastError();
1897 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1898 errorlevel = 1;
1899 } else if ((!status)) WCMD_print_error();
1903 /****************************************************************************
1904 * WCMD_setshow_path
1906 * Set/Show the path environment variable
1909 void WCMD_setshow_path (WCHAR *command) {
1911 WCHAR string[1024];
1912 DWORD status;
1913 static const WCHAR pathW[] = {'P','A','T','H','\0'};
1914 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1916 if (strlenW(param1) == 0) {
1917 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1918 if (status != 0) {
1919 WCMD_output_asis ( pathEqW);
1920 WCMD_output_asis ( string);
1921 WCMD_output_asis ( newline);
1923 else {
1924 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1927 else {
1928 if (*command == '=') command++; /* Skip leading '=' */
1929 status = SetEnvironmentVariable (pathW, command);
1930 if (!status) WCMD_print_error();
1934 /****************************************************************************
1935 * WCMD_setshow_prompt
1937 * Set or show the command prompt.
1940 void WCMD_setshow_prompt (void) {
1942 WCHAR *s;
1943 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1945 if (strlenW(param1) == 0) {
1946 SetEnvironmentVariable (promptW, NULL);
1948 else {
1949 s = param1;
1950 while ((*s == '=') || (*s == ' ')) s++;
1951 if (strlenW(s) == 0) {
1952 SetEnvironmentVariable (promptW, NULL);
1954 else SetEnvironmentVariable (promptW, s);
1958 /****************************************************************************
1959 * WCMD_setshow_time
1961 * Set/Show the system time
1962 * FIXME: Can't change time yet
1965 void WCMD_setshow_time (void) {
1967 WCHAR curtime[64], buffer[64];
1968 DWORD count;
1969 SYSTEMTIME st;
1970 static const WCHAR parmT[] = {'/','T','\0'};
1972 if (strlenW(param1) == 0) {
1973 GetLocalTime(&st);
1974 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1975 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1976 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1977 if (strstrW (quals, parmT) == NULL) {
1978 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1979 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1980 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1981 if (count > 2) {
1982 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1986 else WCMD_print_error ();
1988 else {
1989 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1993 /****************************************************************************
1994 * WCMD_shift
1996 * Shift batch parameters.
1997 * Optional /n says where to start shifting (n=0-8)
2000 void WCMD_shift (WCHAR *command) {
2001 int start;
2003 if (context != NULL) {
2004 WCHAR *pos = strchrW(command, '/');
2005 int i;
2007 if (pos == NULL) {
2008 start = 0;
2009 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2010 start = (*(pos+1) - '0');
2011 } else {
2012 SetLastError(ERROR_INVALID_PARAMETER);
2013 WCMD_print_error();
2014 return;
2017 WINE_TRACE("Shifting variables, starting at %d\n", start);
2018 for (i=start;i<=8;i++) {
2019 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2021 context -> shift_count[9] = context -> shift_count[9] + 1;
2026 /****************************************************************************
2027 * WCMD_title
2029 * Set the console title
2031 void WCMD_title (WCHAR *command) {
2032 SetConsoleTitle(command);
2035 /****************************************************************************
2036 * WCMD_type
2038 * Copy a file to standard output.
2041 void WCMD_type (WCHAR *command) {
2043 int argno = 0;
2044 WCHAR *argN = command;
2045 BOOL writeHeaders = FALSE;
2047 if (param1[0] == 0x00) {
2048 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2049 return;
2052 if (param2[0] != 0x00) writeHeaders = TRUE;
2054 /* Loop through all args */
2055 errorlevel = 0;
2056 while (argN) {
2057 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2059 HANDLE h;
2060 WCHAR buffer[512];
2061 DWORD count;
2063 if (!argN) break;
2065 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2066 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2067 FILE_ATTRIBUTE_NORMAL, NULL);
2068 if (h == INVALID_HANDLE_VALUE) {
2069 WCMD_print_error ();
2070 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2071 errorlevel = 1;
2072 } else {
2073 if (writeHeaders) {
2074 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2075 WCMD_output(fmt, thisArg);
2077 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2078 if (count == 0) break; /* ReadFile reports success on EOF! */
2079 buffer[count] = 0;
2080 WCMD_output_asis (buffer);
2082 CloseHandle (h);
2087 /****************************************************************************
2088 * WCMD_more
2090 * Output either a file or stdin to screen in pages
2093 void WCMD_more (WCHAR *command) {
2095 int argno = 0;
2096 WCHAR *argN = command;
2097 BOOL useinput = FALSE;
2098 WCHAR moreStr[100];
2099 WCHAR moreStrPage[100];
2100 WCHAR buffer[512];
2101 DWORD count;
2102 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2103 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2104 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2105 ')',' ','-','-','\n','\0'};
2106 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2108 /* Prefix the NLS more with '-- ', then load the text */
2109 errorlevel = 0;
2110 strcpyW(moreStr, moreStart);
2111 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2112 (sizeof(moreStr)/sizeof(WCHAR))-3);
2114 if (param1[0] == 0x00) {
2116 /* Wine implements pipes via temporary files, and hence stdin is
2117 effectively reading from the file. This means the prompts for
2118 more are satistied by the next line from the input (file). To
2119 avoid this, ensure stdin is to the console */
2120 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2121 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2122 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2123 FILE_ATTRIBUTE_NORMAL, 0);
2124 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2126 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2127 once you get in this bit unless due to a pipe, its going to end badly... */
2128 useinput = TRUE;
2129 wsprintf(moreStrPage, moreFmt, moreStr);
2131 WCMD_enter_paged_mode(moreStrPage);
2132 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2133 if (count == 0) break; /* ReadFile reports success on EOF! */
2134 buffer[count] = 0;
2135 WCMD_output_asis (buffer);
2137 WCMD_leave_paged_mode();
2139 /* Restore stdin to what it was */
2140 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2141 CloseHandle(hConIn);
2143 return;
2144 } else {
2145 BOOL needsPause = FALSE;
2147 /* Loop through all args */
2148 WCMD_enter_paged_mode(moreStrPage);
2150 while (argN) {
2151 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2152 HANDLE h;
2154 if (!argN) break;
2156 if (needsPause) {
2158 /* Wait */
2159 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2160 WCMD_leave_paged_mode();
2161 WCMD_output_asis(moreStrPage);
2162 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2163 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2164 WCMD_enter_paged_mode(moreStrPage);
2168 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2169 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2170 FILE_ATTRIBUTE_NORMAL, NULL);
2171 if (h == INVALID_HANDLE_VALUE) {
2172 WCMD_print_error ();
2173 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2174 errorlevel = 1;
2175 } else {
2176 ULONG64 curPos = 0;
2177 ULONG64 fileLen = 0;
2178 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2180 /* Get the file size */
2181 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2182 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2184 needsPause = TRUE;
2185 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2186 if (count == 0) break; /* ReadFile reports success on EOF! */
2187 buffer[count] = 0;
2188 curPos += count;
2190 /* Update % count (would be used in WCMD_output_asis as prompt) */
2191 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2193 WCMD_output_asis (buffer);
2195 CloseHandle (h);
2199 WCMD_leave_paged_mode();
2203 /****************************************************************************
2204 * WCMD_verify
2206 * Display verify flag.
2207 * FIXME: We don't actually do anything with the verify flag other than toggle
2208 * it...
2211 void WCMD_verify (WCHAR *command) {
2213 int count;
2215 count = strlenW(command);
2216 if (count == 0) {
2217 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2218 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2219 return;
2221 if (lstrcmpiW(command, onW) == 0) {
2222 verify_mode = 1;
2223 return;
2225 else if (lstrcmpiW(command, offW) == 0) {
2226 verify_mode = 0;
2227 return;
2229 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2232 /****************************************************************************
2233 * WCMD_version
2235 * Display version info.
2238 void WCMD_version (void) {
2240 WCMD_output (version_string);
2244 /****************************************************************************
2245 * WCMD_volume
2247 * Display volume info and/or set volume label. Returns 0 if error.
2250 int WCMD_volume (int mode, WCHAR *path) {
2252 DWORD count, serial;
2253 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2254 BOOL status;
2256 if (strlenW(path) == 0) {
2257 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2258 if (!status) {
2259 WCMD_print_error ();
2260 return 0;
2262 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2263 &serial, NULL, NULL, NULL, 0);
2265 else {
2266 static const WCHAR fmt[] = {'%','s','\\','\0'};
2267 if ((path[1] != ':') || (strlenW(path) != 2)) {
2268 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2269 return 0;
2271 wsprintf (curdir, fmt, path);
2272 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2273 &serial, NULL,
2274 NULL, NULL, 0);
2276 if (!status) {
2277 WCMD_print_error ();
2278 return 0;
2280 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2281 curdir[0], label, HIWORD(serial), LOWORD(serial));
2282 if (mode) {
2283 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2284 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2285 sizeof(string)/sizeof(WCHAR), &count, NULL);
2286 if (count > 1) {
2287 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2288 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2290 if (strlenW(path) != 0) {
2291 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2293 else {
2294 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2297 return 1;
2300 /**************************************************************************
2301 * WCMD_exit
2303 * Exit either the process, or just this batch program
2307 void WCMD_exit (CMD_LIST **cmdList) {
2309 static const WCHAR parmB[] = {'/','B','\0'};
2310 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2312 if (context && lstrcmpiW(quals, parmB) == 0) {
2313 errorlevel = rc;
2314 context -> skip_rest = TRUE;
2315 *cmdList = NULL;
2316 } else {
2317 ExitProcess(rc);
2321 /**************************************************************************
2322 * WCMD_ask_confirm
2324 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2325 * answer.
2327 * Returns True if Y (or A) answer is selected
2328 * If optionAll contains a pointer, ALL is allowed, and if answered
2329 * set to TRUE
2332 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2334 WCHAR msgbuffer[MAXSTRING];
2335 WCHAR Ybuffer[MAXSTRING];
2336 WCHAR Nbuffer[MAXSTRING];
2337 WCHAR Abuffer[MAXSTRING];
2338 WCHAR answer[MAX_PATH] = {'\0'};
2339 DWORD count = 0;
2341 /* Load the translated 'Are you sure', plus valid answers */
2342 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2343 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2344 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2345 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2347 /* Loop waiting on a Y or N */
2348 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2349 static const WCHAR startBkt[] = {' ','(','\0'};
2350 static const WCHAR endBkt[] = {')','?','\0'};
2352 WCMD_output_asis (message);
2353 if (showSureText) {
2354 WCMD_output_asis (msgbuffer);
2356 WCMD_output_asis (startBkt);
2357 WCMD_output_asis (Ybuffer);
2358 WCMD_output_asis (fslashW);
2359 WCMD_output_asis (Nbuffer);
2360 if (optionAll) {
2361 WCMD_output_asis (fslashW);
2362 WCMD_output_asis (Abuffer);
2364 WCMD_output_asis (endBkt);
2365 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2366 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2367 answer[0] = toupperW(answer[0]);
2370 /* Return the answer */
2371 return ((answer[0] == Ybuffer[0]) ||
2372 (optionAll && (answer[0] == Abuffer[0])));
2375 /*****************************************************************************
2376 * WCMD_assoc
2378 * Lists or sets file associations (assoc = TRUE)
2379 * Lists or sets file types (assoc = FALSE)
2381 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2383 HKEY key;
2384 DWORD accessOptions = KEY_READ;
2385 WCHAR *newValue;
2386 LONG rc = ERROR_SUCCESS;
2387 WCHAR keyValue[MAXSTRING];
2388 DWORD valueLen = MAXSTRING;
2389 HKEY readKey;
2390 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2391 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2393 /* See if parameter includes '=' */
2394 errorlevel = 0;
2395 newValue = strchrW(command, '=');
2396 if (newValue) accessOptions |= KEY_WRITE;
2398 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2399 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2400 accessOptions, &key) != ERROR_SUCCESS) {
2401 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2402 return;
2405 /* If no parameters then list all associations */
2406 if (*command == 0x00) {
2407 int index = 0;
2409 /* Enumerate all the keys */
2410 while (rc != ERROR_NO_MORE_ITEMS) {
2411 WCHAR keyName[MAXSTRING];
2412 DWORD nameLen;
2414 /* Find the next value */
2415 nameLen = MAXSTRING;
2416 rc = RegEnumKeyEx(key, index++,
2417 keyName, &nameLen,
2418 NULL, NULL, NULL, NULL);
2420 if (rc == ERROR_SUCCESS) {
2422 /* Only interested in extension ones if assoc, or others
2423 if not assoc */
2424 if ((keyName[0] == '.' && assoc) ||
2425 (!(keyName[0] == '.') && (!assoc)))
2427 WCHAR subkey[MAXSTRING];
2428 strcpyW(subkey, keyName);
2429 if (!assoc) strcatW(subkey, shOpCmdW);
2431 if (RegOpenKeyEx(key, subkey, 0,
2432 accessOptions, &readKey) == ERROR_SUCCESS) {
2434 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2435 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2436 (LPBYTE)keyValue, &valueLen);
2437 WCMD_output_asis(keyName);
2438 WCMD_output_asis(equalW);
2439 /* If no default value found, leave line empty after '=' */
2440 if (rc == ERROR_SUCCESS) {
2441 WCMD_output_asis(keyValue);
2443 WCMD_output_asis(newline);
2448 RegCloseKey(readKey);
2450 } else {
2452 /* Parameter supplied - if no '=' on command line, its a query */
2453 if (newValue == NULL) {
2454 WCHAR *space;
2455 WCHAR subkey[MAXSTRING];
2457 /* Query terminates the parameter at the first space */
2458 strcpyW(keyValue, command);
2459 space = strchrW(keyValue, ' ');
2460 if (space) *space=0x00;
2462 /* Set up key name */
2463 strcpyW(subkey, keyValue);
2464 if (!assoc) strcatW(subkey, shOpCmdW);
2466 if (RegOpenKeyEx(key, subkey, 0,
2467 accessOptions, &readKey) == ERROR_SUCCESS) {
2469 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2470 (LPBYTE)keyValue, &valueLen);
2471 WCMD_output_asis(command);
2472 WCMD_output_asis(equalW);
2473 /* If no default value found, leave line empty after '=' */
2474 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2475 WCMD_output_asis(newline);
2476 RegCloseKey(readKey);
2478 } else {
2479 WCHAR msgbuffer[MAXSTRING];
2480 WCHAR outbuffer[MAXSTRING];
2482 /* Load the translated 'File association not found' */
2483 if (assoc) {
2484 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2485 } else {
2486 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2488 wsprintf(outbuffer, msgbuffer, keyValue);
2489 WCMD_output_asis(outbuffer);
2490 errorlevel = 2;
2493 /* Not a query - its a set or clear of a value */
2494 } else {
2496 WCHAR subkey[MAXSTRING];
2498 /* Get pointer to new value */
2499 *newValue = 0x00;
2500 newValue++;
2502 /* Set up key name */
2503 strcpyW(subkey, command);
2504 if (!assoc) strcatW(subkey, shOpCmdW);
2506 /* If nothing after '=' then clear value - only valid for ASSOC */
2507 if (*newValue == 0x00) {
2509 if (assoc) rc = RegDeleteKey(key, command);
2510 if (assoc && rc == ERROR_SUCCESS) {
2511 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2513 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2514 WCMD_print_error();
2515 errorlevel = 2;
2517 } else {
2518 WCHAR msgbuffer[MAXSTRING];
2519 WCHAR outbuffer[MAXSTRING];
2521 /* Load the translated 'File association not found' */
2522 if (assoc) {
2523 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2524 sizeof(msgbuffer)/sizeof(WCHAR));
2525 } else {
2526 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2527 sizeof(msgbuffer)/sizeof(WCHAR));
2529 wsprintf(outbuffer, msgbuffer, keyValue);
2530 WCMD_output_asis(outbuffer);
2531 errorlevel = 2;
2534 /* It really is a set value = contents */
2535 } else {
2536 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2537 accessOptions, NULL, &readKey, NULL);
2538 if (rc == ERROR_SUCCESS) {
2539 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2540 (LPBYTE)newValue, strlenW(newValue));
2541 RegCloseKey(readKey);
2544 if (rc != ERROR_SUCCESS) {
2545 WCMD_print_error();
2546 errorlevel = 2;
2547 } else {
2548 WCMD_output_asis(command);
2549 WCMD_output_asis(equalW);
2550 WCMD_output_asis(newValue);
2551 WCMD_output_asis(newline);
2557 /* Clean up */
2558 RegCloseKey(key);
2561 /****************************************************************************
2562 * WCMD_color
2564 * Clear the terminal screen.
2567 void WCMD_color (void) {
2569 /* Emulate by filling the screen from the top left to bottom right with
2570 spaces, then moving the cursor to the top left afterwards */
2571 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2572 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2574 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2575 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2576 return;
2579 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2581 COORD topLeft;
2582 DWORD screenSize;
2583 DWORD color = 0;
2585 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2587 topLeft.X = 0;
2588 topLeft.Y = 0;
2590 /* Convert the color hex digits */
2591 if (param1[0] == 0x00) {
2592 color = defaultColor;
2593 } else {
2594 color = strtoulW(param1, NULL, 16);
2597 /* Fail if fg == bg color */
2598 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2599 errorlevel = 1;
2600 return;
2603 /* Set the current screen contents and ensure all future writes
2604 remain this color */
2605 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2606 SetConsoleTextAttribute(hStdOut, color);