dplayx: Adjust GetCaps behaviour to documentation
[wine/gsoc_dplay.git] / programs / cmd / builtins.c
blob612e5d9dd55cd9f4d547f3f45db50b8c7416cd8c
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 HeapFree(GetProcessHeap(), 0, 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 && (*cmdList)->command) {
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++;
2075 if (*s=='\"')
2076 WCMD_opt_s_strip_quotes(s);
2078 /* If no parameter, or no '=' sign, return an error */
2079 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2080 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2081 return;
2084 /* Output the prompt */
2085 *p++ = '\0';
2086 if (strlenW(p) != 0) WCMD_output(p);
2088 /* Read the reply */
2089 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2090 sizeof(string)/sizeof(WCHAR), &count, NULL);
2091 if (count > 1) {
2092 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2093 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2094 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2095 wine_dbgstr_w(string));
2096 status = SetEnvironmentVariable (s, string);
2099 } else {
2100 DWORD gle;
2102 if (*s=='\"')
2103 WCMD_opt_s_strip_quotes(s);
2104 p = strchrW (s, '=');
2105 if (p == NULL) {
2106 env = GetEnvironmentStrings ();
2107 if (WCMD_setshow_sortenv( env, s ) == 0) {
2108 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2109 errorlevel = 1;
2111 return;
2113 *p++ = '\0';
2115 if (strlenW(p) == 0) p = NULL;
2116 status = SetEnvironmentVariable (s, p);
2117 gle = GetLastError();
2118 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2119 errorlevel = 1;
2120 } else if ((!status)) WCMD_print_error();
2124 /****************************************************************************
2125 * WCMD_setshow_path
2127 * Set/Show the path environment variable
2130 void WCMD_setshow_path (WCHAR *command) {
2132 WCHAR string[1024];
2133 DWORD status;
2134 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2135 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2137 if (strlenW(param1) == 0) {
2138 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2139 if (status != 0) {
2140 WCMD_output_asis ( pathEqW);
2141 WCMD_output_asis ( string);
2142 WCMD_output_asis ( newline);
2144 else {
2145 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2148 else {
2149 if (*command == '=') command++; /* Skip leading '=' */
2150 status = SetEnvironmentVariable (pathW, command);
2151 if (!status) WCMD_print_error();
2155 /****************************************************************************
2156 * WCMD_setshow_prompt
2158 * Set or show the command prompt.
2161 void WCMD_setshow_prompt (void) {
2163 WCHAR *s;
2164 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2166 if (strlenW(param1) == 0) {
2167 SetEnvironmentVariable (promptW, NULL);
2169 else {
2170 s = param1;
2171 while ((*s == '=') || (*s == ' ')) s++;
2172 if (strlenW(s) == 0) {
2173 SetEnvironmentVariable (promptW, NULL);
2175 else SetEnvironmentVariable (promptW, s);
2179 /****************************************************************************
2180 * WCMD_setshow_time
2182 * Set/Show the system time
2183 * FIXME: Can't change time yet
2186 void WCMD_setshow_time (void) {
2188 WCHAR curtime[64], buffer[64];
2189 DWORD count;
2190 SYSTEMTIME st;
2191 static const WCHAR parmT[] = {'/','T','\0'};
2193 if (strlenW(param1) == 0) {
2194 GetLocalTime(&st);
2195 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2196 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2197 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2198 if (strstrW (quals, parmT) == NULL) {
2199 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2200 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2201 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2202 if (count > 2) {
2203 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2207 else WCMD_print_error ();
2209 else {
2210 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2214 /****************************************************************************
2215 * WCMD_shift
2217 * Shift batch parameters.
2218 * Optional /n says where to start shifting (n=0-8)
2221 void WCMD_shift (WCHAR *command) {
2222 int start;
2224 if (context != NULL) {
2225 WCHAR *pos = strchrW(command, '/');
2226 int i;
2228 if (pos == NULL) {
2229 start = 0;
2230 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2231 start = (*(pos+1) - '0');
2232 } else {
2233 SetLastError(ERROR_INVALID_PARAMETER);
2234 WCMD_print_error();
2235 return;
2238 WINE_TRACE("Shifting variables, starting at %d\n", start);
2239 for (i=start;i<=8;i++) {
2240 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2242 context -> shift_count[9] = context -> shift_count[9] + 1;
2247 /****************************************************************************
2248 * WCMD_title
2250 * Set the console title
2252 void WCMD_title (WCHAR *command) {
2253 SetConsoleTitle(command);
2256 /****************************************************************************
2257 * WCMD_type
2259 * Copy a file to standard output.
2262 void WCMD_type (WCHAR *command) {
2264 int argno = 0;
2265 WCHAR *argN = command;
2266 BOOL writeHeaders = FALSE;
2268 if (param1[0] == 0x00) {
2269 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2270 return;
2273 if (param2[0] != 0x00) writeHeaders = TRUE;
2275 /* Loop through all args */
2276 errorlevel = 0;
2277 while (argN) {
2278 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2280 HANDLE h;
2281 WCHAR buffer[512];
2282 DWORD count;
2284 if (!argN) break;
2286 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2287 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2288 FILE_ATTRIBUTE_NORMAL, NULL);
2289 if (h == INVALID_HANDLE_VALUE) {
2290 WCMD_print_error ();
2291 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2292 errorlevel = 1;
2293 } else {
2294 if (writeHeaders) {
2295 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2296 WCMD_output(fmt, thisArg);
2298 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2299 if (count == 0) break; /* ReadFile reports success on EOF! */
2300 buffer[count] = 0;
2301 WCMD_output_asis (buffer);
2303 CloseHandle (h);
2304 if (!writeHeaders)
2305 WCMD_output_asis (newline);
2310 /****************************************************************************
2311 * WCMD_more
2313 * Output either a file or stdin to screen in pages
2316 void WCMD_more (WCHAR *command) {
2318 int argno = 0;
2319 WCHAR *argN = command;
2320 BOOL useinput = FALSE;
2321 WCHAR moreStr[100];
2322 WCHAR moreStrPage[100];
2323 WCHAR buffer[512];
2324 DWORD count;
2325 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2326 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2327 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2328 ')',' ','-','-','\n','\0'};
2329 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2331 /* Prefix the NLS more with '-- ', then load the text */
2332 errorlevel = 0;
2333 strcpyW(moreStr, moreStart);
2334 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2335 (sizeof(moreStr)/sizeof(WCHAR))-3);
2337 if (param1[0] == 0x00) {
2339 /* Wine implements pipes via temporary files, and hence stdin is
2340 effectively reading from the file. This means the prompts for
2341 more are satisfied by the next line from the input (file). To
2342 avoid this, ensure stdin is to the console */
2343 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2344 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2345 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2346 FILE_ATTRIBUTE_NORMAL, 0);
2347 WINE_TRACE("No parms - working probably in pipe mode\n");
2348 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2350 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2351 once you get in this bit unless due to a pipe, its going to end badly... */
2352 useinput = TRUE;
2353 wsprintf(moreStrPage, moreFmt, moreStr);
2355 WCMD_enter_paged_mode(moreStrPage);
2356 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2357 if (count == 0) break; /* ReadFile reports success on EOF! */
2358 buffer[count] = 0;
2359 WCMD_output_asis (buffer);
2361 WCMD_leave_paged_mode();
2363 /* Restore stdin to what it was */
2364 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2365 CloseHandle(hConIn);
2367 return;
2368 } else {
2369 BOOL needsPause = FALSE;
2371 /* Loop through all args */
2372 WINE_TRACE("Parms supplied - working through each file\n");
2373 WCMD_enter_paged_mode(moreStrPage);
2375 while (argN) {
2376 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2377 HANDLE h;
2379 if (!argN) break;
2381 if (needsPause) {
2383 /* Wait */
2384 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2385 WCMD_leave_paged_mode();
2386 WCMD_output_asis(moreStrPage);
2387 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2388 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2389 WCMD_enter_paged_mode(moreStrPage);
2393 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2394 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2395 FILE_ATTRIBUTE_NORMAL, NULL);
2396 if (h == INVALID_HANDLE_VALUE) {
2397 WCMD_print_error ();
2398 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2399 errorlevel = 1;
2400 } else {
2401 ULONG64 curPos = 0;
2402 ULONG64 fileLen = 0;
2403 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2405 /* Get the file size */
2406 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2407 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2409 needsPause = TRUE;
2410 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2411 if (count == 0) break; /* ReadFile reports success on EOF! */
2412 buffer[count] = 0;
2413 curPos += count;
2415 /* Update % count (would be used in WCMD_output_asis as prompt) */
2416 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2418 WCMD_output_asis (buffer);
2420 CloseHandle (h);
2424 WCMD_leave_paged_mode();
2428 /****************************************************************************
2429 * WCMD_verify
2431 * Display verify flag.
2432 * FIXME: We don't actually do anything with the verify flag other than toggle
2433 * it...
2436 void WCMD_verify (WCHAR *command) {
2438 int count;
2440 count = strlenW(command);
2441 if (count == 0) {
2442 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2443 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2444 return;
2446 if (lstrcmpiW(command, onW) == 0) {
2447 verify_mode = 1;
2448 return;
2450 else if (lstrcmpiW(command, offW) == 0) {
2451 verify_mode = 0;
2452 return;
2454 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2457 /****************************************************************************
2458 * WCMD_version
2460 * Display version info.
2463 void WCMD_version (void) {
2465 WCMD_output (version_string);
2469 /****************************************************************************
2470 * WCMD_volume
2472 * Display volume info and/or set volume label. Returns 0 if error.
2475 int WCMD_volume (int mode, WCHAR *path) {
2477 DWORD count, serial;
2478 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2479 BOOL status;
2481 if (strlenW(path) == 0) {
2482 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2483 if (!status) {
2484 WCMD_print_error ();
2485 return 0;
2487 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2488 &serial, NULL, NULL, NULL, 0);
2490 else {
2491 static const WCHAR fmt[] = {'%','s','\\','\0'};
2492 if ((path[1] != ':') || (strlenW(path) != 2)) {
2493 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2494 return 0;
2496 wsprintf (curdir, fmt, path);
2497 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2498 &serial, NULL,
2499 NULL, NULL, 0);
2501 if (!status) {
2502 WCMD_print_error ();
2503 return 0;
2505 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2506 curdir[0], label, HIWORD(serial), LOWORD(serial));
2507 if (mode) {
2508 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2509 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2510 sizeof(string)/sizeof(WCHAR), &count, NULL);
2511 if (count > 1) {
2512 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2513 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2515 if (strlenW(path) != 0) {
2516 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2518 else {
2519 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2522 return 1;
2525 /**************************************************************************
2526 * WCMD_exit
2528 * Exit either the process, or just this batch program
2532 void WCMD_exit (CMD_LIST **cmdList) {
2534 static const WCHAR parmB[] = {'/','B','\0'};
2535 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2537 if (context && lstrcmpiW(quals, parmB) == 0) {
2538 errorlevel = rc;
2539 context -> skip_rest = TRUE;
2540 *cmdList = NULL;
2541 } else {
2542 ExitProcess(rc);
2547 /*****************************************************************************
2548 * WCMD_assoc
2550 * Lists or sets file associations (assoc = TRUE)
2551 * Lists or sets file types (assoc = FALSE)
2553 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2555 HKEY key;
2556 DWORD accessOptions = KEY_READ;
2557 WCHAR *newValue;
2558 LONG rc = ERROR_SUCCESS;
2559 WCHAR keyValue[MAXSTRING];
2560 DWORD valueLen = MAXSTRING;
2561 HKEY readKey;
2562 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2563 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2565 /* See if parameter includes '=' */
2566 errorlevel = 0;
2567 newValue = strchrW(command, '=');
2568 if (newValue) accessOptions |= KEY_WRITE;
2570 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2571 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2572 accessOptions, &key) != ERROR_SUCCESS) {
2573 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2574 return;
2577 /* If no parameters then list all associations */
2578 if (*command == 0x00) {
2579 int index = 0;
2581 /* Enumerate all the keys */
2582 while (rc != ERROR_NO_MORE_ITEMS) {
2583 WCHAR keyName[MAXSTRING];
2584 DWORD nameLen;
2586 /* Find the next value */
2587 nameLen = MAXSTRING;
2588 rc = RegEnumKeyEx(key, index++,
2589 keyName, &nameLen,
2590 NULL, NULL, NULL, NULL);
2592 if (rc == ERROR_SUCCESS) {
2594 /* Only interested in extension ones if assoc, or others
2595 if not assoc */
2596 if ((keyName[0] == '.' && assoc) ||
2597 (!(keyName[0] == '.') && (!assoc)))
2599 WCHAR subkey[MAXSTRING];
2600 strcpyW(subkey, keyName);
2601 if (!assoc) strcatW(subkey, shOpCmdW);
2603 if (RegOpenKeyEx(key, subkey, 0,
2604 accessOptions, &readKey) == ERROR_SUCCESS) {
2606 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2607 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2608 (LPBYTE)keyValue, &valueLen);
2609 WCMD_output_asis(keyName);
2610 WCMD_output_asis(equalW);
2611 /* If no default value found, leave line empty after '=' */
2612 if (rc == ERROR_SUCCESS) {
2613 WCMD_output_asis(keyValue);
2615 WCMD_output_asis(newline);
2616 RegCloseKey(readKey);
2622 } else {
2624 /* Parameter supplied - if no '=' on command line, its a query */
2625 if (newValue == NULL) {
2626 WCHAR *space;
2627 WCHAR subkey[MAXSTRING];
2629 /* Query terminates the parameter at the first space */
2630 strcpyW(keyValue, command);
2631 space = strchrW(keyValue, ' ');
2632 if (space) *space=0x00;
2634 /* Set up key name */
2635 strcpyW(subkey, keyValue);
2636 if (!assoc) strcatW(subkey, shOpCmdW);
2638 if (RegOpenKeyEx(key, subkey, 0,
2639 accessOptions, &readKey) == ERROR_SUCCESS) {
2641 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2642 (LPBYTE)keyValue, &valueLen);
2643 WCMD_output_asis(command);
2644 WCMD_output_asis(equalW);
2645 /* If no default value found, leave line empty after '=' */
2646 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2647 WCMD_output_asis(newline);
2648 RegCloseKey(readKey);
2650 } else {
2651 WCHAR msgbuffer[MAXSTRING];
2652 WCHAR outbuffer[MAXSTRING];
2654 /* Load the translated 'File association not found' */
2655 if (assoc) {
2656 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2657 } else {
2658 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2660 wsprintf(outbuffer, msgbuffer, keyValue);
2661 WCMD_output_asis(outbuffer);
2662 errorlevel = 2;
2665 /* Not a query - its a set or clear of a value */
2666 } else {
2668 WCHAR subkey[MAXSTRING];
2670 /* Get pointer to new value */
2671 *newValue = 0x00;
2672 newValue++;
2674 /* Set up key name */
2675 strcpyW(subkey, command);
2676 if (!assoc) strcatW(subkey, shOpCmdW);
2678 /* If nothing after '=' then clear value - only valid for ASSOC */
2679 if (*newValue == 0x00) {
2681 if (assoc) rc = RegDeleteKey(key, command);
2682 if (assoc && rc == ERROR_SUCCESS) {
2683 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2685 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2686 WCMD_print_error();
2687 errorlevel = 2;
2689 } else {
2690 WCHAR msgbuffer[MAXSTRING];
2691 WCHAR outbuffer[MAXSTRING];
2693 /* Load the translated 'File association not found' */
2694 if (assoc) {
2695 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2696 sizeof(msgbuffer)/sizeof(WCHAR));
2697 } else {
2698 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2699 sizeof(msgbuffer)/sizeof(WCHAR));
2701 wsprintf(outbuffer, msgbuffer, keyValue);
2702 WCMD_output_asis(outbuffer);
2703 errorlevel = 2;
2706 /* It really is a set value = contents */
2707 } else {
2708 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2709 accessOptions, NULL, &readKey, NULL);
2710 if (rc == ERROR_SUCCESS) {
2711 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2712 (LPBYTE)newValue, strlenW(newValue));
2713 RegCloseKey(readKey);
2716 if (rc != ERROR_SUCCESS) {
2717 WCMD_print_error();
2718 errorlevel = 2;
2719 } else {
2720 WCMD_output_asis(command);
2721 WCMD_output_asis(equalW);
2722 WCMD_output_asis(newValue);
2723 WCMD_output_asis(newline);
2729 /* Clean up */
2730 RegCloseKey(key);
2733 /****************************************************************************
2734 * WCMD_color
2736 * Clear the terminal screen.
2739 void WCMD_color (void) {
2741 /* Emulate by filling the screen from the top left to bottom right with
2742 spaces, then moving the cursor to the top left afterwards */
2743 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2744 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2746 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2747 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2748 return;
2751 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2753 COORD topLeft;
2754 DWORD screenSize;
2755 DWORD color = 0;
2757 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2759 topLeft.X = 0;
2760 topLeft.Y = 0;
2762 /* Convert the color hex digits */
2763 if (param1[0] == 0x00) {
2764 color = defaultColor;
2765 } else {
2766 color = strtoulW(param1, NULL, 16);
2769 /* Fail if fg == bg color */
2770 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2771 errorlevel = 1;
2772 return;
2775 /* Set the current screen contents and ensure all future writes
2776 remain this color */
2777 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2778 SetConsoleTextAttribute(hStdOut, color);