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
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.
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
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 /****************************************************************************
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
))
86 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
90 FillConsoleOutputCharacter(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
91 SetConsoleCursorPosition(hStdOut
, topLeft
);
95 /****************************************************************************
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 /****************************************************************************
110 * Copy a file or wildcarded set.
111 * FIXME: Add support for a+b+c type syntax
114 void WCMD_copy (void) {
119 WCHAR outpath
[MAX_PATH
], srcpath
[MAX_PATH
], copycmd
[3];
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
];
128 WCHAR fname
[MAX_PATH
];
131 if (param1
[0] == 0x00) {
132 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
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
);
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
);
153 strcatW(srcspec
, slashW
);
154 strcatW(srcspec
, starW
);
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
);
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
))
183 else if (strstrW (quals
, parmY
))
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
) {
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
) {
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 */
228 status
= CopyFile (srcname
, outname
, FALSE
);
229 if (!status
) WCMD_print_error ();
232 } while (FindNextFile(hff
, &fd
) != 0);
235 status
= ERROR_FILE_NOT_FOUND
;
240 /****************************************************************************
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
)
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
))
264 DWORD last_error
= GetLastError();
265 if (last_error
== ERROR_ALREADY_EXISTS
)
268 if (last_error
!= ERROR_PATH_NOT_FOUND
)
274 if (!(slash
= strrchrW(new_path
,'\\')) && ! (slash
= strrchrW(new_path
,'/')))
280 len
= slash
- new_path
;
282 if (!create_full_path(new_path
))
287 new_path
[len
] = '\\';
289 HeapFree(GetProcessHeap(),0,new_path
);
293 void WCMD_create_dir (void) {
295 if (param1
[0] == 0x00) {
296 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
299 if (!create_full_path(param1
)) WCMD_print_error ();
302 /****************************************************************************
305 * Delete a file or wildcarded set.
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
315 BOOL
WCMD_delete (WCHAR
*command
, BOOL expectDir
) {
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 */
332 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
333 WCHAR argCopy
[MAX_PATH
];
335 if (argN
&& argN
[0] != '/') {
339 WCHAR fpath
[MAX_PATH
];
341 BOOL handleParm
= TRUE
;
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
));
350 /* If filename part of parameter is * or *.*, prompt unless
352 if ((strstrW (quals
, parmQ
) == NULL
) && (strstrW (quals
, parmP
) == NULL
)) {
356 WCHAR fname
[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))) {
367 WCHAR question
[MAXSTRING
];
368 static const WCHAR fmt
[] = {'%','s',' ','\0'};
370 /* Note: Flag as found, to avoid file not found message */
373 /* Ask for confirmation */
374 wsprintf(question
, fmt
, fpath
);
375 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
377 /* Abort if answer is 'N' */
382 /* First, try to delete in the current directory */
383 hff
= FindFirstFile (argCopy
, &fd
);
384 if (hff
== INVALID_HANDLE_VALUE
) {
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
);
400 WCMD_delete(modifiedParm
, FALSE
);
402 } else if (handleParm
) {
404 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
405 strcpyW (fpath
, argCopy
);
407 p
= strrchrW (fpath
, '\\');
410 strcatW (fpath
, fd
.cFileName
);
412 else strcpyW (fpath
, fd
.cFileName
);
413 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
415 WCHAR
*nextA
= strstrW (quals
, parmA
);
417 /* Handle attribute matching (/A) */
420 while (nextA
!= NULL
&& !ok
) {
422 WCHAR
*thisA
= (nextA
+2);
425 /* Skip optional : */
426 if (*thisA
== ':') thisA
++;
428 /* Parse each of the /A[:]xxx in turn */
429 while (*thisA
&& *thisA
!= '/') {
431 BOOL attribute
= FALSE
;
433 /* Match negation of attribute first */
439 /* Match attribute */
441 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
443 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
445 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
447 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
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
) {
457 } else if (!attribute
&& negate
) {
465 /* Save the running total as the final result */
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 */
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);
500 /* Now recurse into all subdirectories handling the parameter in the same way */
501 if (strstrW (quals
, parmS
) != NULL
) {
503 WCHAR thisDir
[MAX_PATH
];
508 WCHAR fname
[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 */
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
;
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
;
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);
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
);
576 /* Keep running total to see if any found, and if not recursing
577 issue error message */
581 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), argCopy
);
588 /* Handle no valid args */
589 if (argsProcessed
== 0) {
590 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
596 /****************************************************************************
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
) {
607 if ((command
[0] == '.') && (command
[1] == 0)) {
608 WCMD_output (newline
);
613 count
= strlenW(command
);
615 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
616 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
619 if (lstrcmpiW(command
, onW
) == 0) {
623 if (lstrcmpiW(command
, offW
) == 0) {
627 WCMD_output_asis (command
);
628 WCMD_output (newline
);
632 /**************************************************************************
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
) {
651 const WCHAR inW
[] = {'i', 'n', '\0'};
652 const WCHAR doW
[] = {'d', 'o', ' ','\0'};
653 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
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
));
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
);
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
));
701 /* Save away the starting position for the commands (and offset for the
705 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
708 /* Loop through all set entries */
710 thisSet
->command
!= NULL
&&
711 thisSet
->bracketDepth
>= thisDepth
) {
713 /* Loop through all entries on the same line */
716 WINE_TRACE("Processing for set %p\n", thisSet
);
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
) {
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);
746 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
749 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
750 cmdEnd
= thisCmdStart
;
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 */
764 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
768 /*****************************************************************************
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
),
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
);
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 */
803 BOOL processThese
= TRUE
;
805 if (isIF
) processThese
= conditionTRUE
;
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",
815 (*cmdList
)->isAmphersand
,
816 (*cmdList
)->bracketDepth
, myDepth
);
818 /* Execute any appended to the statement with &&'s */
819 if ((*cmdList
)->isAmphersand
) {
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
) {
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? */
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 */
844 WCHAR
*cmd
= ((*cmdList
)->command
) + strlenW(ifElse
);
846 /* Skip leading whitespace between condition and the command */
847 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
849 WCMD_execute (cmd
, variable
, value
, cmdList
);
852 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
854 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
863 /*****************************************************************************
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
;
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
))) {
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
);
890 LocalFree ((HANDLE
)new_cmd
);
892 WCMD_process_command (orig_cmd
, cmdList
);
897 /**************************************************************************
900 * Simple on-line help. Help text is stored in the resource file.
903 void WCMD_give_help (WCHAR
*command
) {
907 command
= WCMD_strtrim_leading_spaces(command
);
908 if (strlenW(command
) == 0) {
909 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
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
));
919 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), param1
);
924 /****************************************************************************
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 */
940 if (param1
[0] == 0x00) {
941 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
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
;
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
));
966 /*****************************************************************************
969 * Push a directory onto the stack
972 void WCMD_pushd (WCHAR
*command
) {
973 struct env_stack
*curdir
;
975 static const WCHAR parmD
[] = {'/','D','\0'};
977 if (strchrW(command
, '/') != NULL
) {
978 SetLastError(ERROR_INVALID_PARAMETER
);
983 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
984 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
985 if( !curdir
|| !thisdir
) {
988 WINE_ERR ("out of memory\n");
992 /* Change directory using CD code with /D parameter */
993 strcpyW(quals
, parmD
);
994 GetCurrentDirectoryW (1024, thisdir
);
996 WCMD_setshow_default(command
);
1002 curdir
-> next
= pushd_directories
;
1003 curdir
-> strings
= thisdir
;
1004 if (pushd_directories
== NULL
) {
1005 curdir
-> u
.stackdepth
= 1;
1007 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
1009 pushd_directories
= curdir
;
1014 /*****************************************************************************
1017 * Pop a directory from the stack
1020 void WCMD_popd (void) {
1021 struct env_stack
*temp
= pushd_directories
;
1023 if (!pushd_directories
)
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
);
1033 /****************************************************************************
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
)) {
1060 strcpyW (condition
, param2
);
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
) {
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) {
1079 WCMD_parameter (p
, 2+negate
, &command
);
1081 else if ((s
= strstrW (p
, eqeqW
))) {
1083 if (!lstrcmpiW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
1084 WCMD_parameter (s
, 1, &command
);
1087 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
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 /****************************************************************************
1099 * Move a file, directory tree or wildcarded set of files.
1102 void WCMD_move (void) {
1107 WCHAR input
[MAX_PATH
];
1108 WCHAR output
[MAX_PATH
];
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
));
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
];
1140 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1142 /* Build src & dest name */
1143 strcpyW(src
, drive
);
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
);
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
);
1171 /* If destination exists, prompt unless /Y supplied */
1172 if (GetFileAttributes(dest
) != INVALID_FILE_ATTRIBUTES
) {
1174 WCHAR copycmd
[MAXSTRING
];
1177 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1178 if (strstrW (quals
, parmNoY
))
1180 else if (strstrW (quals
, parmY
))
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 */
1191 WCHAR question
[MAXSTRING
];
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 */
1202 if (!DeleteFile (dest
)) {
1203 WCMD_print_error ();
1212 status
= MoveFile (src
, dest
);
1214 status
= 1; /* Anything other than 0 to prevent error msg below */
1219 WCMD_print_error ();
1223 /* Step on to next match */
1224 if (FindNextFile(hff
, &fd
) == 0) {
1226 hff
= INVALID_HANDLE_VALUE
;
1232 /****************************************************************************
1235 * Wait for keyboard input.
1238 void WCMD_pause (void) {
1243 WCMD_output (anykey
);
1244 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1245 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1248 /****************************************************************************
1251 * Delete a directory.
1254 void WCMD_remove_dir (WCHAR
*command
) {
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 */
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
));
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 */
1278 SHFILEOPSTRUCT lpDir
;
1281 if (strstrW (quals
, parmQ
) == NULL
) {
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' */
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
));
1313 /****************************************************************************
1319 void WCMD_rename (void) {
1324 WCHAR input
[MAX_PATH
];
1325 WCHAR
*dotDst
= NULL
;
1327 WCHAR dir
[MAX_PATH
];
1328 WCHAR fname
[MAX_PATH
];
1329 WCHAR ext
[MAX_PATH
];
1334 /* Must be at least two args */
1335 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1336 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1341 /* Destination cannot contain a drive letter or directory separator */
1342 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1343 SetLastError(ERROR_INVALID_PARAMETER
);
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
;
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:
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
);
1379 dirLen
= strlenW(src
);
1380 strcatW(src
, fd
.cFileName
);
1383 if (param2
[0] == '*') {
1384 strcatW(dest
, fd
.cFileName
);
1385 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
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
);
1408 status
= MoveFile (src
, dest
);
1412 WCMD_print_error ();
1416 /* Step on to next match */
1417 if (FindNextFile(hff
, &fd
) == 0) {
1419 hff
= INVALID_HANDLE_VALUE
;
1425 /*****************************************************************************
1428 * Make a copy of the environment.
1430 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1440 len
+= (strlenW(&env
[len
]) + 1);
1442 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1445 WINE_ERR("out of memory\n");
1448 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1454 /*****************************************************************************
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
) {
1462 struct env_stack
*env_copy
;
1463 WCHAR cwd
[MAX_PATH
];
1465 /* DISABLEEXTENSIONS ignored */
1467 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1470 WINE_ERR ("out of memory\n");
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];
1487 LocalFree (env_copy
);
1489 FreeEnvironmentStringsW (env
);
1493 /*****************************************************************************
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
;
1505 if (!saved_environment
)
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 ());
1517 n
= strlenW(&old
[len
]) + 1;
1518 p
= strchrW(&old
[len
] + 1, '=');
1522 SetEnvironmentVariableW (&old
[len
], NULL
);
1527 FreeEnvironmentStringsW (env
);
1529 /* restore old environment */
1530 env
= temp
->strings
;
1533 n
= strlenW(&env
[len
]) + 1;
1534 p
= strchrW(&env
[len
] + 1, '=');
1538 SetEnvironmentVariableW (&env
[len
], p
);
1543 /* Restore current drive letter */
1544 if (IsCharAlpha(temp
->u
.cwd
)) {
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
);
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) {
1578 WCHAR flags
[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1580 if (param1
[0] == '-') {
1581 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
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
);
1598 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1599 static const WCHAR fmt
[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1600 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1603 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1606 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1609 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1612 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1615 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1618 WCMD_output (fmt
, flags
, fd
.cFileName
);
1619 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1621 } while (FindNextFile(hff
, &fd
) != 0);
1626 /*****************************************************************************
1627 * WCMD_setshow_default
1629 * Set/Show the current default directory
1632 void WCMD_setshow_default (WCHAR
*command
) {
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) {
1649 while (*command
&& *command
==' ') command
++;
1652 GetCurrentDirectory (sizeof(cwd
)/sizeof(WCHAR
), cwd
);
1653 if (strlenW(command
) == 0) {
1654 strcatW (cwd
, newline
);
1658 /* Remove any double quotes, which may be in the
1659 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1662 if (*command
!= '"') *pos
++ = *command
;
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
];
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
);
1684 wsprintf(string
, fmt
, drive
, dir
, fd
.cFileName
);
1687 hff
= INVALID_HANDLE_VALUE
;
1691 /* Step on to next match */
1692 if (FindNextFile(hff
, &fd
) == 0) {
1694 hff
= INVALID_HANDLE_VALUE
;
1699 /* Change to that directory */
1700 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
1702 status
= SetCurrentDirectory (string
);
1705 WCMD_print_error ();
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
1721 if ((string
[1] == ':') && IsCharAlpha (string
[0])) {
1723 strcpyW(env
, equalW
);
1724 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
1726 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
1727 SetEnvironmentVariable(env
, string
);
1734 /****************************************************************************
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];
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
);
1756 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1760 else WCMD_print_error ();
1763 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1767 /****************************************************************************
1770 static int WCMD_compare( const void *a
, const void *b
)
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;
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;
1793 if (stub
) stublen
= strlenW(stub
);
1795 /* count the number of strings, and the total length */
1797 len
+= (strlenW(&s
[len
]) + 1);
1801 /* add the strings to an array */
1802 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
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
);
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
);
1827 return displayedcount
;
1830 /****************************************************************************
1833 * Set/Show the environment variables
1836 void WCMD_setshow_env (WCHAR
*s
) {
1841 static const WCHAR parmP
[] = {'/','P','\0'};
1844 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
1845 env
= GetEnvironmentStrings ();
1846 WCMD_setshow_sortenv( env
, NULL
);
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
];
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
));
1866 /* Output the prompt */
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
);
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
);
1883 p
= strchrW (s
, '=');
1885 env
= GetEnvironmentStrings ();
1886 if (WCMD_setshow_sortenv( env
, s
) == 0) {
1887 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
1894 if (strlenW(p
) == 0) p
= NULL
;
1895 status
= SetEnvironmentVariable (s
, p
);
1896 gle
= GetLastError();
1897 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
1899 } else if ((!status
)) WCMD_print_error();
1903 /****************************************************************************
1906 * Set/Show the path environment variable
1909 void WCMD_setshow_path (WCHAR
*command
) {
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
));
1919 WCMD_output_asis ( pathEqW
);
1920 WCMD_output_asis ( string
);
1921 WCMD_output_asis ( newline
);
1924 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH
));
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) {
1943 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
1945 if (strlenW(param1
) == 0) {
1946 SetEnvironmentVariable (promptW
, NULL
);
1950 while ((*s
== '=') || (*s
== ' ')) s
++;
1951 if (strlenW(s
) == 0) {
1952 SetEnvironmentVariable (promptW
, NULL
);
1954 else SetEnvironmentVariable (promptW
, s
);
1958 /****************************************************************************
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];
1970 static const WCHAR parmT
[] = {'/','T','\0'};
1972 if (strlenW(param1
) == 0) {
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
);
1982 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1986 else WCMD_print_error ();
1989 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1993 /****************************************************************************
1996 * Shift batch parameters.
1997 * Optional /n says where to start shifting (n=0-8)
2000 void WCMD_shift (WCHAR
*command
) {
2003 if (context
!= NULL
) {
2004 WCHAR
*pos
= strchrW(command
, '/');
2009 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2010 start
= (*(pos
+1) - '0');
2012 SetLastError(ERROR_INVALID_PARAMETER
);
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 /****************************************************************************
2029 * Set the console title
2031 void WCMD_title (WCHAR
*command
) {
2032 SetConsoleTitle(command
);
2035 /****************************************************************************
2038 * Copy a file to standard output.
2041 void WCMD_type (WCHAR
*command
) {
2044 WCHAR
*argN
= command
;
2045 BOOL writeHeaders
= FALSE
;
2047 if (param1
[0] == 0x00) {
2048 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2052 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2054 /* Loop through all args */
2057 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
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
);
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! */
2080 WCMD_output_asis (buffer
);
2087 /****************************************************************************
2090 * Output either a file or stdin to screen in pages
2093 void WCMD_more (WCHAR
*command
) {
2096 WCHAR
*argN
= command
;
2097 BOOL useinput
= FALSE
;
2099 WCHAR moreStrPage
[100];
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 */
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... */
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! */
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
);
2145 BOOL needsPause
= FALSE
;
2147 /* Loop through all args */
2148 WCMD_enter_paged_mode(moreStrPage
);
2151 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
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
);
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
;
2185 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2186 if (count
== 0) break; /* ReadFile reports success on EOF! */
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
);
2199 WCMD_leave_paged_mode();
2203 /****************************************************************************
2206 * Display verify flag.
2207 * FIXME: We don't actually do anything with the verify flag other than toggle
2211 void WCMD_verify (WCHAR
*command
) {
2215 count
= strlenW(command
);
2217 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2218 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2221 if (lstrcmpiW(command
, onW
) == 0) {
2225 else if (lstrcmpiW(command
, offW
) == 0) {
2229 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR
));
2232 /****************************************************************************
2235 * Display version info.
2238 void WCMD_version (void) {
2240 WCMD_output (version_string
);
2244 /****************************************************************************
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
];
2256 if (strlenW(path
) == 0) {
2257 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2259 WCMD_print_error ();
2262 status
= GetVolumeInformation (NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2263 &serial
, NULL
, NULL
, NULL
, 0);
2266 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2267 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2268 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2271 wsprintf (curdir
, fmt
, path
);
2272 status
= GetVolumeInformation (curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2277 WCMD_print_error ();
2280 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2281 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2283 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2284 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2285 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
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 ();
2294 if (!SetVolumeLabel (NULL
, string
)) WCMD_print_error ();
2300 /**************************************************************************
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) {
2314 context
-> skip_rest
= TRUE
;
2321 /**************************************************************************
2324 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2327 * Returns True if Y (or A) answer is selected
2328 * If optionAll contains a pointer, ALL is allowed, and if answered
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'};
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
);
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
);
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 /*****************************************************************************
2378 * Lists or sets file associations (assoc = TRUE)
2379 * Lists or sets file types (assoc = FALSE)
2381 void WCMD_assoc (WCHAR
*command
, BOOL assoc
) {
2384 DWORD accessOptions
= KEY_READ
;
2386 LONG rc
= ERROR_SUCCESS
;
2387 WCHAR keyValue
[MAXSTRING
];
2388 DWORD valueLen
= MAXSTRING
;
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 '=' */
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());
2405 /* If no parameters then list all associations */
2406 if (*command
== 0x00) {
2409 /* Enumerate all the keys */
2410 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2411 WCHAR keyName
[MAXSTRING
];
2414 /* Find the next value */
2415 nameLen
= MAXSTRING
;
2416 rc
= RegEnumKeyEx(key
, index
++,
2418 NULL
, NULL
, NULL
, NULL
);
2420 if (rc
== ERROR_SUCCESS
) {
2422 /* Only interested in extension ones if assoc, or others
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
);
2452 /* Parameter supplied - if no '=' on command line, its a query */
2453 if (newValue
== NULL
) {
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
);
2479 WCHAR msgbuffer
[MAXSTRING
];
2480 WCHAR outbuffer
[MAXSTRING
];
2482 /* Load the translated 'File association not found' */
2484 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2486 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2488 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2489 WCMD_output_asis(outbuffer
);
2493 /* Not a query - its a set or clear of a value */
2496 WCHAR subkey
[MAXSTRING
];
2498 /* Get pointer to new value */
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
) {
2518 WCHAR msgbuffer
[MAXSTRING
];
2519 WCHAR outbuffer
[MAXSTRING
];
2521 /* Load the translated 'File association not found' */
2523 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
,
2524 sizeof(msgbuffer
)/sizeof(WCHAR
));
2526 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
,
2527 sizeof(msgbuffer
)/sizeof(WCHAR
));
2529 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2530 WCMD_output_asis(outbuffer
);
2534 /* It really is a set value = contents */
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
) {
2548 WCMD_output_asis(command
);
2549 WCMD_output_asis(equalW
);
2550 WCMD_output_asis(newValue
);
2551 WCMD_output_asis(newline
);
2561 /****************************************************************************
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
));
2579 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2585 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2590 /* Convert the color hex digits */
2591 if (param1
[0] == 0x00) {
2592 color
= defaultColor
;
2594 color
= strtoulW(param1
, NULL
, 16);
2597 /* Fail if fg == bg color */
2598 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
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
);