Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/programs/cmd/wcmdmain.c
4387 views
1
/*
2
* CMD - Wine-compatible command line interface.
3
*
4
* Copyright (C) 1999 - 2001 D A Pickles
5
* Copyright (C) 2007 J Edmeades
6
* Copyright (C) 2025 Joe Souza (tab-completion support)
7
*
8
* This library is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Lesser General Public
10
* License as published by the Free Software Foundation; either
11
* version 2.1 of the License, or (at your option) any later version.
12
*
13
* This library is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* Lesser General Public License for more details.
17
*
18
* You should have received a copy of the GNU Lesser General Public
19
* License along with this library; if not, write to the Free Software
20
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21
*/
22
23
/*
24
* FIXME:
25
* - Cannot handle parameters in quotes
26
* - Lots of functionality missing from builtins
27
*/
28
29
#include <time.h>
30
#include "wcmd.h"
31
#include "shellapi.h"
32
#include "wine/debug.h"
33
34
WINE_DEFAULT_DEBUG_CHANNEL(cmd);
35
36
#define BASE_DELIMS L",=;~!^&()+{}[]"
37
#define PATH_SEPARATION_DELIMS L" " BASE_DELIMS
38
#define INTRA_PATH_DELIMS L"\\" BASE_DELIMS
39
40
typedef struct _SEARCH_CONTEXT
41
{
42
WIN32_FIND_DATAW *fd;
43
BOOL have_quotes;
44
BOOL user_specified_quotes;
45
BOOL is_dir_search;
46
int search_pos;
47
int insert_pos;
48
int entry_count;
49
int current_entry;
50
WCHAR searchstr[MAXSTRING];
51
} SEARCH_CONTEXT;
52
53
extern const WCHAR inbuilt[][10];
54
extern struct env_stack *pushd_directories;
55
56
struct batch_context *context = NULL;
57
int errorlevel;
58
WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING];
59
FOR_CONTEXT *forloopcontext; /* The 'for' loop context */
60
BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
61
62
BOOL echo_mode = TRUE;
63
64
WCHAR anykey[100], version_string[100];
65
66
static BOOL unicodeOutput = FALSE;
67
68
/* Variables pertaining to paging */
69
static BOOL paged_mode;
70
static const WCHAR *pagedMessage = NULL;
71
static int line_count;
72
static int max_height;
73
74
static HANDLE control_c_event;
75
76
#define MAX_WRITECONSOLE_SIZE 65535
77
78
79
static BOOL is_directory_operation(WCHAR *inputBuffer)
80
{
81
WCHAR *param = NULL, *first_param;
82
BOOL ret = FALSE;
83
84
first_param = WCMD_parameter(inputBuffer, 0, &param, TRUE, FALSE);
85
86
if (!wcsicmp(first_param, L"cd") ||
87
!wcsicmp(first_param, L"rd") ||
88
!wcsicmp(first_param, L"md") ||
89
!wcsicmp(first_param, L"chdir") ||
90
!wcsicmp(first_param, L"rmdir") ||
91
!wcsicmp(first_param, L"mkdir")) {
92
93
ret = TRUE;
94
}
95
96
return ret;
97
}
98
99
static void clear_console_characters(const HANDLE hOutput, SHORT cCount, const SHORT width)
100
{
101
CONSOLE_SCREEN_BUFFER_INFO csbi;
102
DWORD written;
103
SHORT chars;
104
105
GetConsoleScreenBufferInfo(hOutput, &csbi);
106
107
/* Need to handle clearing multiple lines, in case user resized console window. */
108
while (cCount) {
109
chars = min(width - csbi.dwCursorPosition.X, cCount);
110
FillConsoleOutputCharacterW(hOutput, L' ', chars, csbi.dwCursorPosition, &written);
111
csbi.dwCursorPosition.Y++; /* Bump to next row. */
112
csbi.dwCursorPosition.X = 0; /* First column in the row. */
113
cCount -= chars;
114
}
115
}
116
117
static void set_cursor_visible(const HANDLE hOutput, const BOOL visible)
118
{
119
CONSOLE_CURSOR_INFO cursorInfo;
120
121
if (GetConsoleCursorInfo(hOutput, &cursorInfo)) {
122
cursorInfo.bVisible = visible;
123
SetConsoleCursorInfo(hOutput, &cursorInfo);
124
}
125
}
126
127
static void build_search_string(WCHAR *inputBuffer, int len, SEARCH_CONTEXT *sc)
128
{
129
int cc = 0, nn = 0;
130
WCHAR *param = NULL, *last_param, *stripped_copy = NULL;
131
WCHAR last_stripped_copy[MAXSTRING] = L"\0";
132
BOOL need_wildcard = TRUE;
133
134
sc->searchstr[0] = L'\0';
135
136
/* Parse the buffer to find the last parameter in the buffer, where tab was pressed. */
137
do {
138
last_param = param;
139
if (stripped_copy) {
140
wcsncpy_s(last_stripped_copy, ARRAY_SIZE(last_stripped_copy), stripped_copy, _TRUNCATE);
141
}
142
stripped_copy = WCMD_parameter_with_delims(inputBuffer, nn++, &param, FALSE, FALSE, PATH_SEPARATION_DELIMS);
143
} while (param);
144
145
if (last_param) {
146
cc = last_param - inputBuffer;
147
}
148
149
if (inputBuffer[cc] == L'\"') {
150
sc->user_specified_quotes = TRUE;
151
sc->have_quotes = TRUE;
152
cc++;
153
}
154
155
if (last_stripped_copy[0]) {
156
/* We used the stripped version of the path for the search string, and also use
157
* it to replace the user's text in case and only if we find a match.
158
* It's legal to have quotes in strange places in the path, and WCMD_parameter
159
* removes them for us.
160
*/
161
wcsncpy_s(sc->searchstr, ARRAY_SIZE(sc->searchstr), last_stripped_copy, _TRUNCATE);
162
if (wcschr(sc->searchstr, L'?') || wcschr(sc->searchstr, L'*')) {
163
need_wildcard = FALSE;
164
}
165
}
166
167
/* If the user specified quotes then we treat delimiters in the path as literals and ignore them.
168
* Otherwise if inputBuffer ends in one of our delimiters then override the parsing above and use
169
* that as the search pos (i.e. a wildcard search).
170
* We do this after the parsing because the parsing is needed to determine if the user specified
171
* quotes on the current path that is subject to tab completion.
172
*/
173
if (!sc->user_specified_quotes && len && wcschr(PATH_SEPARATION_DELIMS, inputBuffer[len-1])) {
174
cc = len;
175
sc->searchstr[0] = L'\0';
176
need_wildcard = TRUE;
177
}
178
179
sc->search_pos = cc;
180
if (need_wildcard) {
181
wcsncat_s(sc->searchstr, ARRAY_SIZE(sc->searchstr), L"*", _TRUNCATE);
182
}
183
}
184
185
static void find_insert_pos(const WCHAR *inputBuffer, int len, SEARCH_CONTEXT *sc)
186
{
187
int cc = len - 1;
188
189
/* Handle paths here. Find last '\\' or other delimiter.
190
* If not found then insert pos is the same as search pos.
191
*/
192
if (sc->user_specified_quotes) {
193
/* If the user specified quotes then treat the usual delimiters as literals
194
* and ignore them.
195
*/
196
while (cc > sc->search_pos && inputBuffer[cc] != L'\\') {
197
cc--;
198
}
199
200
if (inputBuffer[cc] == L'\"' || inputBuffer[cc] == L'\\') {
201
cc++;
202
}
203
} else {
204
while (cc > sc->search_pos && !wcschr(INTRA_PATH_DELIMS, inputBuffer[cc])) {
205
cc--;
206
}
207
208
if (inputBuffer[cc] == L'\"' || wcschr(INTRA_PATH_DELIMS, inputBuffer[cc])) {
209
cc++;
210
}
211
}
212
213
sc->insert_pos = cc;
214
}
215
216
/* Based on code in WCMD_list_directory.
217
* Could have used a linked-list, but array is more efficient for
218
* build once / read mostly.
219
*/
220
static void build_directory_entry_list(SEARCH_CONTEXT *sc)
221
{
222
HANDLE hff;
223
224
sc->entry_count = 0;
225
sc->current_entry = -1;
226
227
sc->fd = xalloc(sizeof(WIN32_FIND_DATAW));
228
229
WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(sc->searchstr));
230
hff = FindFirstFileW(sc->searchstr, &sc->fd[sc->entry_count]);
231
if (hff != INVALID_HANDLE_VALUE) {
232
do {
233
/* Always skip "." and ".." entries. */
234
if (wcscmp(sc->fd[sc->entry_count].cFileName, L".") && wcscmp(sc->fd[sc->entry_count].cFileName, L"..")) {
235
if (!sc->is_dir_search || sc->fd[sc->entry_count].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
236
sc->entry_count++;
237
sc->fd = xrealloc(sc->fd, (sc->entry_count + 1) * sizeof(WIN32_FIND_DATAW));
238
}
239
}
240
} while (FindNextFileW(hff, &sc->fd[sc->entry_count]));
241
242
FindClose(hff);
243
}
244
}
245
246
static void free_directory_entry_list(SEARCH_CONTEXT *sc)
247
{
248
free(sc->fd);
249
sc->fd = NULL;
250
sc->entry_count = 0;
251
sc->current_entry = 0;
252
}
253
254
static void get_next_matching_directory_entry(SEARCH_CONTEXT *sc, BOOL reverse)
255
{
256
if (reverse) {
257
sc->current_entry--;
258
if (sc->current_entry < 0) {
259
sc->current_entry = sc->entry_count - 1;
260
}
261
} else {
262
sc->current_entry++;
263
if (sc->current_entry >= sc->entry_count) {
264
sc->current_entry = 0;
265
}
266
}
267
}
268
269
static void update_input_buffer(WCHAR *inputBuffer, const DWORD inputBufferLength, SEARCH_CONTEXT *sc)
270
{
271
BOOL needQuotes = FALSE;
272
BOOL removeQuotes = FALSE;
273
int len;
274
275
/* We have found the insert position for the results. Terminate the string here. */
276
inputBuffer[sc->insert_pos] = L'\0';
277
278
/* If there are no spaces or delimiters in the path then we can remove quotes when appending
279
* the search result, unless the search result itself requires them.
280
*/
281
if (sc->have_quotes && !sc->user_specified_quotes && !wcspbrk(&inputBuffer[sc->search_pos], PATH_SEPARATION_DELIMS)) {
282
TRACE("removeQuotes = TRUE\n");
283
removeQuotes = TRUE;
284
}
285
286
/* Online documentation states that paths or filenames should be quoted if they are long
287
* file names or contain spaces. In practice, modern Windows seems to quote paths/files
288
* only if they contain spaces or delimiters.
289
*/
290
needQuotes = !!wcspbrk(sc->fd[sc->current_entry].cFileName, PATH_SEPARATION_DELIMS);
291
len = lstrlenW(inputBuffer);
292
/* Remove starting quotes, if able. */
293
if (removeQuotes && !needQuotes) {
294
/* Quotes are at search_pos-1 if they were already present at the start of this search.
295
* Otherwise quotes are at search_pos if we added them.
296
*/
297
if (inputBuffer[sc->search_pos] == L'"') {
298
memmove(&inputBuffer[sc->search_pos], &inputBuffer[sc->search_pos+1], (len - sc->search_pos) * sizeof(WCHAR));
299
sc->have_quotes = FALSE;
300
sc->insert_pos--;
301
}
302
} else
303
/* Add starting quotes if needed. */
304
if (needQuotes && !sc->have_quotes) {
305
if (len < inputBufferLength - 1) {
306
if (sc->search_pos <= len) {
307
memmove(&inputBuffer[sc->search_pos+1], &inputBuffer[sc->search_pos], (len - sc->search_pos + 1) * sizeof(WCHAR));
308
inputBuffer[sc->search_pos] = L'\"';
309
sc->have_quotes = TRUE;
310
sc->insert_pos++;
311
}
312
}
313
}
314
wcsncat_s(inputBuffer, inputBufferLength, sc->fd[sc->current_entry].cFileName, _TRUNCATE);
315
/* Add closing quotes if needed. */
316
if (needQuotes || (sc->have_quotes && !removeQuotes)) {
317
len = lstrlenW(inputBuffer);
318
if (len < inputBufferLength - 1) {
319
inputBuffer[len] = L'\"';
320
inputBuffer[len+1] = L'\0';
321
}
322
}
323
}
324
325
/* Intended as a mostly drop-in replacement for ReadConsole, but with tab-completion support.
326
*/
327
BOOL WCMD_read_console(const HANDLE hInput, WCHAR *inputBuffer, const DWORD inputBufferLength, LPDWORD numRead)
328
{
329
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
330
SEARCH_CONTEXT sc = {0};
331
WCHAR *lastResult = NULL;
332
CONSOLE_SCREEN_BUFFER_INFO startConsoleInfo, lastConsoleInfo;
333
DWORD numWritten;
334
UINT oldCurPos, curPos;
335
BOOL done = FALSE;
336
BOOL ret = FALSE;
337
int maxLen = 0; /* Track maximum length in case user fetches a long string from a previous iteration in history. */
338
339
if (!VerifyConsoleIoHandle(hInput) || !inputBuffer || !inputBufferLength) {
340
return FALSE;
341
}
342
343
/* Get starting cursor position and size */
344
if (!GetConsoleScreenBufferInfo(hOutput, &startConsoleInfo)) {
345
return FALSE;
346
}
347
lastConsoleInfo = startConsoleInfo;
348
349
*inputBuffer = L'\0';
350
curPos = 0;
351
352
while (!done) {
353
CONSOLE_READCONSOLE_CONTROL inputControl;
354
int len;
355
356
len = lstrlenW(inputBuffer);
357
358
/* Update current input display in console */
359
set_cursor_visible(hOutput, FALSE);
360
SetConsoleCursorPosition(hOutput, startConsoleInfo.dwCursorPosition);
361
362
WriteConsoleW(hOutput, inputBuffer, len, &numWritten, NULL);
363
if (maxLen > len) {
364
clear_console_characters(hOutput, maxLen - len, lastConsoleInfo.dwSize.X); /* width at time of last console update */
365
}
366
maxLen = len;
367
set_cursor_visible(hOutput, TRUE);
368
369
/* Remember current dimensions in case user resizes console window. */
370
GetConsoleScreenBufferInfo(hOutput, &lastConsoleInfo);
371
372
inputControl.nLength = sizeof(inputControl);
373
inputControl.nInitialChars = len;
374
inputControl.dwCtrlWakeupMask = (1 << '\t');
375
inputControl.dwControlKeyState = 0;
376
377
/* Allow room for NULL terminator. inputBufferLength is at least 1 due to check above. */
378
ret = ReadConsoleW(hInput, inputBuffer, inputBufferLength - 1, numRead, &inputControl);
379
if (!ret) {
380
break;
381
}
382
383
inputBuffer[*numRead] = L'\0';
384
TRACE("ReadConsole: [%lu][%s]\n", *numRead, wine_dbgstr_w(inputBuffer));
385
len = *numRead;
386
if (len > maxLen) {
387
maxLen = len;
388
}
389
oldCurPos = curPos;
390
curPos = 0;
391
while (curPos < len && inputBuffer[curPos] != L'\t') {
392
curPos++;
393
}
394
/* curPos is often numRead - 1, but not always, as in the case where history is retrieved
395
* and then user backspaces to somewhere mid-string and then hits Tab.
396
*/
397
TRACE("numRead: %lu, curPos: %u\n", *numRead, curPos);
398
399
switch (inputBuffer[curPos]) {
400
case L'\t':
401
TRACE("TAB: [%s]\n", wine_dbgstr_w(inputBuffer));
402
inputBuffer[curPos] = L'\0';
403
404
/* See if we need to conduct a new search. */
405
if (curPos != oldCurPos || (!lastResult || wcscmp(inputBuffer, lastResult))) {
406
/* New search */
407
408
sc.have_quotes = FALSE;
409
sc.user_specified_quotes = FALSE;
410
sc.search_pos = 0;
411
sc.insert_pos = 0;
412
413
build_search_string(inputBuffer, curPos, &sc);
414
TRACE("***** New search: [%s]\n", wine_dbgstr_w(sc.searchstr));
415
416
sc.is_dir_search = is_directory_operation(inputBuffer);
417
418
free_directory_entry_list(&sc);
419
build_directory_entry_list(&sc);
420
}
421
422
if (sc.entry_count) {
423
get_next_matching_directory_entry(&sc, (inputControl.dwControlKeyState & SHIFT_PRESSED) ? TRUE : FALSE);
424
425
/* If this is our first time through here for this search, we need to find the insert position
426
* for the results. Note that this is very likely not the same location as the search position.
427
*/
428
if (!sc.insert_pos) {
429
/* Replace the user's path with the stripped version (i.e. the search string), in case the user
430
* had quotes in unexpected places.
431
*/
432
wcsncpy_s(&inputBuffer[sc.search_pos], inputBufferLength - sc.search_pos, sc.searchstr, _TRUNCATE);
433
curPos = lstrlenW(inputBuffer);
434
435
find_insert_pos(inputBuffer, curPos, &sc);
436
}
437
438
/* Copy search results to input buffer. */
439
update_input_buffer(inputBuffer, inputBufferLength, &sc);
440
441
/* Save last result in case user edits existing portion of the string before hitting tab again. */
442
free(lastResult);
443
lastResult = xstrdupW(inputBuffer);
444
445
/* Update cursor position to end of buffer. */
446
curPos = lstrlenW(inputBuffer);
447
if (curPos > maxLen) {
448
maxLen = curPos;
449
}
450
}
451
break;
452
453
default:
454
TRACE("RETURN: [%s]\n", wine_dbgstr_w(inputBuffer));
455
done = TRUE;
456
break;
457
}
458
}
459
460
/* Cleanup any existing search results and related data before exiting. */
461
free_directory_entry_list(&sc);
462
free(lastResult);
463
464
return ret;
465
}
466
467
/*
468
* Returns a buffer for reading from/writing to file
469
* Never freed
470
*/
471
static char *get_file_buffer(void)
472
{
473
static char *output_bufA = NULL;
474
if (!output_bufA)
475
output_bufA = xalloc(MAX_WRITECONSOLE_SIZE);
476
return output_bufA;
477
}
478
479
/*******************************************************************
480
* WCMD_output_asis_len - send output to current standard output
481
*
482
* Output a formatted unicode string. Ideally this will go to the console
483
* and hence required WriteConsoleW to output it, however if file i/o is
484
* redirected, it needs to be WriteFile'd using OEM (not ANSI) format
485
*/
486
static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
487
{
488
DWORD nOut= 0;
489
DWORD res = 0;
490
491
/* If nothing to write, return (MORE does this sometimes) */
492
if (!len) return;
493
494
/* Try to write as unicode assuming it is to a console */
495
res = WriteConsoleW(device, message, len, &nOut, NULL);
496
497
/* If writing to console fails, assume it's file
498
i/o so convert to OEM codepage and output */
499
if (!res) {
500
BOOL usedDefaultChar = FALSE;
501
DWORD convertedChars;
502
char *buffer;
503
504
if (!unicodeOutput) {
505
UINT code_page;
506
507
if (!(buffer = get_file_buffer()))
508
return;
509
510
/* On Wine, GetConsoleOutputCP function fails
511
if Shell-no-window console is used */
512
code_page = GetConsoleOutputCP();
513
if (!code_page)
514
code_page = GetOEMCP();
515
516
/* Convert to OEM, then output */
517
convertedChars = WideCharToMultiByte(code_page, 0, message,
518
len, buffer, MAX_WRITECONSOLE_SIZE,
519
"?", &usedDefaultChar);
520
WriteFile(device, buffer, convertedChars,
521
&nOut, FALSE);
522
} else {
523
WriteFile(device, message, len*sizeof(WCHAR),
524
&nOut, FALSE);
525
}
526
}
527
return;
528
}
529
530
/*******************************************************************
531
* WCMD_output - send output to current standard output device.
532
*
533
*/
534
535
void WINAPIV WCMD_output (const WCHAR *format, ...) {
536
537
va_list ap;
538
WCHAR* string;
539
DWORD len;
540
541
va_start(ap,format);
542
string = NULL;
543
len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
544
format, 0, 0, (LPWSTR)&string, 0, &ap);
545
va_end(ap);
546
if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
547
WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
548
else
549
{
550
WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
551
LocalFree(string);
552
}
553
}
554
555
/*******************************************************************
556
* WCMD_output_stderr - send output to current standard error device.
557
*
558
*/
559
560
void WINAPIV WCMD_output_stderr (const WCHAR *format, ...) {
561
562
va_list ap;
563
WCHAR* string;
564
DWORD len;
565
566
va_start(ap,format);
567
string = NULL;
568
len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
569
format, 0, 0, (LPWSTR)&string, 0, &ap);
570
va_end(ap);
571
if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
572
WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
573
else
574
{
575
WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
576
LocalFree(string);
577
}
578
}
579
580
/*******************************************************************
581
* WCMD_format_string - allocate a buffer and format a string
582
*
583
*/
584
585
WCHAR* WINAPIV WCMD_format_string (const WCHAR *format, ...)
586
{
587
va_list ap;
588
WCHAR* string;
589
DWORD len;
590
591
va_start(ap,format);
592
len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
593
format, 0, 0, (LPWSTR)&string, 0, &ap);
594
va_end(ap);
595
if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
596
WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
597
string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
598
*string = 0;
599
}
600
return string;
601
}
602
603
void WCMD_enter_paged_mode(const WCHAR *msg)
604
{
605
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
606
607
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
608
/* Use console window dimensions, not screen buffer dimensions. */
609
max_height = consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1;
610
} else {
611
max_height = 25;
612
}
613
paged_mode = TRUE;
614
line_count = 0;
615
pagedMessage = (msg==NULL)? anykey : msg;
616
}
617
618
void WCMD_leave_paged_mode(void)
619
{
620
paged_mode = FALSE;
621
pagedMessage = NULL;
622
}
623
624
static BOOL has_pending_char_events(HANDLE h)
625
{
626
INPUT_RECORD ir;
627
DWORD count;
628
BOOL ret = FALSE;
629
630
while (!ret && GetNumberOfConsoleInputEvents(h, &count) && count)
631
{
632
/* FIXME could be racy if another thread/process gets the input record */
633
if (ReadConsoleInputA(h, &ir, 1, &count) && count)
634
ret = ir.EventType == KEY_EVENT &&
635
ir.Event.KeyEvent.bKeyDown &&
636
ir.Event.KeyEvent.uChar.AsciiChar;
637
}
638
return ret;
639
}
640
641
/***************************************************************************
642
* WCMD_wait_for_input
643
*
644
* Wait for input from a console/file.
645
* Used by commands like PAUSE and DIR /P that need to wait for user
646
* input before continuing.
647
*/
648
RETURN_CODE WCMD_wait_for_input(HANDLE hIn)
649
{
650
RETURN_CODE return_code;
651
DWORD oldmode;
652
DWORD count;
653
WCHAR key;
654
655
return_code = ERROR_INVALID_FUNCTION;
656
if (GetConsoleMode(hIn, &oldmode))
657
{
658
HANDLE h[2] = {hIn, control_c_event};
659
660
SetConsoleMode(hIn, oldmode & ~ENABLE_LINE_INPUT);
661
FlushConsoleInputBuffer(hIn);
662
while (return_code == ERROR_INVALID_FUNCTION)
663
{
664
switch (WaitForMultipleObjects(2, h, FALSE, INFINITE))
665
{
666
case WAIT_OBJECT_0:
667
if (has_pending_char_events(hIn))
668
return_code = NO_ERROR;
669
/* will make both hIn no long signaled, and also process the pending input record */
670
FlushConsoleInputBuffer(hIn);
671
break;
672
case WAIT_OBJECT_0 + 1:
673
return_code = STATUS_CONTROL_C_EXIT;
674
break;
675
default: break;
676
}
677
}
678
SetConsoleMode(hIn, oldmode);
679
}
680
else if (WCMD_ReadFile(hIn, &key, 1, &count) && count)
681
return_code = NO_ERROR;
682
else
683
return_code = ERROR_INVALID_FUNCTION;
684
return return_code;
685
}
686
687
/***************************************************************************
688
* WCMD_ReadFile
689
*
690
* Read characters in from a console/file, returning result in Unicode
691
*/
692
BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
693
{
694
DWORD numRead;
695
char *buffer;
696
697
/* Try to read from console as Unicode */
698
if (VerifyConsoleIoHandle(hIn) && ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL)) return TRUE;
699
700
/* We assume it's a file handle and read then convert from assumed OEM codepage */
701
if (!(buffer = get_file_buffer()))
702
return FALSE;
703
704
if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
705
return FALSE;
706
707
*charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
708
709
return TRUE;
710
}
711
712
/*******************************************************************
713
* WCMD_output_asis_handle
714
*
715
* Send output to specified handle without formatting e.g. when message contains '%'
716
*/
717
static RETURN_CODE WCMD_output_asis_handle(DWORD std_handle, const WCHAR *message)
718
{
719
RETURN_CODE return_code = NO_ERROR;
720
const WCHAR* ptr;
721
HANDLE handle = GetStdHandle(std_handle);
722
723
if (paged_mode)
724
{
725
do
726
{
727
for (ptr = message; *ptr && *ptr != L'\n'; ptr++) {}
728
if (*ptr == L'\n') ptr++;
729
WCMD_output_asis_len(message, ptr - message, handle);
730
if (++line_count >= max_height - 1)
731
{
732
line_count = 0;
733
WCMD_output_asis_len(pagedMessage, lstrlenW(pagedMessage), handle);
734
return_code = WCMD_wait_for_input(GetStdHandle(STD_INPUT_HANDLE));
735
WCMD_output_asis_len(L"\r\n", 2, handle);
736
}
737
} while (*(message = ptr) && !return_code);
738
} else
739
WCMD_output_asis_len(message, lstrlenW(message), handle);
740
741
return return_code;
742
}
743
744
/*******************************************************************
745
* WCMD_output_asis
746
*
747
* Send output to current standard output device, without formatting
748
* e.g. when message contains '%'
749
*/
750
RETURN_CODE WCMD_output_asis (const WCHAR *message) {
751
return WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
752
}
753
754
/*******************************************************************
755
* WCMD_output_asis_stderr
756
*
757
* Send output to current standard error device, without formatting
758
* e.g. when message contains '%'
759
*/
760
RETURN_CODE WCMD_output_asis_stderr (const WCHAR *message) {
761
return WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
762
}
763
764
/****************************************************************************
765
* WCMD_print_error
766
*
767
* Print the message for GetLastError
768
*/
769
770
void WCMD_print_error (void) {
771
LPVOID lpMsgBuf;
772
DWORD error_code;
773
int status;
774
775
error_code = GetLastError ();
776
status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
777
NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
778
if (!status) {
779
WINE_FIXME ("Cannot display message for error %ld, status %ld\n",
780
error_code, GetLastError());
781
return;
782
}
783
784
WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
785
GetStdHandle(STD_ERROR_HANDLE));
786
LocalFree (lpMsgBuf);
787
WCMD_output_asis_len(L"\r\n", lstrlenW(L"\r\n"), GetStdHandle(STD_ERROR_HANDLE));
788
return;
789
}
790
791
/******************************************************************************
792
* WCMD_show_prompt
793
*
794
* Display the prompt on STDout
795
*
796
*/
797
798
static void WCMD_show_prompt(void)
799
{
800
int status;
801
WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
802
WCHAR *p, *q;
803
DWORD len;
804
805
len = GetEnvironmentVariableW(L"PROMPT", prompt_string, ARRAY_SIZE(prompt_string));
806
if ((len == 0) || (len >= ARRAY_SIZE(prompt_string))) {
807
lstrcpyW(prompt_string, L"$P$G");
808
}
809
p = prompt_string;
810
q = out_string;
811
*q = '\0';
812
while (*p != '\0') {
813
if (*p != '$') {
814
*q++ = *p++;
815
*q = '\0';
816
}
817
else {
818
p++;
819
switch (towupper(*p)) {
820
case '$':
821
*q++ = '$';
822
break;
823
case 'A':
824
*q++ = '&';
825
break;
826
case 'B':
827
*q++ = '|';
828
break;
829
case 'C':
830
*q++ = '(';
831
break;
832
case 'D':
833
GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH - (q - out_string));
834
while (*q) q++;
835
break;
836
case 'E':
837
*q++ = '\x1b';
838
break;
839
case 'F':
840
*q++ = ')';
841
break;
842
case 'G':
843
*q++ = '>';
844
break;
845
case 'H':
846
*q++ = '\b';
847
break;
848
case 'L':
849
*q++ = '<';
850
break;
851
case 'N':
852
status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
853
if (status) {
854
*q++ = curdir[0];
855
}
856
break;
857
case 'P':
858
status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
859
if (status) {
860
lstrcatW (q, curdir);
861
while (*q) q++;
862
}
863
break;
864
case 'Q':
865
*q++ = '=';
866
break;
867
case 'S':
868
*q++ = ' ';
869
break;
870
case 'T':
871
GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
872
while (*q) q++;
873
break;
874
case 'V':
875
lstrcatW (q, version_string);
876
while (*q) q++;
877
break;
878
case '_':
879
*q++ = '\n';
880
break;
881
case '+':
882
if (pushd_directories) {
883
memset(q, '+', pushd_directories->u.stackdepth);
884
q = q + pushd_directories->u.stackdepth;
885
}
886
break;
887
}
888
p++;
889
*q = '\0';
890
}
891
}
892
WCMD_output_asis (out_string);
893
}
894
895
void *xrealloc(void *ptr, size_t size)
896
{
897
void *ret;
898
899
if (!(ret = realloc(ptr, size)))
900
{
901
ERR("Out of memory\n");
902
ExitProcess(1);
903
}
904
905
return ret;
906
}
907
908
/*************************************************************************
909
* WCMD_strsubstW
910
* Replaces a portion of a Unicode string with the specified string.
911
* It's up to the caller to ensure there is enough space in the
912
* destination buffer.
913
*/
914
WCHAR *WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
915
916
if (len < 0)
917
len=insert ? lstrlenW(insert) : 0;
918
if (start+len != next)
919
memmove(start+len, next, (lstrlenW(next) + 1) * sizeof(*next));
920
if (insert)
921
memcpy(start, insert, len * sizeof(*insert));
922
return start + len;
923
}
924
925
BOOL WCMD_get_fullpath(const WCHAR* in, SIZE_T outsize, WCHAR* out, WCHAR** start)
926
{
927
DWORD ret = GetFullPathNameW(in, outsize, out, start);
928
if (!ret) return FALSE;
929
if (ret > outsize)
930
{
931
WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_FILENAMETOOLONG));
932
return FALSE;
933
}
934
return TRUE;
935
}
936
937
/***************************************************************************
938
* WCMD_skip_leading_spaces
939
*
940
* Return a pointer to the first non-whitespace character of string.
941
* Does not modify the input string.
942
*/
943
WCHAR *WCMD_skip_leading_spaces(WCHAR *string)
944
{
945
while (*string == L' ' || *string == L'\t') string++;
946
return string;
947
}
948
949
static WCHAR *WCMD_strip_for_command_start(WCHAR *string)
950
{
951
while (*string == L' ' || *string == L'\t' || *string == L'@') string++;
952
return string;
953
}
954
955
/***************************************************************************
956
* WCMD_keyword_ws_found
957
*
958
* Checks if the string located at ptr matches a keyword (of length len)
959
* followed by a whitespace character (space or tab)
960
*/
961
BOOL WCMD_keyword_ws_found(const WCHAR *keyword, const WCHAR *ptr) {
962
const int len = lstrlenW(keyword);
963
return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
964
ptr, len, keyword, len) == CSTR_EQUAL)
965
&& ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
966
}
967
968
/*************************************************************************
969
* WCMD_strip_quotes
970
*
971
* Remove first and last quote WCHARacters, preserving all other text
972
* Returns the location of the final quote
973
*/
974
WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
975
WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
976
while((*dest=*src) != '\0') {
977
if (*src=='\"')
978
lastq=dest;
979
dest++; src++;
980
}
981
lastquote = lastq;
982
if (lastq) {
983
dest=lastq++;
984
while ((*dest++=*lastq++) != 0)
985
;
986
}
987
return lastquote;
988
}
989
990
static inline int read_int_in_range(const WCHAR *from, WCHAR **after, int low, int high)
991
{
992
int val = wcstol(from, after, 10);
993
val += (val < 0) ? high : low;
994
return val <= low ? low : (val >= high ? high : val);
995
}
996
997
/*************************************************************************
998
* WCMD_expand_envvar
999
*
1000
* Expands environment variables, allowing for WCHARacter substitution
1001
*/
1002
static WCHAR *WCMD_expand_envvar(WCHAR *start)
1003
{
1004
WCHAR *endOfVar = NULL, *s;
1005
WCHAR *colonpos = NULL;
1006
WCHAR thisVar[MAXSTRING];
1007
WCHAR thisVarContents[MAXSTRING];
1008
int len;
1009
1010
WINE_TRACE("Expanding: %s\n", wine_dbgstr_w(start));
1011
1012
endOfVar = wcschr(start + 1, *start);
1013
if (!endOfVar)
1014
/* no corresponding closing char... either skip startchar in batch, or leave untouched otherwise */
1015
return WCMD_is_in_context(NULL) ? WCMD_strsubstW(start, start + 1, NULL, 0) : start + 1;
1016
1017
memcpy(thisVar, start + 1, (endOfVar - start - 1) * sizeof(WCHAR));
1018
thisVar[endOfVar - start - 1] = L'\0';
1019
colonpos = wcschr(thisVar, L':');
1020
1021
/* If there's complex substitution, just need %var% for now
1022
* to get the expanded data to play with
1023
*/
1024
if (colonpos) colonpos[0] = L'\0';
1025
1026
TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1027
1028
/* env variables (when set) have priority over magic env variables */
1029
len = GetEnvironmentVariableW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents));
1030
if (!len)
1031
{
1032
if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"ERRORLEVEL", -1) == CSTR_EQUAL)
1033
len = wsprintfW(thisVarContents, L"%d", errorlevel);
1034
else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"DATE", -1) == CSTR_EQUAL)
1035
len = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1036
NULL, thisVarContents, ARRAY_SIZE(thisVarContents));
1037
else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"TIME", -1) == CSTR_EQUAL)
1038
len = GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1039
NULL, thisVarContents, ARRAY_SIZE(thisVarContents));
1040
else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"CD", -1) == CSTR_EQUAL)
1041
len = GetCurrentDirectoryW(ARRAY_SIZE(thisVarContents), thisVarContents);
1042
else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"RANDOM", -1) == CSTR_EQUAL)
1043
len = wsprintfW(thisVarContents, L"%d", rand() % 32768);
1044
}
1045
/* Restore complex bit */
1046
if (colonpos) colonpos[0] = L':';
1047
1048
/* In a batch program, unknown env vars are replaced with nothing,
1049
* note syntax %garbage:1,3% results in anything after the ':'
1050
* except the %
1051
* From the command line, you just get back what you entered
1052
*/
1053
if (!len)
1054
{
1055
/* Command line - just ignore this */
1056
if (!WCMD_is_in_context(NULL)) return endOfVar + 1;
1057
1058
/* Batch - replace unknown env var with nothing */
1059
if (colonpos == NULL)
1060
return WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
1061
if (colonpos == thisVar)
1062
return WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
1063
return WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
1064
}
1065
1066
/* See if we need to do complex substitution (any ':'s) */
1067
if (colonpos == NULL)
1068
return WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
1069
1070
/*
1071
Handle complex substitutions:
1072
xxx=yyy (replace xxx with yyy)
1073
*xxx=yyy (replace up to and including xxx with yyy)
1074
~x (from x WCHARs in)
1075
~-x (from x WCHARs from the end)
1076
~x,y (from x WCHARs in for y WCHARacters)
1077
~x,-y (from x WCHARs in until y WCHARacters from the end)
1078
*/
1079
1080
/* ~ is substring manipulation */
1081
if (colonpos[1] == L'~')
1082
{
1083
int substr_beg, substr_end;
1084
WCHAR *ptr;
1085
1086
substr_beg = read_int_in_range(colonpos + 2, &ptr, 0, len);
1087
if (*ptr == L',')
1088
substr_end = read_int_in_range(ptr + 1, &ptr, substr_beg, len);
1089
else
1090
substr_end = len;
1091
if (*ptr == L'\0')
1092
return WCMD_strsubstW(start, endOfVar + 1, &thisVarContents[substr_beg], substr_end - substr_beg);
1093
/* error, remove enclosing % pair (in place) */
1094
memmove(start, start + 1, (endOfVar - start - 1) * sizeof(WCHAR));
1095
return WCMD_strsubstW(endOfVar - 1, endOfVar + 1, NULL, 0);
1096
/* search and replace manipulation */
1097
} else {
1098
WCHAR *equalspos = wcschr(colonpos, L'=');
1099
WCHAR *replacewith = equalspos+1;
1100
WCHAR *found = NULL;
1101
WCHAR *searchIn;
1102
WCHAR *searchFor;
1103
WCHAR *ret;
1104
1105
if (equalspos == NULL) return start+1;
1106
s = xstrdupW(endOfVar + 1);
1107
1108
*equalspos = 0x00;
1109
1110
/* Since we need to be case insensitive, copy the 2 buffers */
1111
searchIn = xstrdupW(thisVarContents);
1112
CharUpperBuffW(searchIn, lstrlenW(thisVarContents));
1113
searchFor = xstrdupW(colonpos + 1);
1114
CharUpperBuffW(searchFor, lstrlenW(colonpos+1));
1115
1116
/* Handle wildcard case */
1117
if (*(colonpos+1) == '*') {
1118
/* Search for string to replace */
1119
found = wcsstr(searchIn, searchFor+1);
1120
1121
if (found) {
1122
/* Do replacement */
1123
lstrcpyW(start, replacewith);
1124
lstrcatW(start, thisVarContents + (found-searchIn) + lstrlenW(searchFor+1));
1125
ret = start + wcslen(start);
1126
lstrcatW(start, s);
1127
} else {
1128
/* Copy as is */
1129
lstrcpyW(start, thisVarContents);
1130
ret = start + wcslen(start);
1131
lstrcatW(start, s);
1132
}
1133
} else {
1134
/* Loop replacing all instances */
1135
WCHAR *lastFound = searchIn;
1136
WCHAR *outputposn = start;
1137
1138
*start = 0x00;
1139
while ((found = wcsstr(lastFound, searchFor))) {
1140
lstrcpynW(outputposn,
1141
thisVarContents + (lastFound-searchIn),
1142
(found - lastFound)+1);
1143
outputposn = outputposn + (found - lastFound);
1144
lstrcatW(outputposn, replacewith);
1145
outputposn = outputposn + lstrlenW(replacewith);
1146
lastFound = found + lstrlenW(searchFor);
1147
}
1148
lstrcatW(outputposn,
1149
thisVarContents + (lastFound-searchIn));
1150
ret = outputposn + wcslen(outputposn);
1151
lstrcatW(outputposn, s);
1152
}
1153
free(s);
1154
free(searchIn);
1155
free(searchFor);
1156
return ret;
1157
}
1158
}
1159
1160
/*****************************************************************************
1161
* Expand the command. Native expands lines from batch programs as they are
1162
* read in and not again, except for 'for' variable substitution.
1163
* eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
1164
* atExecute is TRUE when the expansion is occurring as the command is executed
1165
* rather than at parse time, i.e. delayed expansion and for loops need to be
1166
* processed
1167
*/
1168
static void handleExpansion(WCHAR *cmd, BOOL atExecute) {
1169
1170
/* For commands in a context (batch program): */
1171
/* Expand environment variables in a batch file %{0-9} first */
1172
/* including support for any ~ modifiers */
1173
/* Additionally: */
1174
/* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
1175
/* names allowing environment variable overrides */
1176
/* NOTE: To support the %PATH:xxx% syntax, also perform */
1177
/* manual expansion of environment variables here */
1178
1179
WCHAR *p = cmd;
1180
WCHAR *t;
1181
int i;
1182
BOOL delayed = atExecute ? delayedsubst : FALSE;
1183
WCHAR *delayedp = NULL;
1184
WCHAR startchar = '%';
1185
WCHAR *normalp;
1186
1187
/* Display the FOR variables in effect */
1188
for (i=0;i<ARRAY_SIZE(forloopcontext->variable);i++) {
1189
if (forloopcontext->variable[i]) {
1190
TRACE("FOR variable context: %s = '%s'\n",
1191
debugstr_for_var((WCHAR)i), wine_dbgstr_w(forloopcontext->variable[i]));
1192
}
1193
}
1194
1195
for (;;)
1196
{
1197
/* Find the next environment variable delimiter */
1198
normalp = wcschr(p, '%');
1199
if (delayed) delayedp = wcschr(p, '!');
1200
if (!normalp) p = delayedp;
1201
else if (!delayedp) p = normalp;
1202
else p = min(p,delayedp);
1203
if (!p) break;
1204
startchar = *p;
1205
1206
WINE_TRACE("Translate command:%s %d (at: %s)\n",
1207
wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
1208
i = *(p+1) - '0';
1209
1210
/* handle consecutive % or ! */
1211
if ((!atExecute || startchar == L'!') && p[1] == startchar) {
1212
if (WCMD_is_in_context(NULL)) WCMD_strsubstW(p, p + 1, NULL, 0);
1213
if (!WCMD_is_in_context(NULL) || startchar == L'%') p++;
1214
/* Replace %~ modifications if in batch program */
1215
} else if (p[1] == L'~' && p[2] && !iswspace(p[2])) {
1216
WCMD_HandleTildeModifiers(&p, atExecute);
1217
p++;
1218
1219
/* Replace use of %0...%9 if in batch program*/
1220
} else if (!atExecute && WCMD_is_in_context(NULL) && (i >= 0) && (i <= 9) && startchar == L'%') {
1221
t = WCMD_parameter(context->command, i + context->shift_count[i],
1222
NULL, TRUE, TRUE);
1223
p = WCMD_strsubstW(p, p+2, t, -1);
1224
1225
/* Replace use of %* if in batch program*/
1226
} else if (!atExecute && WCMD_is_in_context(NULL) && p[1] == L'*' && startchar == L'%') {
1227
WCHAR *startOfParms = NULL;
1228
WCHAR *thisParm = WCMD_parameter(context->command, 0, &startOfParms, TRUE, TRUE);
1229
if (startOfParms != NULL) {
1230
startOfParms += lstrlenW(thisParm);
1231
while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
1232
p = WCMD_strsubstW(p, p+2, startOfParms, -1);
1233
} else
1234
p = WCMD_strsubstW(p, p+2, NULL, 0);
1235
1236
} else {
1237
if (startchar == L'%' && for_var_is_valid(p[1]) && forloopcontext->variable[p[1]]) {
1238
/* Replace the 2 characters, % and for variable character */
1239
p = WCMD_strsubstW(p, p + 2, forloopcontext->variable[p[1]], -1);
1240
} else if (!atExecute || startchar == L'!') {
1241
BOOL first = p == cmd;
1242
p = WCMD_expand_envvar(p);
1243
/* FIXME: maybe this more likely calls for a specific handling of first arg? */
1244
if (WCMD_is_in_context(NULL) && startchar == L'!' && first)
1245
{
1246
WCHAR *last;
1247
for (last = p; *last == startchar; last++) {}
1248
p = WCMD_strsubstW(p, last, NULL, 0);
1249
}
1250
/* In a FOR loop, see if this is the variable to replace */
1251
} else { /* Ignore %'s on second pass of batch program */
1252
p++;
1253
}
1254
}
1255
}
1256
}
1257
1258
1259
/*******************************************************************
1260
* WCMD_parse - parse a command into parameters and qualifiers.
1261
*
1262
* On exit, all qualifiers are concatenated into q, the first string
1263
* not beginning with "/" is in p1 and the
1264
* second in p2. Any subsequent non-qualifier strings are lost.
1265
* Parameters in quotes are handled.
1266
*/
1267
static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
1268
{
1269
int p = 0;
1270
1271
*q = *p1 = *p2 = '\0';
1272
while (TRUE) {
1273
switch (*s) {
1274
case '/':
1275
*q++ = *s++;
1276
while ((*s != '\0') && (*s != ' ') && *s != '/') {
1277
*q++ = towupper (*s++);
1278
}
1279
*q = '\0';
1280
break;
1281
case ' ':
1282
case '\t':
1283
s++;
1284
break;
1285
case '"':
1286
s++;
1287
while ((*s != '\0') && (*s != '"')) {
1288
if (p == 0) *p1++ = *s++;
1289
else if (p == 1) *p2++ = *s++;
1290
else s++;
1291
}
1292
if (p == 0) *p1 = '\0';
1293
if (p == 1) *p2 = '\0';
1294
p++;
1295
if (*s == '"') s++;
1296
break;
1297
case '\0':
1298
return;
1299
default:
1300
while ((*s != '\0') && (*s != ' ') && (*s != '\t')
1301
&& (*s != '=') && (*s != ',') ) {
1302
if (p == 0) *p1++ = *s++;
1303
else if (p == 1) *p2++ = *s++;
1304
else s++;
1305
}
1306
/* Skip concurrent parms */
1307
while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
1308
1309
if (p == 0) *p1 = '\0';
1310
if (p == 1) *p2 = '\0';
1311
p++;
1312
}
1313
}
1314
}
1315
1316
void WCMD_expand(const WCHAR *src, WCHAR *dst)
1317
{
1318
wcscpy(dst, src);
1319
handleExpansion(dst, FALSE);
1320
WCMD_parse(dst, quals, param1, param2);
1321
}
1322
1323
/* ============================== */
1324
/* Data structures for commands */
1325
/* ============================== */
1326
1327
static void redirection_dispose_list(CMD_REDIRECTION *redir)
1328
{
1329
while (redir)
1330
{
1331
CMD_REDIRECTION *next = redir->next;
1332
free(redir);
1333
redir = next;
1334
}
1335
}
1336
1337
static CMD_REDIRECTION *redirection_create_file(enum CMD_REDIRECTION_KIND kind, unsigned fd, const WCHAR *file)
1338
{
1339
size_t len = wcslen(file) + 1;
1340
CMD_REDIRECTION *redir = xalloc(offsetof(CMD_REDIRECTION, file[len]));
1341
1342
redir->kind = kind;
1343
redir->fd = fd;
1344
memcpy(redir->file, file, len * sizeof(WCHAR));
1345
redir->next = NULL;
1346
1347
return redir;
1348
}
1349
1350
static CMD_REDIRECTION *redirection_create_clone(unsigned fd, unsigned fd_clone)
1351
{
1352
CMD_REDIRECTION *redir = xalloc(sizeof(*redir));
1353
1354
redir->kind = REDIR_WRITE_CLONE;
1355
redir->fd = fd;
1356
redir->clone = fd_clone;
1357
redir->next = NULL;
1358
1359
return redir;
1360
}
1361
1362
static const char *debugstr_redirection(const CMD_REDIRECTION *redir)
1363
{
1364
switch (redir->kind)
1365
{
1366
case REDIR_READ_FROM:
1367
return wine_dbg_sprintf("%u< (%ls)", redir->fd, redir->file);
1368
case REDIR_WRITE_TO:
1369
return wine_dbg_sprintf("%u> (%ls)", redir->fd, redir->file);
1370
case REDIR_WRITE_APPEND:
1371
return wine_dbg_sprintf("%u>> (%ls)", redir->fd, redir->file);
1372
case REDIR_WRITE_CLONE:
1373
return wine_dbg_sprintf("%u>&%u", redir->fd, redir->clone);
1374
default:
1375
return "-^-";
1376
}
1377
}
1378
1379
static WCHAR *command_create(const WCHAR *ptr, size_t len)
1380
{
1381
WCHAR *command = xalloc((len + 1) * sizeof(WCHAR));
1382
memcpy(command, ptr, len * sizeof(WCHAR));
1383
command[len] = L'\0';
1384
return command;
1385
}
1386
1387
static void for_control_dispose(CMD_FOR_CONTROL *for_ctrl)
1388
{
1389
free((void*)for_ctrl->set);
1390
switch (for_ctrl->operator)
1391
{
1392
case CMD_FOR_FILE_SET:
1393
free((void*)for_ctrl->delims);
1394
free((void*)for_ctrl->tokens);
1395
break;
1396
case CMD_FOR_FILETREE:
1397
free((void*)for_ctrl->root_dir);
1398
break;
1399
default:
1400
break;
1401
}
1402
}
1403
1404
const char *debugstr_for_control(const CMD_FOR_CONTROL *for_ctrl)
1405
{
1406
static const char* for_ctrl_strings[] = {"tree", "file", "numbers"};
1407
const char *flags, *options;
1408
1409
if (for_ctrl->operator >= ARRAY_SIZE(for_ctrl_strings))
1410
{
1411
FIXME("Unexpected operator\n");
1412
return wine_dbg_sprintf("<<%u>>", for_ctrl->operator);
1413
}
1414
1415
if (for_ctrl->flags)
1416
flags = wine_dbg_sprintf("flags=%s%s%s ",
1417
(for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) ? "~recurse" : "",
1418
(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_FILES) ? "~+files" : "",
1419
(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES) ? "~+dirs" : "");
1420
else
1421
flags = "";
1422
switch (for_ctrl->operator)
1423
{
1424
case CMD_FOR_FILETREE:
1425
options = wine_dbg_sprintf("root=(%ls) ", for_ctrl->root_dir);
1426
break;
1427
case CMD_FOR_FILE_SET:
1428
{
1429
WCHAR eol_buf[4] = {L'\'', for_ctrl->eol, L'\'', L'\0'};
1430
const WCHAR *eol = for_ctrl->eol ? eol_buf : L"<nul>";
1431
options = wine_dbg_sprintf("eol=%ls skip=%d use_backq=%c delims=%s tokens=%s ",
1432
eol, for_ctrl->num_lines_to_skip, for_ctrl->use_backq ? 'Y' : 'N',
1433
wine_dbgstr_w(for_ctrl->delims), wine_dbgstr_w(for_ctrl->tokens));
1434
}
1435
break;
1436
default:
1437
options = "";
1438
break;
1439
}
1440
return wine_dbg_sprintf("[FOR] %s %s%s%s (%ls)",
1441
for_ctrl_strings[for_ctrl->operator], flags, options,
1442
debugstr_for_var(for_ctrl->variable_index), for_ctrl->set);
1443
}
1444
1445
static void for_control_create(enum for_control_operator for_op, unsigned flags, const WCHAR *options, unsigned varidx, CMD_FOR_CONTROL *for_ctrl)
1446
{
1447
for_ctrl->operator = for_op;
1448
for_ctrl->flags = flags;
1449
for_ctrl->variable_index = varidx;
1450
for_ctrl->set = NULL;
1451
switch (for_ctrl->operator)
1452
{
1453
case CMD_FOR_FILETREE:
1454
for_ctrl->root_dir = options && *options ? xstrdupW(options) : NULL;
1455
break;
1456
default:
1457
break;
1458
}
1459
}
1460
1461
static void for_control_create_fileset(unsigned flags, unsigned varidx, WCHAR eol, int num_lines_to_skip, BOOL use_backq,
1462
const WCHAR *delims, const WCHAR *tokens,
1463
CMD_FOR_CONTROL *for_ctrl)
1464
{
1465
for_ctrl->operator = CMD_FOR_FILE_SET;
1466
for_ctrl->flags = flags;
1467
for_ctrl->variable_index = varidx;
1468
for_ctrl->set = NULL;
1469
1470
for_ctrl->eol = eol;
1471
for_ctrl->use_backq = use_backq;
1472
for_ctrl->num_lines_to_skip = num_lines_to_skip;
1473
for_ctrl->delims = delims;
1474
for_ctrl->tokens = tokens;
1475
}
1476
1477
static void for_control_append_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *set)
1478
{
1479
if (for_ctrl->set)
1480
{
1481
for_ctrl->set = xrealloc((void*)for_ctrl->set,
1482
(wcslen(for_ctrl->set) + 1 + wcslen(set) + 1) * sizeof(WCHAR));
1483
wcscat((WCHAR*)for_ctrl->set, L" ");
1484
wcscat((WCHAR*)for_ctrl->set, set);
1485
}
1486
else
1487
for_ctrl->set = xstrdupW(set);
1488
}
1489
1490
void if_condition_dispose(CMD_IF_CONDITION *cond)
1491
{
1492
switch (cond->op)
1493
{
1494
case CMD_IF_ERRORLEVEL:
1495
case CMD_IF_EXIST:
1496
case CMD_IF_DEFINED:
1497
free((void*)cond->operand);
1498
break;
1499
case CMD_IF_BINOP_EQUAL:
1500
case CMD_IF_BINOP_LSS:
1501
case CMD_IF_BINOP_LEQ:
1502
case CMD_IF_BINOP_EQU:
1503
case CMD_IF_BINOP_NEQ:
1504
case CMD_IF_BINOP_GEQ:
1505
case CMD_IF_BINOP_GTR:
1506
free((void*)cond->left);
1507
free((void*)cond->right);
1508
break;
1509
}
1510
}
1511
1512
static BOOL if_condition_parse(WCHAR *start, WCHAR **end, CMD_IF_CONDITION *cond)
1513
{
1514
WCHAR *param_start;
1515
const WCHAR *param_copy;
1516
int narg = 0;
1517
1518
if (cond) memset(cond, 0, sizeof(*cond));
1519
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1520
/* /I is the only option supported */
1521
if (!wcsicmp(param_copy, L"/I"))
1522
{
1523
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1524
if (cond) cond->case_insensitive = 1;
1525
}
1526
if (!wcsicmp(param_copy, L"NOT"))
1527
{
1528
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1529
if (cond) cond->negated = 1;
1530
}
1531
if (!wcsicmp(param_copy, L"errorlevel"))
1532
{
1533
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1534
if (cond) cond->op = CMD_IF_ERRORLEVEL;
1535
if (cond) cond->operand = wcsdup(param_copy);
1536
}
1537
else if (!wcsicmp(param_copy, L"exist"))
1538
{
1539
param_copy = WCMD_parameter(start, narg++, &param_start, FALSE, FALSE);
1540
if (cond) cond->op = CMD_IF_EXIST;
1541
if (cond) cond->operand = wcsdup(param_copy);
1542
}
1543
else if (!wcsicmp(param_copy, L"defined"))
1544
{
1545
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1546
if (cond) cond->op = CMD_IF_DEFINED;
1547
if (cond) cond->operand = wcsdup(param_copy);
1548
}
1549
else /* comparison operation */
1550
{
1551
if (*param_copy == L'\0') return FALSE;
1552
param_copy = WCMD_parameter(start, narg - 1, &param_start, TRUE, FALSE);
1553
if (cond) cond->left = wcsdup(param_copy);
1554
1555
start = WCMD_skip_leading_spaces(param_start + wcslen(param_copy));
1556
1557
/* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
1558
if (start[0] == L'=' && start[1] == L'=')
1559
{
1560
start += 2; /* == */
1561
if (cond) cond->op = CMD_IF_BINOP_EQUAL;
1562
}
1563
else
1564
{
1565
static struct
1566
{
1567
const WCHAR *name;
1568
enum cond_operator binop;
1569
}
1570
allowed_operators[] = {{L"lss", CMD_IF_BINOP_LSS},
1571
{L"leq", CMD_IF_BINOP_LEQ},
1572
{L"equ", CMD_IF_BINOP_EQU},
1573
{L"neq", CMD_IF_BINOP_NEQ},
1574
{L"geq", CMD_IF_BINOP_GEQ},
1575
{L"gtr", CMD_IF_BINOP_GTR},
1576
};
1577
int i;
1578
1579
param_copy = WCMD_parameter(start, 0, &param_start, FALSE, FALSE);
1580
for (i = 0; i < ARRAY_SIZE(allowed_operators); i++)
1581
if (!wcsicmp(param_copy, allowed_operators[i].name)) break;
1582
if (i == ARRAY_SIZE(allowed_operators))
1583
{
1584
if (cond) free((void*)cond->left);
1585
return FALSE;
1586
}
1587
if (cond) cond->op = allowed_operators[i].binop;
1588
start += wcslen(param_copy);
1589
}
1590
1591
param_copy = WCMD_parameter(start, 0, &param_start, TRUE, FALSE);
1592
if (*param_copy == L'\0')
1593
{
1594
if (cond) free((void*)cond->left);
1595
return FALSE;
1596
}
1597
if (cond) cond->right = wcsdup(param_copy);
1598
1599
start = param_start + wcslen(param_copy);
1600
narg = 0;
1601
}
1602
/* check all remaning args are present, and compute pointer to end of condition */
1603
param_copy = WCMD_parameter(start, narg, end, TRUE, FALSE);
1604
return cond || *param_copy != L'\0';
1605
}
1606
1607
static const char *debugstr_if_condition(const CMD_IF_CONDITION *cond)
1608
{
1609
const char *header = wine_dbg_sprintf("{{%s%s", cond->negated ? "not " : "", cond->case_insensitive ? "nocase " : "");
1610
1611
switch (cond->op)
1612
{
1613
case CMD_IF_ERRORLEVEL: return wine_dbg_sprintf("%serrorlevel %ls}}", header, cond->operand);
1614
case CMD_IF_EXIST: return wine_dbg_sprintf("%sexist %ls}}", header, cond->operand);
1615
case CMD_IF_DEFINED: return wine_dbg_sprintf("%sdefined %ls}}", header, cond->operand);
1616
case CMD_IF_BINOP_EQUAL: return wine_dbg_sprintf("%s%ls == %ls}}", header, cond->left, cond->right);
1617
1618
case CMD_IF_BINOP_LSS: return wine_dbg_sprintf("%s%ls LSS %ls}}", header, cond->left, cond->right);
1619
case CMD_IF_BINOP_LEQ: return wine_dbg_sprintf("%s%ls LEQ %ls}}", header, cond->left, cond->right);
1620
case CMD_IF_BINOP_EQU: return wine_dbg_sprintf("%s%ls EQU %ls}}", header, cond->left, cond->right);
1621
case CMD_IF_BINOP_NEQ: return wine_dbg_sprintf("%s%ls NEQ %ls}}", header, cond->left, cond->right);
1622
case CMD_IF_BINOP_GEQ: return wine_dbg_sprintf("%s%ls GEQ %ls}}", header, cond->left, cond->right);
1623
case CMD_IF_BINOP_GTR: return wine_dbg_sprintf("%s%ls GTR %ls}}", header, cond->left, cond->right);
1624
default:
1625
FIXME("Unexpected condition operator %u\n", cond->op);
1626
return "{{}}";
1627
}
1628
}
1629
1630
/***************************************************************************
1631
* node_dispose_tree
1632
*
1633
* Frees the storage held for a parsed command line
1634
* - This is not done in the process_commands, as eventually the current
1635
* pointer will be modified within the commands, and hence a single free
1636
* routine is simpler
1637
*/
1638
void node_dispose_tree(CMD_NODE *node)
1639
{
1640
/* Loop through the commands, freeing them one by one */
1641
if (!node) return;
1642
switch (node->op)
1643
{
1644
case CMD_SINGLE:
1645
free(node->command);
1646
break;
1647
case CMD_CONCAT:
1648
case CMD_PIPE:
1649
case CMD_ONFAILURE:
1650
case CMD_ONSUCCESS:
1651
node_dispose_tree(node->left);
1652
node_dispose_tree(node->right);
1653
break;
1654
case CMD_IF:
1655
if_condition_dispose(&node->condition);
1656
node_dispose_tree(node->then_block);
1657
node_dispose_tree(node->else_block);
1658
break;
1659
case CMD_FOR:
1660
for_control_dispose(&node->for_ctrl);
1661
node_dispose_tree(node->do_block);
1662
break;
1663
}
1664
redirection_dispose_list(node->redirects);
1665
free(node);
1666
}
1667
1668
static CMD_NODE *node_create_single(WCHAR *c)
1669
{
1670
CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1671
1672
new->op = CMD_SINGLE;
1673
new->command = c;
1674
new->redirects = NULL;
1675
1676
return new;
1677
}
1678
1679
static CMD_NODE *node_create_binary(CMD_OPERATOR op, CMD_NODE *l, CMD_NODE *r)
1680
{
1681
CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1682
1683
new->op = op;
1684
new->left = l;
1685
new->right = r;
1686
new->redirects = NULL;
1687
1688
return new;
1689
}
1690
1691
static CMD_NODE *node_create_if(CMD_IF_CONDITION *cond, CMD_NODE *then_block, CMD_NODE *else_block)
1692
{
1693
CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1694
1695
new->op = CMD_IF;
1696
new->condition = *cond;
1697
new->then_block = then_block;
1698
new->else_block = else_block;
1699
new->redirects = NULL;
1700
1701
return new;
1702
}
1703
1704
static CMD_NODE *node_create_for(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *do_block)
1705
{
1706
CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1707
1708
new->op = CMD_FOR;
1709
new->for_ctrl = *for_ctrl;
1710
new->do_block = do_block;
1711
new->redirects = NULL;
1712
1713
return new;
1714
}
1715
1716
static void init_msvcrt_io_block(STARTUPINFOW* st)
1717
{
1718
STARTUPINFOW st_p;
1719
/* fetch the parent MSVCRT info block if any, so that the child can use the
1720
* same handles as its grand-father
1721
*/
1722
st_p.cb = sizeof(STARTUPINFOW);
1723
GetStartupInfoW(&st_p);
1724
st->cbReserved2 = st_p.cbReserved2;
1725
st->lpReserved2 = st_p.lpReserved2;
1726
if (st_p.cbReserved2 && st_p.lpReserved2)
1727
{
1728
unsigned num = *(unsigned*)st_p.lpReserved2;
1729
char* flags;
1730
HANDLE* handles;
1731
BYTE *ptr;
1732
size_t sz;
1733
1734
/* Override the entries for fd 0,1,2 if we happened
1735
* to change those std handles (this depends on the way cmd sets
1736
* its new input & output handles)
1737
*/
1738
sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
1739
ptr = xalloc(sz);
1740
flags = (char*)(ptr + sizeof(unsigned));
1741
handles = (HANDLE*)(flags + num * sizeof(char));
1742
1743
memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
1744
st->cbReserved2 = sz;
1745
st->lpReserved2 = ptr;
1746
1747
#define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
1748
if (num <= 0 || (flags[0] & WX_OPEN))
1749
{
1750
handles[0] = GetStdHandle(STD_INPUT_HANDLE);
1751
flags[0] |= WX_OPEN;
1752
}
1753
if (num <= 1 || (flags[1] & WX_OPEN))
1754
{
1755
handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
1756
flags[1] |= WX_OPEN;
1757
}
1758
if (num <= 2 || (flags[2] & WX_OPEN))
1759
{
1760
handles[2] = GetStdHandle(STD_ERROR_HANDLE);
1761
flags[2] |= WX_OPEN;
1762
}
1763
#undef WX_OPEN
1764
}
1765
}
1766
1767
/* Attempt to open a file at a known path. */
1768
static RETURN_CODE run_external_full_path(const WCHAR *file, WCHAR *full_cmdline)
1769
{
1770
STARTUPINFOW si = {.cb = sizeof(si)};
1771
DWORD console, exit_code;
1772
WCHAR exe_path[MAX_PATH];
1773
PROCESS_INFORMATION pi;
1774
SHFILEINFOW psfi;
1775
HANDLE handle;
1776
BOOL ret;
1777
1778
TRACE("%s\n", debugstr_w(file));
1779
1780
if ((INT_PTR)FindExecutableW(file, NULL, exe_path) < 32)
1781
console = 0;
1782
else
1783
console = SHGetFileInfoW(exe_path, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1784
1785
init_msvcrt_io_block(&si);
1786
ret = CreateProcessW(file, full_cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
1787
free(si.lpReserved2);
1788
1789
if (ret)
1790
{
1791
CloseHandle(pi.hThread);
1792
handle = pi.hProcess;
1793
}
1794
else
1795
{
1796
SHELLEXECUTEINFOW sei = {.cbSize = sizeof(sei)};
1797
WCHAR *args;
1798
1799
WCMD_parameter(full_cmdline, 1, &args, FALSE, TRUE);
1800
/* FIXME: when the file extension is not registered,
1801
* native cmd does popup a dialog box to register an app for this extension.
1802
* Also, ShellExecuteW returns before the dialog box is closed.
1803
* Moreover, on Wine, displaying a dialog box without a message loop
1804
* (in cmd.exe) blocks the dialog.
1805
* So, until the above bits are solved, don't display any dialog box.
1806
*/
1807
sei.fMask = SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
1808
sei.lpFile = file;
1809
sei.lpParameters = args;
1810
sei.nShow = SW_SHOWNORMAL;
1811
1812
if (ShellExecuteExW(&sei) && (INT_PTR)sei.hInstApp >= 32)
1813
{
1814
handle = sei.hProcess;
1815
}
1816
else
1817
{
1818
errorlevel = GetLastError();
1819
return errorlevel;
1820
}
1821
}
1822
1823
if (context || (console && !HIWORD(console)))
1824
WaitForSingleObject(handle, INFINITE);
1825
GetExitCodeProcess(handle, &exit_code);
1826
errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code;
1827
1828
CloseHandle(handle);
1829
return errorlevel;
1830
}
1831
1832
static RETURN_CODE run_command_file(const WCHAR *file, WCHAR *full_cmdline)
1833
{
1834
RETURN_CODE return_code;
1835
BOOL prev_echo_mode = echo_mode;
1836
1837
return_code = WCMD_call_batch(file, full_cmdline);
1838
1839
if (!context)
1840
echo_mode = prev_echo_mode;
1841
return return_code;
1842
}
1843
1844
struct search_command
1845
{
1846
WCHAR path[MAX_PATH];
1847
BOOL has_path; /* if input has path part (ie cannot be a builtin command) */
1848
BOOL has_extension; /* if extension was given to input */
1849
BOOL is_command_file; /* when has_path is set, tells whether its a command file, or an external executable */
1850
int cmd_index; /* potential index to builtin command */
1851
};
1852
1853
static BOOL search_in_pathext(WCHAR *path)
1854
{
1855
static struct
1856
{
1857
unsigned short offset; /* into cached_data */
1858
unsigned short length;
1859
} *cached_pathext;
1860
static WCHAR *cached_data;
1861
static unsigned cached_count;
1862
WCHAR pathext[MAX_PATH];
1863
WIN32_FIND_DATAW find;
1864
WCHAR *pos;
1865
HANDLE h;
1866
unsigned efound;
1867
DWORD len;
1868
1869
len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
1870
if (len == 0 || len >= ARRAY_SIZE(pathext))
1871
wcscpy(pathext, L".bat;.com;.cmd;.exe");
1872
/* erase cache if PATHEXT has changed */
1873
if (cached_data && wcscmp(cached_data, pathext))
1874
{
1875
free(cached_pathext);
1876
cached_pathext = NULL;
1877
free(cached_data);
1878
cached_data = NULL;
1879
cached_count = 0;
1880
}
1881
/* (re)create cache if needed */
1882
if (!cached_pathext)
1883
{
1884
size_t c;
1885
WCHAR *p, *n;
1886
1887
cached_data = xstrdupW(pathext);
1888
for (p = cached_data, c = 1; (p = wcschr(p, L';')) != NULL; c++, p++) {}
1889
cached_pathext = xalloc(sizeof(cached_pathext[0]) * c);
1890
cached_count = c;
1891
for (c = 0, p = cached_data; (n = wcschr(p, L';')) != NULL; c++, p = n + 1)
1892
{
1893
cached_pathext[c].offset = p - cached_data;
1894
cached_pathext[c].length = n - p;
1895
}
1896
cached_pathext[c].offset = p - cached_data;
1897
cached_pathext[c].length = wcslen(p);
1898
}
1899
1900
pos = &path[wcslen(path)]; /* Pos = end of name */
1901
wcscpy(pos, L".*");
1902
efound = cached_count;
1903
if ((h = FindFirstFileW(path, &find)) != INVALID_HANDLE_VALUE)
1904
{
1905
do
1906
{
1907
WCHAR *last;
1908
size_t basefound_len;
1909
unsigned i;
1910
1911
if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
1912
if (!(last = wcsrchr(find.cFileName, L'.'))) continue;
1913
basefound_len = last - find.cFileName;
1914
/* skip foo.bar.exe when looking for foo.exe */
1915
if (pos < path + basefound_len || wcsnicmp(pos - basefound_len, find.cFileName, basefound_len))
1916
continue;
1917
for (i = 0; i < efound; i++)
1918
if (!wcsnicmp(last, cached_data + cached_pathext[i].offset, cached_pathext[i].length) &&
1919
!last[cached_pathext[i].length])
1920
efound = i;
1921
} while (FindNextFileW(h, &find));
1922
CloseHandle(h);
1923
}
1924
if (efound == cached_count) return FALSE;
1925
1926
memcpy(pos, cached_data + cached_pathext[efound].offset, cached_pathext[efound].length * sizeof(WCHAR));
1927
pos[cached_pathext[efound].length] = L'\0';
1928
return TRUE;
1929
}
1930
1931
static RETURN_CODE search_command(WCHAR *command, struct search_command *sc, BOOL fast)
1932
{
1933
WCHAR temp[MAX_PATH];
1934
WCHAR pathtosearch[MAXSTRING];
1935
WCHAR *pathposn;
1936
WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1937
MAX_PATH, including null character */
1938
WCHAR *lastSlash;
1939
WCHAR *firstParam;
1940
DWORD len;
1941
WCHAR *p;
1942
1943
/* Quick way to get the filename is to extract the first argument. */
1944
firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1945
1946
sc->cmd_index = WCMD_EXIT + 1;
1947
1948
if (!firstParam[0])
1949
{
1950
sc->path[0] = L'\0';
1951
return NO_ERROR;
1952
}
1953
for (p = firstParam; *p && IsCharAlphaW(*p); p++) {}
1954
if (p > firstParam && (!*p || wcschr(L" \t+./(;=:", *p)))
1955
{
1956
for (sc->cmd_index = 0; sc->cmd_index <= WCMD_EXIT; sc->cmd_index++)
1957
if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1958
firstParam, p - firstParam, inbuilt[sc->cmd_index], -1) == CSTR_EQUAL)
1959
break;
1960
}
1961
if (firstParam[1] == L':' && (!firstParam[2] || iswspace(firstParam[2])))
1962
{
1963
sc->cmd_index = WCMD_CHGDRIVE;
1964
fast = TRUE;
1965
}
1966
1967
if (fast && sc->cmd_index <= WCMD_EXIT && firstParam[wcslen(inbuilt[sc->cmd_index])] != L'.')
1968
{
1969
sc->path[0] = L'\0';
1970
sc->has_path = sc->has_extension = FALSE;
1971
return RETURN_CODE_CANT_LAUNCH;
1972
}
1973
1974
/* Calculate the search path and stem to search for */
1975
if (wcspbrk(firstParam, L"/\\:") == NULL)
1976
{
1977
/* No explicit path given, search path */
1978
wcscpy(pathtosearch, L".;");
1979
len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
1980
if (len == 0 || len >= ARRAY_SIZE(pathtosearch) - 2)
1981
wcscpy(pathtosearch, L".");
1982
sc->has_extension = wcschr(firstParam, L'.') != NULL;
1983
if (wcslen(firstParam) >= MAX_PATH)
1984
{
1985
WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1986
return ERROR_INVALID_FUNCTION;
1987
}
1988
1989
wcscpy(stemofsearch, firstParam);
1990
sc->has_path = FALSE;
1991
}
1992
else
1993
{
1994
/* Convert eg. ..\fred to include a directory by removing file part */
1995
if (!WCMD_get_fullpath(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL))
1996
return ERROR_INVALID_FUNCTION;
1997
lastSlash = wcsrchr(pathtosearch, L'\\');
1998
sc->has_extension = wcschr(lastSlash ? lastSlash + 1 : firstParam, L'.') != NULL;
1999
wcscpy(stemofsearch, lastSlash ? lastSlash + 1 : firstParam);
2000
2001
/* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
2002
c:\windows\a.bat syntax */
2003
if (lastSlash) *(lastSlash + 1) = L'\0';
2004
sc->has_path = TRUE;
2005
}
2006
2007
/* Loop through the search path, dir by dir */
2008
pathposn = pathtosearch;
2009
WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
2010
wine_dbgstr_w(stemofsearch));
2011
while (pathposn)
2012
{
2013
int length = 0;
2014
WCHAR *pos = NULL;
2015
BOOL found = FALSE;
2016
BOOL inside_quotes = FALSE;
2017
2018
sc->path[0] = L'\0';
2019
2020
if (sc->has_path)
2021
{
2022
wcscpy(sc->path, pathposn);
2023
pathposn = NULL;
2024
}
2025
else
2026
{
2027
/* Work on the next directory on the search path */
2028
pos = pathposn;
2029
while ((inside_quotes || *pos != ';') && *pos != 0)
2030
{
2031
if (*pos == '"')
2032
inside_quotes = !inside_quotes;
2033
pos++;
2034
}
2035
2036
if (*pos) /* Reached semicolon */
2037
{
2038
memcpy(sc->path, pathposn, (pos-pathposn) * sizeof(WCHAR));
2039
sc->path[(pos-pathposn)] = 0x00;
2040
pathposn = pos+1;
2041
}
2042
else /* Reached string end */
2043
{
2044
wcscpy(sc->path, pathposn);
2045
pathposn = NULL;
2046
}
2047
2048
/* Remove quotes */
2049
length = wcslen(sc->path);
2050
if (length && sc->path[length - 1] == L'"')
2051
sc->path[length - 1] = 0;
2052
2053
if (*sc->path != L'"')
2054
wcscpy(temp, sc->path);
2055
else
2056
wcscpy(temp, sc->path + 1);
2057
2058
/* When temp is an empty string, skip over it. This needs
2059
to be done before the expansion, because WCMD_get_fullpath
2060
fails when given an empty string */
2061
if (*temp == L'\0')
2062
continue;
2063
2064
/* Since you can have eg. ..\.. on the path, need to expand
2065
to full information */
2066
if (!WCMD_get_fullpath(temp, ARRAY_SIZE(sc->path), sc->path, NULL))
2067
return ERROR_INVALID_FUNCTION;
2068
}
2069
2070
if (wcslen(sc->path) + 1 + wcslen(stemofsearch) >= ARRAY_SIZE(sc->path))
2071
return ERROR_INVALID_FUNCTION;
2072
2073
/* 1. If extension supplied, see if that file exists */
2074
wcscat(sc->path, L"\\");
2075
wcscat(sc->path, stemofsearch);
2076
2077
if (sc->has_extension)
2078
{
2079
DWORD attribs = GetFileAttributesW(sc->path);
2080
found = attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
2081
}
2082
/* if foo.bat was given but not found, try to match foo.bat.bat (or any valid ext) */
2083
if (!found) found = search_in_pathext(sc->path);
2084
if (found)
2085
{
2086
const WCHAR *ext = wcsrchr(sc->path, '.');
2087
sc->is_command_file = ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"));
2088
return NO_ERROR;
2089
}
2090
}
2091
return RETURN_CODE_CANT_LAUNCH;
2092
}
2093
2094
static BOOL set_std_redirections(CMD_REDIRECTION *redir)
2095
{
2096
static DWORD std_index[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE};
2097
static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE};
2098
WCHAR expanded_filename[MAXSTRING];
2099
HANDLE h;
2100
2101
for (; redir; redir = redir->next)
2102
{
2103
CMD_REDIRECTION *next;
2104
2105
/* if we have several elements changing same std stream, only use last one */
2106
for (next = redir->next; next; next = next->next)
2107
if (redir->fd == next->fd) break;
2108
if (next) continue;
2109
switch (redir->kind)
2110
{
2111
case REDIR_READ_FROM:
2112
wcscpy(expanded_filename, redir->file);
2113
handleExpansion(expanded_filename, TRUE);
2114
h = CreateFileW(expanded_filename, GENERIC_READ, FILE_SHARE_READ,
2115
&sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2116
if (h == INVALID_HANDLE_VALUE)
2117
{
2118
WARN("Failed to open (%ls)\n", expanded_filename);
2119
return FALSE;
2120
}
2121
TRACE("Open (%ls) => %p\n", expanded_filename, h);
2122
break;
2123
case REDIR_WRITE_TO:
2124
case REDIR_WRITE_APPEND:
2125
{
2126
DWORD disposition = redir->kind == REDIR_WRITE_TO ? CREATE_ALWAYS : OPEN_ALWAYS;
2127
wcscpy(expanded_filename, redir->file);
2128
handleExpansion(expanded_filename, TRUE);
2129
h = CreateFileW(expanded_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
2130
&sa, disposition, FILE_ATTRIBUTE_NORMAL, NULL);
2131
if (h == INVALID_HANDLE_VALUE)
2132
{
2133
WARN("Failed to open (%ls)\n", expanded_filename);
2134
return FALSE;
2135
}
2136
TRACE("Open %u (%ls) => %p\n", redir->fd, expanded_filename, h);
2137
if (SetFilePointer(h, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
2138
WCMD_print_error();
2139
}
2140
break;
2141
case REDIR_WRITE_CLONE:
2142
if (redir->clone > 2 || redir->clone == redir->fd)
2143
{
2144
WARN("Can't duplicate %d from %d\n", redir->fd, redir->clone);
2145
return FALSE;
2146
}
2147
if (!DuplicateHandle(GetCurrentProcess(),
2148
GetStdHandle(std_index[redir->clone]),
2149
GetCurrentProcess(),
2150
&h,
2151
0, TRUE, DUPLICATE_SAME_ACCESS))
2152
{
2153
WARN("Duplicating handle failed with gle %ld\n", GetLastError());
2154
return FALSE;
2155
}
2156
break;
2157
}
2158
if (redir->fd > 2)
2159
CloseHandle(h);
2160
else
2161
SetStdHandle(std_index[redir->fd], h);
2162
}
2163
return TRUE;
2164
}
2165
2166
RETURN_CODE WCMD_run_builtin_command(int cmd_index, WCHAR *cmd)
2167
{
2168
size_t count = wcslen(inbuilt[cmd_index]);
2169
WCHAR *parms_start = WCMD_skip_leading_spaces(&cmd[count]);
2170
RETURN_CODE return_code;
2171
2172
WCMD_parse(parms_start, quals, param1, param2);
2173
TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2174
2175
if (cmd_index <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?'))
2176
{
2177
/* this is a help request for a builtin program */
2178
cmd_index = WCMD_HELP;
2179
wcscpy(parms_start, inbuilt[cmd_index]);
2180
}
2181
2182
switch (cmd_index)
2183
{
2184
case WCMD_CALL:
2185
return_code = WCMD_call(parms_start);
2186
break;
2187
case WCMD_CD:
2188
case WCMD_CHDIR:
2189
return_code = WCMD_setshow_default(parms_start);
2190
break;
2191
case WCMD_CLS:
2192
return_code = WCMD_clear_screen();
2193
break;
2194
case WCMD_COPY:
2195
return_code = WCMD_copy(parms_start);
2196
break;
2197
case WCMD_DATE:
2198
return_code = WCMD_setshow_date();
2199
break;
2200
case WCMD_DEL:
2201
case WCMD_ERASE:
2202
return_code = WCMD_delete(parms_start);
2203
break;
2204
case WCMD_DIR:
2205
return_code = WCMD_directory(parms_start);
2206
break;
2207
case WCMD_ECHO:
2208
return_code = WCMD_echo(&cmd[count]);
2209
break;
2210
case WCMD_GOTO:
2211
return_code = WCMD_goto();
2212
break;
2213
case WCMD_HELP:
2214
return_code = WCMD_give_help(parms_start);
2215
break;
2216
case WCMD_LABEL:
2217
return_code = WCMD_label();
2218
break;
2219
case WCMD_MD:
2220
case WCMD_MKDIR:
2221
return_code = WCMD_create_dir(parms_start);
2222
break;
2223
case WCMD_MOVE:
2224
return_code = WCMD_move();
2225
break;
2226
case WCMD_PATH:
2227
return_code = WCMD_setshow_path(parms_start);
2228
break;
2229
case WCMD_PAUSE:
2230
return_code = WCMD_pause();
2231
break;
2232
case WCMD_PROMPT:
2233
return_code = WCMD_setshow_prompt();
2234
break;
2235
case WCMD_REM:
2236
return_code = NO_ERROR;
2237
break;
2238
case WCMD_REN:
2239
case WCMD_RENAME:
2240
return_code = WCMD_rename();
2241
break;
2242
case WCMD_RD:
2243
case WCMD_RMDIR:
2244
return_code = WCMD_remove_dir(parms_start);
2245
break;
2246
case WCMD_SETLOCAL:
2247
return_code = WCMD_setlocal(parms_start);
2248
break;
2249
case WCMD_ENDLOCAL:
2250
return_code = WCMD_endlocal();
2251
break;
2252
case WCMD_SET:
2253
return_code = WCMD_setshow_env(parms_start);
2254
break;
2255
case WCMD_SHIFT:
2256
return_code = WCMD_shift(parms_start);
2257
break;
2258
case WCMD_START:
2259
return_code = WCMD_start(parms_start);
2260
break;
2261
case WCMD_TIME:
2262
return_code = WCMD_setshow_time();
2263
break;
2264
case WCMD_TITLE:
2265
return_code = WCMD_title(parms_start);
2266
break;
2267
case WCMD_TYPE:
2268
return_code = WCMD_type(parms_start);
2269
break;
2270
case WCMD_VER:
2271
return_code = WCMD_version();
2272
break;
2273
case WCMD_VERIFY:
2274
return_code = WCMD_verify();
2275
break;
2276
case WCMD_VOL:
2277
return_code = WCMD_volume();
2278
break;
2279
case WCMD_PUSHD:
2280
return_code = WCMD_pushd(parms_start);
2281
break;
2282
case WCMD_POPD:
2283
return_code = WCMD_popd();
2284
break;
2285
case WCMD_ASSOC:
2286
return_code = WCMD_assoc(parms_start, TRUE);
2287
break;
2288
case WCMD_COLOR:
2289
return_code = WCMD_color();
2290
break;
2291
case WCMD_FTYPE:
2292
return_code = WCMD_assoc(parms_start, FALSE);
2293
break;
2294
case WCMD_MORE:
2295
return_code = WCMD_more(parms_start);
2296
break;
2297
case WCMD_CHOICE:
2298
return_code = WCMD_choice(parms_start);
2299
break;
2300
case WCMD_MKLINK:
2301
return_code = WCMD_mklink(parms_start);
2302
break;
2303
case WCMD_CHGDRIVE:
2304
return_code = WCMD_change_drive(cmd[0]);
2305
break;
2306
case WCMD_EXIT:
2307
return_code = WCMD_exit();
2308
break;
2309
default:
2310
FIXME("Shouldn't happen %d\n", cmd_index);
2311
case WCMD_FOR: /* can happen in 'call for...' and should fail */
2312
case WCMD_IF:
2313
return_code = RETURN_CODE_CANT_LAUNCH;
2314
break;
2315
}
2316
return return_code;
2317
}
2318
2319
/*****************************************************************************
2320
* Process one command. If the command is EXIT this routine does not return.
2321
* We will recurse through here executing batch files.
2322
* Note: If call is used to a non-existing program, we reparse the line and
2323
* try to run it as an internal command. 'retrycall' represents whether
2324
* we are attempting this retry.
2325
*/
2326
static RETURN_CODE execute_single_command(const WCHAR *command)
2327
{
2328
struct search_command sc;
2329
RETURN_CODE return_code;
2330
WCHAR *cmd;
2331
2332
TRACE("command on entry:%s\n", wine_dbgstr_w(command));
2333
2334
/* Move copy of the command onto the heap so it can be expanded */
2335
cmd = xalloc(MAXSTRING * sizeof(WCHAR));
2336
lstrcpyW(cmd, command);
2337
handleExpansion(cmd, TRUE);
2338
2339
TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
2340
2341
return_code = search_command(cmd, &sc, TRUE);
2342
if (return_code != NO_ERROR && sc.cmd_index == WCMD_EXIT + 1)
2343
{
2344
/* Not found anywhere - give up */
2345
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), cmd);
2346
2347
/* If a command fails to launch, it sets errorlevel 9009 - which
2348
* does not seem to have any associated constant definition
2349
*/
2350
errorlevel = RETURN_CODE_CANT_LAUNCH;
2351
return_code = ERROR_INVALID_FUNCTION;
2352
}
2353
else if (sc.cmd_index <= WCMD_EXIT && (return_code != NO_ERROR || (!sc.has_path && !sc.has_extension)))
2354
return_code = WCMD_run_builtin_command(sc.cmd_index, cmd);
2355
else
2356
{
2357
if (*sc.path)
2358
{
2359
if (sc.is_command_file)
2360
{
2361
return_code = run_command_file(sc.path, cmd);
2362
if (context)
2363
{
2364
TRACE("Batch completed, but was not 'called' so skipping outer batch too\n");
2365
context->file_position.QuadPart = WCMD_FILE_POSITION_EOF;
2366
if (return_code == RETURN_CODE_ABORTED)
2367
return_code = RETURN_CODE_EXITED;
2368
}
2369
else
2370
{
2371
if (return_code == RETURN_CODE_ABORTED || return_code == RETURN_CODE_EXITED)
2372
return_code = errorlevel;
2373
else if (return_code == RETURN_CODE_GOTO)
2374
return_code = NO_ERROR;
2375
else if (return_code != NO_ERROR)
2376
errorlevel = return_code;
2377
}
2378
}
2379
else
2380
return_code = run_external_full_path(sc.path, cmd);
2381
}
2382
2383
}
2384
free(cmd);
2385
return return_code;
2386
}
2387
2388
RETURN_CODE WCMD_call_command(WCHAR *command)
2389
{
2390
struct search_command sc;
2391
RETURN_CODE return_code;
2392
2393
return_code = search_command(command, &sc, FALSE);
2394
if (return_code == NO_ERROR)
2395
{
2396
if (!*sc.path) return NO_ERROR;
2397
if (sc.is_command_file)
2398
{
2399
return_code = run_command_file(sc.path, command);
2400
if (WCMD_is_break(return_code))
2401
return_code = errorlevel;
2402
else if (return_code != NO_ERROR)
2403
errorlevel = return_code;
2404
}
2405
else
2406
return_code = run_external_full_path(sc.path, command);
2407
return return_code;
2408
}
2409
2410
if (sc.cmd_index <= WCMD_EXIT)
2411
return errorlevel = WCMD_run_builtin_command(sc.cmd_index, command);
2412
2413
/* Not found anywhere - give up */
2414
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
2415
2416
/* If a command fails to launch, it sets errorlevel 9009 - which
2417
* does not seem to have any associated constant definition
2418
*/
2419
errorlevel = RETURN_CODE_CANT_LAUNCH;
2420
return ERROR_INVALID_FUNCTION;
2421
}
2422
2423
/*************************************************************************
2424
* WCMD_LoadMessage
2425
* Load a string from the resource file, handling any error
2426
* Returns string retrieved from resource file
2427
*/
2428
WCHAR *WCMD_LoadMessage(UINT id) {
2429
static WCHAR msg[2048];
2430
2431
if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
2432
WINE_FIXME("LoadString failed with %ld\n", GetLastError());
2433
lstrcpyW(msg, L"Failed!");
2434
}
2435
return msg;
2436
}
2437
2438
static WCHAR *find_chr(WCHAR *in, WCHAR *last, const WCHAR *delims)
2439
{
2440
for (; in < last; in++)
2441
if (wcschr(delims, *in)) return in;
2442
return NULL;
2443
}
2444
2445
static WCHAR *for_fileset_option_split(WCHAR *from, const WCHAR* key)
2446
{
2447
size_t len = wcslen(key);
2448
2449
if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2450
from, len, key, len) != CSTR_EQUAL)
2451
return NULL;
2452
from += len;
2453
if (len && key[len - 1] == L'=')
2454
while (*from && *from != L' ' && *from != L'\t') from++;
2455
return from;
2456
}
2457
2458
static CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var)
2459
{
2460
CMD_FOR_CONTROL *for_ctrl;
2461
enum for_control_operator for_op;
2462
WCHAR mode = L' ', option;
2463
WCHAR options[MAXSTRING];
2464
WCHAR *arg;
2465
unsigned flags = 0;
2466
int arg_index;
2467
unsigned varidx;
2468
2469
options[0] = L'\0';
2470
/* native allows two options only in the /D /R case, a repetition of the option
2471
* and prints an error otherwise
2472
*/
2473
for (arg_index = 0; ; arg_index++)
2474
{
2475
arg = WCMD_parameter(opts_var, arg_index, NULL, FALSE, FALSE);
2476
2477
if (!arg || *arg != L'/') break;
2478
option = towupper(arg[1]);
2479
if (mode != L' ' && (mode != L'D' || option != 'R') && mode != option)
2480
break;
2481
switch (option)
2482
{
2483
case L'R':
2484
if (mode == L'D')
2485
{
2486
mode = L'X';
2487
break;
2488
}
2489
/* fall thru */
2490
case L'D':
2491
case L'L':
2492
case L'F':
2493
mode = option;
2494
break;
2495
default:
2496
/* error unexpected 'arg' at this time */
2497
WARN("for qualifier '%c' unhandled\n", *arg);
2498
goto syntax_error;
2499
}
2500
}
2501
switch (mode)
2502
{
2503
case L' ':
2504
for_op = CMD_FOR_FILETREE;
2505
flags = CMD_FOR_FLAG_TREE_INCLUDE_FILES;
2506
break;
2507
case L'D':
2508
for_op = CMD_FOR_FILETREE;
2509
flags = CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES;
2510
break;
2511
case L'X':
2512
for_op = CMD_FOR_FILETREE;
2513
flags = CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | CMD_FOR_FLAG_TREE_RECURSE;
2514
break;
2515
case L'R':
2516
for_op = CMD_FOR_FILETREE;
2517
flags = CMD_FOR_FLAG_TREE_INCLUDE_FILES | /*CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | */CMD_FOR_FLAG_TREE_RECURSE;
2518
break;
2519
case L'L':
2520
for_op = CMD_FOR_NUMBERS;
2521
break;
2522
case L'F':
2523
for_op = CMD_FOR_FILE_SET;
2524
break;
2525
default:
2526
FIXME("Unexpected situation\n");
2527
return NULL;
2528
}
2529
2530
if (mode == L'F' || mode == L'R')
2531
{
2532
/* Retrieve next parameter to see if is root/options (raw form required
2533
* with for /f, or unquoted in for /r)
2534
*/
2535
arg = WCMD_parameter(opts_var, arg_index, NULL, for_op == CMD_FOR_FILE_SET, FALSE);
2536
2537
/* Next parm is either qualifier, path/options or variable -
2538
* only care about it if it is the path/options
2539
*/
2540
if (arg && *arg != L'/' && *arg != L'%')
2541
{
2542
arg_index++;
2543
wcscpy(options, arg);
2544
}
2545
}
2546
2547
/* Ensure line continues with variable */
2548
arg = WCMD_parameter(opts_var, arg_index++, NULL, FALSE, FALSE);
2549
if (!arg || *arg != L'%' || !for_var_is_valid(arg[1]))
2550
goto syntax_error; /* FIXME native prints the offending token "%<whatever>" was unexpected at this time */
2551
varidx = arg[1];
2552
for_ctrl = xalloc(sizeof(*for_ctrl));
2553
if (for_op == CMD_FOR_FILE_SET)
2554
{
2555
size_t len = wcslen(options);
2556
WCHAR *p = options, *end;
2557
WCHAR eol = L'\0';
2558
int num_lines_to_skip = 0;
2559
BOOL use_backq = FALSE;
2560
WCHAR *delims = NULL, *tokens = NULL;
2561
/* strip enclosing double-quotes when present */
2562
if (len >= 2 && p[0] == L'"' && p[len - 1] == L'"')
2563
{
2564
p[len - 1] = L'\0';
2565
p++;
2566
}
2567
for ( ; *(p = WCMD_skip_leading_spaces(p)); p = end)
2568
{
2569
/* Save End of line character (Ignore line if first token (based on delims) starts with it) */
2570
if ((end = for_fileset_option_split(p, L"eol=")))
2571
{
2572
/* assuming one char for eol marker */
2573
if (end != p + 5) goto syntax_error;
2574
eol = p[4];
2575
}
2576
/* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
2577
else if ((end = for_fileset_option_split(p, L"skip=")))
2578
{
2579
WCHAR *nextchar;
2580
num_lines_to_skip = wcstoul(p + 5, &nextchar, 0);
2581
if (end != nextchar) goto syntax_error;
2582
}
2583
/* Save if usebackq semantics are in effect */
2584
else if ((end = for_fileset_option_split(p, L"usebackq")))
2585
use_backq = TRUE;
2586
/* Save the supplied delims */
2587
else if ((end = for_fileset_option_split(p, L"delims=")))
2588
{
2589
size_t copy_len;
2590
2591
/* interpret space when last character of whole options string as part of delims= */
2592
if (end[0] && !end[1]) end++;
2593
copy_len = end - (p + 7) /* delims= */;
2594
delims = xalloc((copy_len + 1) * sizeof(WCHAR));
2595
memcpy(delims, p + 7, copy_len * sizeof(WCHAR));
2596
delims[copy_len] = L'\0';
2597
}
2598
/* Save the tokens being requested */
2599
else if ((end = for_fileset_option_split(p, L"tokens=")))
2600
{
2601
size_t copy_len;
2602
2603
copy_len = end - (p + 7) /* tokens= */;
2604
tokens = xalloc((copy_len + 1) * sizeof(WCHAR));
2605
memcpy(tokens, p + 7, copy_len * sizeof(WCHAR));
2606
tokens[copy_len] = L'\0';
2607
}
2608
else
2609
{
2610
WARN("FOR option not found %ls\n", p);
2611
goto syntax_error;
2612
}
2613
}
2614
for_control_create_fileset(flags, varidx, eol, num_lines_to_skip, use_backq,
2615
delims ? delims : xstrdupW(L" \t"),
2616
tokens ? tokens : xstrdupW(L"1"), for_ctrl);
2617
}
2618
else
2619
for_control_create(for_op, flags, options, varidx, for_ctrl);
2620
return for_ctrl;
2621
syntax_error:
2622
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2623
return NULL;
2624
}
2625
2626
/* used to store additional information dedicated a given token */
2627
union token_parameter
2628
{
2629
WCHAR *command;
2630
CMD_REDIRECTION *redirection;
2631
void *none;
2632
};
2633
2634
struct node_builder
2635
{
2636
unsigned num;
2637
unsigned allocated;
2638
struct token
2639
{
2640
enum builder_token
2641
{
2642
TKN_EOF, TKN_EOL, TKN_REDIRECTION, TKN_FOR, TKN_IN, TKN_DO, TKN_IF, TKN_ELSE,
2643
TKN_OPENPAR, TKN_CLOSEPAR, TKN_AMP, TKN_BARBAR, TKN_AMPAMP, TKN_BAR, TKN_COMMAND,
2644
} token;
2645
union token_parameter parameter;
2646
} *stack;
2647
unsigned pos;
2648
unsigned opened_parenthesis;
2649
};
2650
2651
static const char* debugstr_token(enum builder_token tkn, union token_parameter tkn_pmt)
2652
{
2653
static const char *tokens[] = {"EOF", "EOL", "REDIR", "FOR", "IN", "DO", "IF", "ELSE",
2654
"(", ")", "&", "||", "&&", "|", "CMD"};
2655
2656
if (tkn >= ARRAY_SIZE(tokens)) return "<<<>>>";
2657
switch (tkn)
2658
{
2659
case TKN_COMMAND: return wine_dbg_sprintf("%s {{%s}}", tokens[tkn], debugstr_w(tkn_pmt.command));
2660
case TKN_REDIRECTION: return wine_dbg_sprintf("%s {{%s}}", tokens[tkn], debugstr_redirection(tkn_pmt.redirection));
2661
default: return wine_dbg_sprintf("%s", tokens[tkn]);
2662
}
2663
}
2664
2665
static unsigned token_get_precedence(enum builder_token tkn)
2666
{
2667
switch (tkn)
2668
{
2669
case TKN_EOL: return 5;
2670
case TKN_BAR: return 4;
2671
case TKN_AMPAMP: return 3;
2672
case TKN_BARBAR: return 2;
2673
case TKN_AMP: return 1;
2674
default: return 0;
2675
}
2676
}
2677
2678
static void node_builder_init(struct node_builder *builder)
2679
{
2680
memset(builder, 0, sizeof(*builder));
2681
}
2682
2683
static void node_builder_dispose(struct node_builder *builder)
2684
{
2685
free(builder->stack);
2686
}
2687
2688
static void node_builder_push_token_parameter(struct node_builder *builder, enum builder_token tkn, union token_parameter pmt)
2689
{
2690
if (builder->allocated <= builder->num)
2691
{
2692
unsigned sz = builder->allocated ? 2 * builder->allocated : 64;
2693
builder->stack = xrealloc(builder->stack, sz * sizeof(builder->stack[0]));
2694
builder->allocated = sz;
2695
}
2696
builder->stack[builder->num].token = tkn;
2697
builder->stack[builder->num].parameter = pmt;
2698
2699
if (tkn == TKN_OPENPAR)
2700
builder->opened_parenthesis++;
2701
if (tkn == TKN_CLOSEPAR)
2702
builder->opened_parenthesis--;
2703
builder->num++;
2704
}
2705
2706
static void node_builder_push_token(struct node_builder *builder, enum builder_token tkn)
2707
{
2708
union token_parameter pmt = {.none = NULL};
2709
node_builder_push_token_parameter(builder, tkn, pmt);
2710
}
2711
2712
static enum builder_token node_builder_peek_next_token(struct node_builder *builder, union token_parameter *pmt)
2713
{
2714
enum builder_token tkn;
2715
2716
if (builder->pos >= builder->num)
2717
{
2718
tkn = TKN_EOF;
2719
if (pmt) pmt->none = NULL;
2720
}
2721
else
2722
{
2723
tkn = builder->stack[builder->pos].token;
2724
if (pmt)
2725
*pmt = builder->stack[builder->pos].parameter;
2726
}
2727
return tkn;
2728
}
2729
2730
static void node_builder_consume(struct node_builder *builder)
2731
{
2732
builder->stack[builder->pos].parameter.none = NULL;
2733
builder->pos++;
2734
}
2735
2736
static BOOL node_builder_expect_token(struct node_builder *builder, enum builder_token tkn)
2737
{
2738
if (builder->pos >= builder->num || builder->stack[builder->pos].token != tkn)
2739
return FALSE;
2740
node_builder_consume(builder);
2741
return TRUE;
2742
}
2743
2744
static enum builder_token node_builder_top(const struct node_builder *builder, unsigned d)
2745
{
2746
return builder->num > d ? builder->stack[builder->num - (d + 1)].token : TKN_EOF;
2747
}
2748
2749
static void redirection_list_append(CMD_REDIRECTION **redir, CMD_REDIRECTION *last)
2750
{
2751
if (last)
2752
{
2753
for ( ; *redir; redir = &(*redir)->next) {}
2754
*redir = last;
2755
}
2756
}
2757
2758
static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence, CMD_NODE **result)
2759
{
2760
CMD_REDIRECTION *redir = NULL;
2761
unsigned bogus_line;
2762
CMD_NODE *left = NULL, *right;
2763
CMD_FOR_CONTROL *for_ctrl = NULL;
2764
union token_parameter pmt;
2765
enum builder_token tkn;
2766
BOOL done;
2767
2768
#define ERROR_IF(x) if (x) {bogus_line = __LINE__; goto error_handling;}
2769
do
2770
{
2771
tkn = node_builder_peek_next_token(builder, &pmt);
2772
done = FALSE;
2773
2774
TRACE("\t%u/%u) %s\n", builder->pos, builder->num, debugstr_token(tkn, pmt));
2775
switch (tkn)
2776
{
2777
case TKN_EOF:
2778
/* always an error to read past end of tokens */
2779
ERROR_IF(TRUE);
2780
break;
2781
case TKN_EOL:
2782
done = TRUE;
2783
break;
2784
case TKN_OPENPAR:
2785
ERROR_IF(left);
2786
node_builder_consume(builder);
2787
/* empty lines are allowed here */
2788
while ((tkn = node_builder_peek_next_token(builder, &pmt)) == TKN_EOL)
2789
node_builder_consume(builder);
2790
ERROR_IF(!node_builder_parse(builder, 0, &left));
2791
/* temp before using precedence in chaining */
2792
while ((tkn = node_builder_peek_next_token(builder, &pmt)) != TKN_CLOSEPAR)
2793
{
2794
ERROR_IF(tkn != TKN_EOL);
2795
node_builder_consume(builder);
2796
ERROR_IF(!node_builder_parse(builder, 0, &right));
2797
if (right)
2798
left = node_create_binary(CMD_CONCAT, left, right);
2799
}
2800
node_builder_consume(builder);
2801
/* if we had redirection before '(', add them up front */
2802
if (redir)
2803
{
2804
redirection_list_append(&redir, left->redirects);
2805
left->redirects = redir;
2806
redir = NULL;
2807
}
2808
/* just in case we're handling: "(if ...) > a"... to not trigger errors in TKN_REDIRECTION */
2809
while (node_builder_peek_next_token(builder, &pmt) == TKN_REDIRECTION)
2810
{
2811
redirection_list_append(&left->redirects, pmt.redirection);
2812
node_builder_consume(builder);
2813
}
2814
break;
2815
/* shouldn't appear here... error handling ? */
2816
case TKN_IN:
2817
/* following tokens act as a delimiter for inner context; return to upper */
2818
case TKN_CLOSEPAR:
2819
case TKN_ELSE:
2820
case TKN_DO:
2821
done = TRUE;
2822
break;
2823
case TKN_AMP:
2824
ERROR_IF(!left);
2825
if (!(done = token_get_precedence(tkn) <= precedence))
2826
{
2827
node_builder_consume(builder);
2828
if (node_builder_peek_next_token(builder, &pmt) == TKN_CLOSEPAR)
2829
{
2830
done = TRUE;
2831
break;
2832
}
2833
ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2834
if (right)
2835
left = node_create_binary(CMD_CONCAT, left, right);
2836
}
2837
break;
2838
case TKN_AMPAMP:
2839
ERROR_IF(!left);
2840
if (!(done = token_get_precedence(tkn) <= precedence))
2841
{
2842
node_builder_consume(builder);
2843
ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2844
left = node_create_binary(CMD_ONSUCCESS, left, right);
2845
}
2846
break;
2847
case TKN_BAR:
2848
ERROR_IF(!left);
2849
if (!(done = token_get_precedence(tkn) <= precedence))
2850
{
2851
node_builder_consume(builder);
2852
ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2853
left = node_create_binary(CMD_PIPE, left, right);
2854
}
2855
break;
2856
case TKN_BARBAR:
2857
ERROR_IF(!left);
2858
if (!(done = token_get_precedence(tkn) <= precedence))
2859
{
2860
node_builder_consume(builder);
2861
ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2862
left = node_create_binary(CMD_ONFAILURE, left, right);
2863
}
2864
break;
2865
case TKN_COMMAND:
2866
ERROR_IF(left);
2867
left = node_create_single(pmt.command);
2868
node_builder_consume(builder);
2869
left->redirects = redir;
2870
redir = NULL;
2871
break;
2872
case TKN_IF:
2873
ERROR_IF(left);
2874
ERROR_IF(redir);
2875
{
2876
WCHAR *end;
2877
CMD_IF_CONDITION cond;
2878
CMD_NODE *then_block;
2879
CMD_NODE *else_block;
2880
2881
node_builder_consume(builder);
2882
tkn = node_builder_peek_next_token(builder, &pmt);
2883
ERROR_IF(tkn != TKN_COMMAND);
2884
if (!wcscmp(pmt.command, L"/?"))
2885
{
2886
node_builder_consume(builder);
2887
free(pmt.command);
2888
left = node_create_single(command_create(L"help if", 7));
2889
break;
2890
}
2891
ERROR_IF(!if_condition_parse(pmt.command, &end, &cond));
2892
free(pmt.command);
2893
node_builder_consume(builder);
2894
if (!node_builder_parse(builder, 0, &then_block))
2895
{
2896
if_condition_dispose(&cond);
2897
ERROR_IF(TRUE);
2898
}
2899
tkn = node_builder_peek_next_token(builder, NULL);
2900
if (tkn == TKN_ELSE)
2901
{
2902
node_builder_consume(builder);
2903
if (!node_builder_parse(builder, 0, &else_block))
2904
{
2905
if_condition_dispose(&cond);
2906
node_dispose_tree(then_block);
2907
ERROR_IF(TRUE);
2908
}
2909
}
2910
else
2911
else_block = NULL;
2912
left = node_create_if(&cond, then_block, else_block);
2913
}
2914
break;
2915
case TKN_FOR:
2916
ERROR_IF(left);
2917
ERROR_IF(redir);
2918
{
2919
CMD_NODE *do_block;
2920
2921
node_builder_consume(builder);
2922
tkn = node_builder_peek_next_token(builder, &pmt);
2923
ERROR_IF(tkn != TKN_COMMAND);
2924
if (!wcscmp(pmt.command, L"/?"))
2925
{
2926
node_builder_consume(builder);
2927
free(pmt.command);
2928
left = node_create_single(command_create(L"help for", 8));
2929
break;
2930
}
2931
node_builder_consume(builder);
2932
for_ctrl = for_control_parse(pmt.command);
2933
free(pmt.command);
2934
ERROR_IF(for_ctrl == NULL);
2935
ERROR_IF(!node_builder_expect_token(builder, TKN_IN));
2936
ERROR_IF(!node_builder_expect_token(builder, TKN_OPENPAR));
2937
do
2938
{
2939
tkn = node_builder_peek_next_token(builder, &pmt);
2940
switch (tkn)
2941
{
2942
case TKN_COMMAND:
2943
for_control_append_set(for_ctrl, pmt.command);
2944
free(pmt.command);
2945
break;
2946
case TKN_EOL:
2947
case TKN_CLOSEPAR:
2948
break;
2949
default:
2950
ERROR_IF(TRUE);
2951
}
2952
node_builder_consume(builder);
2953
} while (tkn != TKN_CLOSEPAR);
2954
ERROR_IF(!node_builder_expect_token(builder, TKN_DO));
2955
ERROR_IF(!node_builder_parse(builder, 0, &do_block));
2956
left = node_create_for(for_ctrl, do_block);
2957
for_ctrl = NULL;
2958
}
2959
break;
2960
case TKN_REDIRECTION:
2961
ERROR_IF(left && (left->op == CMD_IF || left->op == CMD_FOR));
2962
redirection_list_append(left ? &left->redirects : &redir, pmt.redirection);
2963
node_builder_consume(builder);
2964
break;
2965
}
2966
} while (!done);
2967
#undef ERROR_IF
2968
*result = left;
2969
return TRUE;
2970
error_handling:
2971
TRACE("Parser failed at line %u:token %s\n", bogus_line, debugstr_token(tkn, pmt));
2972
node_dispose_tree(left);
2973
redirection_dispose_list(redir);
2974
if (for_ctrl) for_control_dispose(for_ctrl);
2975
2976
return FALSE;
2977
}
2978
2979
static BOOL node_builder_generate(struct node_builder *builder, CMD_NODE **node)
2980
{
2981
union token_parameter tkn_pmt;
2982
enum builder_token tkn;
2983
2984
if (builder->opened_parenthesis)
2985
{
2986
TRACE("Brackets do not match, error out without executing.\n");
2987
WCMD_output_stderr(WCMD_LoadMessage(WCMD_BADPAREN));
2988
}
2989
else
2990
{
2991
if (!builder->num) /* line without tokens */
2992
{
2993
*node = NULL;
2994
return TRUE;
2995
}
2996
if (node_builder_parse(builder, 0, node) &&
2997
builder->pos + 1 >= builder->num) /* consumed all tokens? */
2998
return TRUE;
2999
/* print error on first unused token */
3000
if (builder->pos < builder->num)
3001
{
3002
WCHAR buffer[MAXSTRING];
3003
const WCHAR *tknstr;
3004
3005
tkn = node_builder_peek_next_token(builder, &tkn_pmt);
3006
switch (tkn)
3007
{
3008
case TKN_COMMAND:
3009
tknstr = tkn_pmt.command;
3010
break;
3011
case TKN_EOF:
3012
tknstr = WCMD_LoadMessage(WCMD_ENDOFFILE);
3013
break;
3014
case TKN_EOL:
3015
tknstr = WCMD_LoadMessage(WCMD_ENDOFLINE);
3016
break;
3017
case TKN_REDIRECTION:
3018
MultiByteToWideChar(CP_ACP, 0, debugstr_redirection(tkn_pmt.redirection), -1, buffer, ARRAY_SIZE(buffer));
3019
tknstr = buffer;
3020
break;
3021
case TKN_AMP:
3022
case TKN_AMPAMP:
3023
case TKN_BAR:
3024
case TKN_BARBAR:
3025
case TKN_FOR:
3026
case TKN_IN:
3027
case TKN_DO:
3028
case TKN_IF:
3029
case TKN_ELSE:
3030
case TKN_OPENPAR:
3031
case TKN_CLOSEPAR:
3032
MultiByteToWideChar(CP_ACP, 0, debugstr_token(tkn, tkn_pmt), -1, buffer, ARRAY_SIZE(buffer));
3033
tknstr = buffer;
3034
break;
3035
default:
3036
FIXME("Unexpected situation\n");
3037
tknstr = L"";
3038
break;
3039
}
3040
WCMD_output_stderr(WCMD_LoadMessage(WCMD_BADTOKEN), tknstr);
3041
}
3042
}
3043
/* free remaining tokens */
3044
for (;;)
3045
{
3046
tkn = node_builder_peek_next_token(builder, &tkn_pmt);
3047
if (tkn == TKN_EOF) break;
3048
if (tkn == TKN_COMMAND) free(tkn_pmt.command);
3049
if (tkn == TKN_REDIRECTION) redirection_dispose_list(tkn_pmt.redirection);
3050
node_builder_consume(builder);
3051
}
3052
3053
*node = NULL;
3054
return FALSE;
3055
}
3056
3057
static void lexer_push_command(struct node_builder *builder,
3058
WCHAR *command, int *commandLen,
3059
WCHAR *redirs, int *redirLen,
3060
WCHAR **copyTo, int **copyToLen)
3061
{
3062
union token_parameter tkn_pmt;
3063
3064
/* push first all redirections */
3065
if (*redirLen)
3066
{
3067
WCHAR *pos;
3068
WCHAR *last = redirs + *redirLen;
3069
3070
redirs[*redirLen] = 0;
3071
3072
/* Create redirects, keeping order (eg "2>foo 1>&2") */
3073
for (pos = redirs; pos; )
3074
{
3075
WCHAR *p = find_chr(pos, last, L"<>");
3076
WCHAR *filename;
3077
3078
if (!p) break;
3079
3080
if (*p == L'<')
3081
{
3082
unsigned fd = 0;
3083
3084
if (p > redirs && p[-1] >= L'0' && p[-1] <= L'9') fd = p[-1] - L'0';
3085
p++;
3086
if (*p == L'&' && (p[1] >= L'0' && p[1] <= L'9'))
3087
{
3088
tkn_pmt.redirection = redirection_create_clone(fd, p[1] - L'0');
3089
p++;
3090
}
3091
else
3092
{
3093
filename = WCMD_parameter(p + 1, 0, NULL, FALSE, FALSE);
3094
tkn_pmt.redirection = redirection_create_file(REDIR_READ_FROM, 0, filename);
3095
}
3096
}
3097
else
3098
{
3099
unsigned fd = 1;
3100
unsigned op = REDIR_WRITE_TO;
3101
3102
if (p > redirs && p[-1] >= L'2' && p[-1] <= L'9') fd = p[-1] - L'0';
3103
if (*++p == L'>') {p++; op = REDIR_WRITE_APPEND;}
3104
if (*p == L'&' && (p[1] >= L'0' && p[1] <= L'9'))
3105
{
3106
tkn_pmt.redirection = redirection_create_clone(fd, p[1] - '0');
3107
p++;
3108
}
3109
else
3110
{
3111
filename = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
3112
tkn_pmt.redirection = redirection_create_file(op, fd, filename);
3113
}
3114
}
3115
pos = p + 1;
3116
node_builder_push_token_parameter(builder, TKN_REDIRECTION, tkn_pmt);
3117
}
3118
}
3119
if (*commandLen)
3120
{
3121
tkn_pmt.command = command_create(command, *commandLen);
3122
node_builder_push_token_parameter(builder, TKN_COMMAND, tkn_pmt);
3123
}
3124
/* Reset the lengths */
3125
*commandLen = 0;
3126
*redirLen = 0;
3127
*copyToLen = commandLen;
3128
*copyTo = command;
3129
}
3130
3131
static WCHAR *fetch_next_line(BOOL first_line, WCHAR* buffer)
3132
{
3133
BOOL ret;
3134
3135
if (!context) /* interactive mode */
3136
{
3137
/* native does is this way... not symmetrical wrt. echo_mode */
3138
if (!first_line)
3139
WCMD_output_asis(WCMD_LoadMessage(WCMD_MOREPROMPT));
3140
else if (echo_mode)
3141
WCMD_show_prompt();
3142
ret = !!WCMD_fgets(buffer, MAXSTRING, GetStdHandle(STD_INPUT_HANDLE));
3143
}
3144
else if (context->batch_file) /* command file */
3145
{
3146
LARGE_INTEGER zeroli = {.QuadPart = 0};
3147
HANDLE h = CreateFileW(context->batch_file->path_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
3148
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
3149
if (h == INVALID_HANDLE_VALUE)
3150
{
3151
SetLastError(ERROR_FILE_NOT_FOUND);
3152
WCMD_print_error();
3153
ret = FALSE;
3154
}
3155
else
3156
{
3157
ret = SetFilePointerEx(h, context->file_position, NULL, FILE_BEGIN) &&
3158
!!WCMD_fgets(buffer, MAXSTRING, h) &&
3159
SetFilePointerEx(h, zeroli, &context->file_position, FILE_CURRENT);
3160
CloseHandle(h);
3161
}
3162
}
3163
else /* /c or /k string from command line */
3164
{
3165
if ((ret = (context->file_position.QuadPart == 0)))
3166
{
3167
wcscpy(buffer, context->command);
3168
context->file_position.QuadPart += wcslen(context->command) + 1;
3169
}
3170
}
3171
3172
if (!ret)
3173
{
3174
buffer[0] = L'\0';
3175
return NULL;
3176
}
3177
3178
/* Handle truncated input - issue warning */
3179
if (wcslen(buffer) == MAXSTRING - 1)
3180
{
3181
WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
3182
WCMD_output_asis_stderr(buffer);
3183
WCMD_output_asis_stderr(L"\r\n");
3184
}
3185
/* Replace env vars if in a batch context */
3186
handleExpansion(buffer, FALSE);
3187
3188
buffer = WCMD_skip_leading_spaces(buffer);
3189
/* Show prompt before batch line IF echo is on and in batch program */
3190
if (WCMD_is_in_context(NULL) && echo_mode && *buffer && *buffer != L'@' && *buffer != L':')
3191
{
3192
if (first_line)
3193
{
3194
const size_t len = wcslen(L"echo.");
3195
size_t curr_size = wcslen(buffer);
3196
size_t min_len = curr_size < len ? curr_size : len;
3197
WCMD_output_asis(L"\r\n");
3198
WCMD_show_prompt();
3199
WCMD_output_asis(buffer);
3200
/* I don't know why Windows puts a space here but it does */
3201
/* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
3202
if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
3203
buffer, min_len, L"echo.", len) != CSTR_EQUAL
3204
&& CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
3205
buffer, min_len, L"echo:", len) != CSTR_EQUAL
3206
&& CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
3207
buffer, min_len, L"echo/", len) != CSTR_EQUAL)
3208
{
3209
WCMD_output_asis(L" ");
3210
}
3211
}
3212
else
3213
WCMD_output_asis(buffer);
3214
3215
WCMD_output_asis(L"\r\n");
3216
}
3217
3218
return buffer;
3219
}
3220
3221
static BOOL lexer_can_accept_do(const struct node_builder *builder)
3222
{
3223
unsigned d = 0;
3224
3225
if (node_builder_top(builder, d++) != TKN_CLOSEPAR) return FALSE;
3226
while (node_builder_top(builder, d) == TKN_COMMAND || node_builder_top(builder, d) == TKN_EOL) d++;
3227
if (node_builder_top(builder, d++) != TKN_OPENPAR) return FALSE;
3228
return node_builder_top(builder, d) == TKN_IN;
3229
}
3230
3231
static BOOL lexer_at_command_start(const struct node_builder *builder)
3232
{
3233
switch (node_builder_top(builder, 0))
3234
{
3235
case TKN_EOF:
3236
case TKN_EOL:
3237
case TKN_DO:
3238
case TKN_ELSE:
3239
case TKN_AMP:
3240
case TKN_AMPAMP:
3241
case TKN_BAR:
3242
case TKN_BARBAR: return TRUE;
3243
case TKN_OPENPAR: return node_builder_top(builder, 1) != TKN_IN;
3244
case TKN_COMMAND: return node_builder_top(builder, 1) == TKN_IF;
3245
default: return FALSE;
3246
}
3247
}
3248
3249
static BOOL lexer_white_space_only(const WCHAR *string, int len)
3250
{
3251
int i;
3252
3253
for (i = 0; i < len; i++)
3254
if (!iswspace(string[i])) return FALSE;
3255
return TRUE;
3256
}
3257
3258
/***************************************************************************
3259
* WCMD_ReadAndParseLine
3260
*
3261
* Either uses supplied input or
3262
* Reads a file from the handle, and then...
3263
* Parse the text buffer, splitting into separate commands
3264
* - unquoted && strings split 2 commands but the 2nd is flagged as
3265
* following an &&
3266
* - ( as the first character just ups the bracket depth
3267
* - unquoted ) when bracket depth > 0 terminates a bracket and
3268
* adds a CMD_LIST structure with null command
3269
* - Anything else gets put into the command string (including
3270
* redirects)
3271
*/
3272
enum read_parse_line WCMD_ReadAndParseLine(CMD_NODE **output)
3273
{
3274
WCHAR *curPos;
3275
WCHAR curString[MAXSTRING];
3276
int curStringLen = 0;
3277
WCHAR curRedirs[MAXSTRING];
3278
int curRedirsLen = 0;
3279
WCHAR *curCopyTo;
3280
int *curLen;
3281
static WCHAR *extraSpace = NULL; /* Deliberately never freed */
3282
struct node_builder builder;
3283
BOOL ret;
3284
3285
*output = NULL;
3286
/* Allocate working space for a command read from keyboard, file etc */
3287
if (!extraSpace)
3288
extraSpace = xalloc((MAXSTRING + 1) * sizeof(WCHAR));
3289
3290
if (!(curPos = fetch_next_line(TRUE, extraSpace)))
3291
return RPL_EOF;
3292
3293
TRACE("About to parse line (%ls)\n", extraSpace);
3294
3295
node_builder_init(&builder);
3296
3297
/* Start with an empty string, copying to the command string */
3298
curStringLen = 0;
3299
curRedirsLen = 0;
3300
curCopyTo = curString;
3301
curLen = &curStringLen;
3302
3303
curPos = WCMD_strip_for_command_start(curPos);
3304
/* Parse every character on the line being processed */
3305
for (;;) {
3306
/* Debugging AID:
3307
WINE_TRACE("Looking at '%c' (len:%d)\n", *curPos, *curLen);
3308
*/
3309
3310
/* Prevent overflow caused by the caret escape char */
3311
if (*curLen >= MAXSTRING) {
3312
WINE_ERR("Overflow detected in command\n");
3313
return RPL_SYNTAXERROR;
3314
}
3315
3316
/* If we have reached the end, add this command into the list
3317
Do not add command to list if escape char ^ was last */
3318
if (*curPos == L'\0') {
3319
/* Add an entry to the command list */
3320
lexer_push_command(&builder, curString, &curStringLen,
3321
curRedirs, &curRedirsLen,
3322
&curCopyTo, &curLen);
3323
node_builder_push_token(&builder, TKN_EOL);
3324
3325
/* If we have reached the end of the string, see if bracketing is outstanding */
3326
if (builder.opened_parenthesis > 0 && (curPos = fetch_next_line(FALSE, extraSpace)))
3327
{
3328
TRACE("Need to read more data as outstanding brackets or carets\n");
3329
}
3330
else break;
3331
}
3332
3333
/* Certain commands need special handling */
3334
if (curStringLen == 0 && curCopyTo == curString) {
3335
if (lexer_at_command_start(&builder) && !*(curPos = WCMD_strip_for_command_start(curPos))) continue;
3336
/* If command starts with 'rem ' or identifies a label, use whole line */
3337
if (WCMD_keyword_ws_found(L"rem", curPos) || *curPos == L':') {
3338
size_t line_len = wcslen(curPos);
3339
memcpy(curString, curPos, (line_len + 1) * sizeof(WCHAR));
3340
curPos += line_len;
3341
curStringLen += line_len;
3342
curRedirsLen = 0; /* even '>foo rem' doesn't touch foo */
3343
lexer_push_command(&builder, curString, &curStringLen,
3344
curRedirs, &curRedirsLen,
3345
&curCopyTo, &curLen);
3346
continue;
3347
} else if (WCMD_keyword_ws_found(L"for", curPos)) {
3348
node_builder_push_token(&builder, TKN_FOR);
3349
3350
curPos = WCMD_skip_leading_spaces(curPos + 3); /* "for */
3351
/* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
3352
is only true in the command portion of the IF statement, but this
3353
should suffice for now.
3354
To be able to handle ('s in the condition part take as much as evaluate_if_condition
3355
would take and skip parsing it here. */
3356
} else if (lexer_at_command_start(&builder) && WCMD_keyword_ws_found(L"if", curPos)) {
3357
WCHAR *command;
3358
3359
node_builder_push_token(&builder, TKN_IF);
3360
3361
curPos = WCMD_skip_leading_spaces(curPos + 2); /* "if" */
3362
if (if_condition_parse(curPos, &command, NULL))
3363
{
3364
int if_condition_len = command - curPos;
3365
TRACE("p: %s, command: %s, if_condition_len: %d\n",
3366
wine_dbgstr_w(curPos), wine_dbgstr_w(command), if_condition_len);
3367
memcpy(&curCopyTo[*curLen], curPos, if_condition_len * sizeof(WCHAR));
3368
(*curLen) += if_condition_len;
3369
curPos += if_condition_len;
3370
3371
/* FIXME we do parsing twice of condition (once here, second time in node_builder_parse) */
3372
lexer_push_command(&builder, curString, &curStringLen,
3373
curRedirs, &curRedirsLen,
3374
&curCopyTo, &curLen);
3375
3376
}
3377
continue;
3378
} else if (WCMD_keyword_ws_found(L"else", curPos)) {
3379
node_builder_push_token(&builder, TKN_ELSE);
3380
3381
curPos = WCMD_skip_leading_spaces(curPos + 4 /* else */);
3382
continue;
3383
3384
/* In a for loop, the DO command will follow a close bracket followed by
3385
whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
3386
is then 0, and all whitespace is skipped */
3387
} else if (lexer_can_accept_do(&builder) && WCMD_keyword_ws_found(L"do", curPos)) {
3388
3389
WINE_TRACE("Found 'DO '\n");
3390
3391
node_builder_push_token(&builder, TKN_DO);
3392
curPos = WCMD_skip_leading_spaces(curPos + 2 /* do */);
3393
continue;
3394
}
3395
} else if (curCopyTo == curString) {
3396
3397
/* Special handling for the 'FOR' command */
3398
if (node_builder_top(&builder, 0) == TKN_FOR) {
3399
WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
3400
3401
if (WCMD_keyword_ws_found(L"in", curPos)) {
3402
WINE_TRACE("Found 'IN '\n");
3403
3404
lexer_push_command(&builder, curString, &curStringLen,
3405
curRedirs, &curRedirsLen,
3406
&curCopyTo, &curLen);
3407
node_builder_push_token(&builder, TKN_IN);
3408
curPos = WCMD_skip_leading_spaces(curPos + 2 /* in */);
3409
continue;
3410
}
3411
}
3412
}
3413
3414
switch (*curPos) {
3415
3416
case L'=': /* drop through - ignore token delimiters at the start of a command */
3417
case L',': /* drop through - ignore token delimiters at the start of a command */
3418
case L'\t':/* drop through - ignore token delimiters at the start of a command */
3419
case L' ':
3420
/* If finishing off a redirect, add a whitespace delimiter */
3421
if (curCopyTo == curRedirs) {
3422
curCopyTo[(*curLen)++] = L' ';
3423
curCopyTo = curString;
3424
curLen = &curStringLen;
3425
}
3426
if (*curLen > 0)
3427
curCopyTo[(*curLen)++] = *curPos;
3428
break;
3429
3430
case L'>': /* drop through - handle redirect chars the same */
3431
case L'<':
3432
/* Make a redirect start here */
3433
curCopyTo = curRedirs;
3434
curLen = &curRedirsLen;
3435
3436
/* See if 1>, 2> etc, in which case we have some patching up
3437
to do (provided there's a preceding whitespace, and enough
3438
chars read so far) */
3439
if (curStringLen && curPos[-1] >= L'1' && curPos[-1] <= L'9' &&
3440
(curStringLen == 1 || iswspace(curPos[-2])))
3441
{
3442
curStringLen--;
3443
curString[curStringLen] = L'\0';
3444
curCopyTo[(*curLen)++] = curPos[-1];
3445
}
3446
3447
curCopyTo[(*curLen)++] = *curPos;
3448
3449
/* If a redirect is immediately followed by '&' (ie. 2>&1) then
3450
do not process that ampersand as an AND operator */
3451
if ((*curPos == L'>' || *curPos == L'<') && curPos[1] == L'&')
3452
{
3453
curCopyTo[(*curLen)++] = curPos[1];
3454
curPos++;
3455
}
3456
/* advance until start of filename */
3457
while (iswspace(curPos[1]) || curPos[1] == L',' || curPos[1] == L'=')
3458
{
3459
curCopyTo[(*curLen)++] = curPos[1];
3460
curPos++;
3461
}
3462
break;
3463
3464
case L'|': /* Pipe character only if not || */
3465
lexer_push_command(&builder, curString, &curStringLen,
3466
curRedirs, &curRedirsLen,
3467
&curCopyTo, &curLen);
3468
3469
if (curPos[1] == L'|') {
3470
curPos++; /* Skip other | */
3471
node_builder_push_token(&builder, TKN_BARBAR);
3472
} else {
3473
node_builder_push_token(&builder, TKN_BAR);
3474
}
3475
break;
3476
3477
case L'"':
3478
/* copy all chars between a pair of " */
3479
curCopyTo[(*curLen)++] = *curPos;
3480
while (curPos[1])
3481
{
3482
curCopyTo[(*curLen)++] = *++curPos;
3483
if (*curPos == L'"') break;
3484
}
3485
break;
3486
3487
case L'(': /* If a '(' is the first non whitespace in a command portion
3488
ie start of line or just after &&, then we read until an
3489
unquoted ) is found */
3490
3491
/* In a FOR loop, an unquoted '(' may occur straight after
3492
IN or DO
3493
In an IF statement just handle it regardless as we don't
3494
parse the operands
3495
In an ELSE statement, only allow it straight away after
3496
the ELSE and whitespace
3497
*/
3498
if ((lexer_at_command_start(&builder) || node_builder_top(&builder, 0) == TKN_IN) &&
3499
lexer_white_space_only(curString, curStringLen)) {
3500
node_builder_push_token(&builder, TKN_OPENPAR);
3501
} else {
3502
curCopyTo[(*curLen)++] = *curPos;
3503
}
3504
break;
3505
3506
case L'^': /* If we reach the end of the input, we need to wait for more */
3507
if (curPos[1] == L'\0') {
3508
TRACE("Caret found at end of line\n");
3509
extraSpace[0] = L'^';
3510
if (!fetch_next_line(FALSE, extraSpace + 1))
3511
break;
3512
if (!extraSpace[1]) /* empty line */
3513
{
3514
extraSpace[1] = L'\r';
3515
if (!fetch_next_line(FALSE, extraSpace + 2))
3516
break;
3517
}
3518
curPos = extraSpace;
3519
break;
3520
}
3521
curPos++;
3522
curCopyTo[(*curLen)++] = *curPos;
3523
break;
3524
3525
case L'&':
3526
/* Add an entry to the command list */
3527
lexer_push_command(&builder, curString, &curStringLen,
3528
curRedirs, &curRedirsLen,
3529
&curCopyTo, &curLen);
3530
3531
if (*(curPos+1) == L'&') {
3532
curPos++; /* Skip other & */
3533
node_builder_push_token(&builder, TKN_AMPAMP);
3534
} else {
3535
node_builder_push_token(&builder, TKN_AMP);
3536
}
3537
break;
3538
3539
case L')':
3540
if (builder.opened_parenthesis > 0) {
3541
/* Add the current command if there is one */
3542
lexer_push_command(&builder, curString, &curStringLen,
3543
curRedirs, &curRedirsLen,
3544
&curCopyTo, &curLen);
3545
node_builder_push_token(&builder, TKN_CLOSEPAR);
3546
} else if (curStringLen == 0 && curCopyTo == curString) {
3547
/* unmatched closing ')': silently skip rest of line */
3548
curPos += wcslen(curPos) - 1;
3549
} else {
3550
curCopyTo[(*curLen)++] = *curPos;
3551
}
3552
break;
3553
default:
3554
curCopyTo[(*curLen)++] = *curPos;
3555
}
3556
3557
curPos++;
3558
}
3559
3560
ret = node_builder_generate(&builder, output);
3561
node_builder_dispose(&builder);
3562
3563
return ret ? RPL_SUCCESS : RPL_SYNTAXERROR;
3564
}
3565
3566
static BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test)
3567
{
3568
WCHAR expanded_left[MAXSTRING];
3569
WCHAR expanded_right[MAXSTRING];
3570
int (WINAPI *cmp)(const WCHAR*, const WCHAR*) = cond->case_insensitive ? lstrcmpiW : lstrcmpW;
3571
3572
TRACE("About to evaluate condition %s\n", debugstr_if_condition(cond));
3573
*test = 0;
3574
switch (cond->op)
3575
{
3576
case CMD_IF_ERRORLEVEL:
3577
{
3578
WCHAR *endptr;
3579
int level;
3580
3581
wcscpy(expanded_left, cond->operand);
3582
handleExpansion(expanded_left, TRUE);
3583
level = wcstol(expanded_left, &endptr, 10);
3584
if (*endptr) return FALSE;
3585
*test = errorlevel >= level;
3586
}
3587
break;
3588
case CMD_IF_EXIST:
3589
{
3590
size_t len;
3591
WIN32_FIND_DATAW fd;
3592
HANDLE hff;
3593
3594
wcscpy(expanded_left, cond->operand);
3595
handleExpansion(expanded_left, TRUE);
3596
if ((len = wcslen(expanded_left)))
3597
{
3598
if (!wcspbrk(expanded_left, L"*?"))
3599
*test = GetFileAttributesW(expanded_left) != INVALID_FILE_ATTRIBUTES;
3600
else
3601
{
3602
hff = FindFirstFileW(expanded_left, &fd);
3603
*test = (hff != INVALID_HANDLE_VALUE);
3604
if (*test) FindClose(hff);
3605
}
3606
}
3607
}
3608
break;
3609
case CMD_IF_DEFINED:
3610
wcscpy(expanded_left, cond->operand);
3611
handleExpansion(expanded_left, TRUE);
3612
*test = GetEnvironmentVariableW(expanded_left, NULL, 0) > 0;
3613
break;
3614
case CMD_IF_BINOP_EQUAL:
3615
wcscpy(expanded_left, cond->left);
3616
handleExpansion(expanded_left, TRUE);
3617
wcscpy(expanded_right, cond->right);
3618
handleExpansion(expanded_right, TRUE);
3619
3620
/* == is a special case, as it always compares strings */
3621
*test = (*cmp)(expanded_left, expanded_right) == 0;
3622
break;
3623
default:
3624
{
3625
int left_int, right_int;
3626
WCHAR *end_left, *end_right;
3627
int cmp_val;
3628
3629
wcscpy(expanded_left, cond->left);
3630
handleExpansion(expanded_left, TRUE);
3631
wcscpy(expanded_right, cond->right);
3632
handleExpansion(expanded_right, TRUE);
3633
3634
/* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
3635
left_int = wcstol(expanded_left, &end_left, 0);
3636
right_int = wcstol(expanded_right, &end_right, 0);
3637
if (end_left > expanded_left && !*end_left && end_right > expanded_right && !*end_right)
3638
cmp_val = left_int - right_int;
3639
else
3640
cmp_val = (*cmp)(expanded_left, expanded_right);
3641
switch (cond->op)
3642
{
3643
case CMD_IF_BINOP_LSS: *test = cmp_val < 0; break;
3644
case CMD_IF_BINOP_LEQ: *test = cmp_val <= 0; break;
3645
case CMD_IF_BINOP_EQU: *test = cmp_val == 0; break;
3646
case CMD_IF_BINOP_NEQ: *test = cmp_val != 0; break;
3647
case CMD_IF_BINOP_GEQ: *test = cmp_val >= 0; break;
3648
case CMD_IF_BINOP_GTR: *test = cmp_val > 0; break;
3649
default:
3650
FIXME("Unexpected comparison operator %u\n", cond->op);
3651
return FALSE;
3652
}
3653
}
3654
break;
3655
}
3656
if (cond->negated) *test ^= 1;
3657
return TRUE;
3658
}
3659
3660
struct for_loop_variables
3661
{
3662
unsigned char table[32];
3663
unsigned last, num_duplicates;
3664
unsigned has_star;
3665
};
3666
3667
static void for_loop_variables_init(struct for_loop_variables *flv)
3668
{
3669
flv->last = flv->num_duplicates = 0;
3670
flv->has_star = FALSE;
3671
}
3672
3673
static BOOL for_loop_variables_push(struct for_loop_variables *flv, unsigned char o)
3674
{
3675
unsigned i;
3676
for (i = 0; i < flv->last; i++)
3677
if (flv->table[i] == o)
3678
{
3679
flv->num_duplicates++;
3680
return TRUE;
3681
}
3682
if (flv->last >= ARRAY_SIZE(flv->table)) return FALSE;
3683
flv->table[flv->last] = o;
3684
flv->last++;
3685
return TRUE;
3686
}
3687
3688
static int my_flv_compare(const void *a1, const void *a2)
3689
{
3690
return *(const char*)a1 - *(const char*)a2;
3691
}
3692
3693
static unsigned for_loop_variables_max(const struct for_loop_variables *flv)
3694
{
3695
return flv->last == 0 ? -1 : flv->table[flv->last - 1];
3696
}
3697
3698
static BOOL for_loop_fill_variables(const WCHAR *forf_tokens, struct for_loop_variables *flv)
3699
{
3700
const WCHAR *pos = forf_tokens;
3701
3702
/* Loop through the token string, parsing it.
3703
* Valid syntax is: token=m or x-y with comma delimiter and optionally * to finish
3704
*/
3705
while (*pos)
3706
{
3707
unsigned num;
3708
WCHAR *nextchar;
3709
3710
if (*pos == L'*')
3711
{
3712
if (pos[1] != L'\0') return FALSE;
3713
qsort(flv->table, flv->last, sizeof(flv->table[0]), my_flv_compare);
3714
if (flv->num_duplicates)
3715
{
3716
flv->num_duplicates++;
3717
return TRUE;
3718
}
3719
flv->has_star = TRUE;
3720
return for_loop_variables_push(flv, for_loop_variables_max(flv) + 1);
3721
}
3722
3723
/* Get the next number */
3724
num = wcstoul(pos, &nextchar, 10);
3725
if (!num || num >= 32) return FALSE;
3726
num--;
3727
/* range x-y */
3728
if (*nextchar == L'-')
3729
{
3730
unsigned int end = wcstoul(nextchar + 1, &nextchar, 10);
3731
if (!end || end >= 32) return FALSE;
3732
end--;
3733
while (num <= end)
3734
if (!for_loop_variables_push(flv, num++)) return FALSE;
3735
}
3736
else if (!for_loop_variables_push(flv, num)) return FALSE;
3737
3738
pos = nextchar;
3739
if (*pos == L',') pos++;
3740
}
3741
if (flv->last)
3742
qsort(flv->table, flv->last, sizeof(flv->table[0]), my_flv_compare);
3743
else
3744
for_loop_variables_push(flv, 0);
3745
return TRUE;
3746
}
3747
3748
static RETURN_CODE for_loop_fileset_parse_line(CMD_NODE *node, unsigned varidx, WCHAR *buffer,
3749
WCHAR forf_eol, const WCHAR *forf_delims, const WCHAR *forf_tokens)
3750
{
3751
RETURN_CODE return_code = NO_ERROR;
3752
struct for_loop_variables flv;
3753
WCHAR *parm;
3754
unsigned i;
3755
3756
for_loop_variables_init(&flv);
3757
if (!for_loop_fill_variables(forf_tokens, &flv))
3758
{
3759
TRACE("Error while parsing tokens=%ls\n", forf_tokens);
3760
return ERROR_INVALID_FUNCTION;
3761
}
3762
3763
TRACE("Using var=%s on %u max%s\n", debugstr_for_var(varidx), flv.last, flv.has_star ? " with star" : "");
3764
/* Empty out variables */
3765
for (i = 0; i < flv.last + flv.num_duplicates; i++)
3766
WCMD_set_for_loop_variable(varidx + i, L"");
3767
3768
for (i = 0; i < flv.last; i++)
3769
{
3770
if (flv.has_star && i + 1 == flv.last)
3771
{
3772
WCMD_parameter_with_delims(buffer, flv.table[i], &parm, FALSE, FALSE, forf_delims);
3773
TRACE("Parsed all remaining tokens %d(%s) as parameter %s\n",
3774
flv.table[i], debugstr_for_var(varidx + i), wine_dbgstr_w(parm));
3775
if (parm)
3776
WCMD_set_for_loop_variable(varidx + i, parm);
3777
break;
3778
}
3779
/* Extract the token number requested and set into the next variable context */
3780
parm = WCMD_parameter_with_delims(buffer, flv.table[i], NULL, TRUE, FALSE, forf_delims);
3781
TRACE("Parsed token %d(%s) as parameter %s\n",
3782
flv.table[i], debugstr_for_var(varidx + i), wine_dbgstr_w(parm));
3783
if (parm)
3784
WCMD_set_for_loop_variable(varidx + i, parm);
3785
}
3786
3787
/* Execute the body of the for loop with these values */
3788
if (forloopcontext->variable[varidx] && forloopcontext->variable[varidx][0] != forf_eol)
3789
{
3790
return_code = node_execute(node);
3791
}
3792
else
3793
{
3794
TRACE("Skipping line because of eol\n");
3795
}
3796
return return_code;
3797
}
3798
3799
void WCMD_save_for_loop_context(BOOL reset)
3800
{
3801
FOR_CONTEXT *new = xalloc(sizeof(*new));
3802
if (reset)
3803
memset(new, 0, sizeof(*new));
3804
else /* clone existing */
3805
*new = *forloopcontext;
3806
new->previous = forloopcontext;
3807
forloopcontext = new;
3808
}
3809
3810
void WCMD_restore_for_loop_context(void)
3811
{
3812
FOR_CONTEXT *old = forloopcontext->previous;
3813
unsigned varidx;
3814
if (!old)
3815
{
3816
FIXME("Unexpected situation\n");
3817
return;
3818
}
3819
for (varidx = 0; varidx < ARRAY_SIZE(forloopcontext->variable); varidx++)
3820
{
3821
if (forloopcontext->variable[varidx] != old->variable[varidx])
3822
free(forloopcontext->variable[varidx]);
3823
}
3824
free(forloopcontext);
3825
forloopcontext = old;
3826
}
3827
3828
void WCMD_set_for_loop_variable(unsigned varidx, const WCHAR *value)
3829
{
3830
if (!for_var_is_valid(varidx)) return;
3831
if (forloopcontext->previous &&
3832
forloopcontext->previous->variable[varidx] != forloopcontext->variable[varidx])
3833
free(forloopcontext->variable[varidx]);
3834
forloopcontext->variable[varidx] = xstrdupW(value);
3835
}
3836
3837
static BOOL match_ending_delim(WCHAR *string)
3838
{
3839
WCHAR *to = string + wcslen(string);
3840
3841
/* strip trailing delim */
3842
if (to > string) to--;
3843
if (to > string && *to == string[0])
3844
{
3845
*to = L'\0';
3846
return TRUE;
3847
}
3848
WARN("Can't find ending delimiter (%ls)\n", string);
3849
return FALSE;
3850
}
3851
3852
static RETURN_CODE for_control_execute_from_FILE(CMD_FOR_CONTROL *for_ctrl, FILE *input, CMD_NODE *node)
3853
{
3854
WCHAR buffer[MAXSTRING];
3855
int skip_count = for_ctrl->num_lines_to_skip;
3856
RETURN_CODE return_code = NO_ERROR;
3857
3858
/* Read line by line until end of file */
3859
while (!WCMD_is_break(return_code) && fgetws(buffer, ARRAY_SIZE(buffer), input))
3860
{
3861
size_t len;
3862
3863
if (skip_count)
3864
{
3865
TRACE("skipping %d\n", skip_count);
3866
skip_count--;
3867
continue;
3868
}
3869
len = wcslen(buffer);
3870
/* Either our buffer isn't large enough to fit a full line, or there's a stray
3871
* '\0' in the buffer.
3872
*/
3873
if (!feof(input) && (len == 0 || (buffer[len - 1] != '\n' && buffer[len - 1] != '\r')))
3874
break;
3875
while (len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r'))
3876
buffer[--len] = L'\0';
3877
return_code = for_loop_fileset_parse_line(node, for_ctrl->variable_index, buffer,
3878
for_ctrl->eol, for_ctrl->delims, for_ctrl->tokens);
3879
buffer[0] = 0;
3880
}
3881
return return_code;
3882
}
3883
3884
static RETURN_CODE for_control_execute_fileset(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
3885
{
3886
RETURN_CODE return_code = NO_ERROR;
3887
WCHAR set[MAXSTRING];
3888
WCHAR *args;
3889
size_t len;
3890
FILE *input;
3891
int i;
3892
3893
wcscpy(set, for_ctrl->set);
3894
handleExpansion(set, TRUE);
3895
3896
args = WCMD_skip_leading_spaces(set);
3897
for (len = wcslen(args); len && (args[len - 1] == L' ' || args[len - 1] == L'\t'); len--)
3898
args[len - 1] = L'\0';
3899
if (args[0] == (for_ctrl->use_backq ? L'\'' : L'"') && match_ending_delim(args))
3900
{
3901
args++;
3902
if (!for_ctrl->num_lines_to_skip)
3903
{
3904
return_code = for_loop_fileset_parse_line(node, for_ctrl->variable_index, args,
3905
for_ctrl->eol, for_ctrl->delims, for_ctrl->tokens);
3906
}
3907
}
3908
else if (args[0] == (for_ctrl->use_backq ? L'`' : L'\'') && match_ending_delim(args))
3909
{
3910
WCHAR temp_cmd[MAX_PATH];
3911
3912
args++;
3913
wsprintfW(temp_cmd, L"CMD.EXE /C %s", args);
3914
TRACE("Reading output of '%s'\n", wine_dbgstr_w(temp_cmd));
3915
input = _wpopen(temp_cmd, L"rt,ccs=unicode");
3916
if (!input)
3917
{
3918
WCMD_print_error();
3919
WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), args);
3920
return errorlevel = ERROR_INVALID_FUNCTION; /* FOR loop aborts at first failure here */
3921
}
3922
return_code = for_control_execute_from_FILE(for_ctrl, input, node);
3923
pclose(input);
3924
}
3925
else
3926
{
3927
for (i = 0; !WCMD_is_break(return_code); i++)
3928
{
3929
WCHAR *element = WCMD_parameter(args, i, NULL, TRUE, FALSE);
3930
if (!element || !*element) break;
3931
if (element[0] == L'"' && match_ending_delim(element)) element++;
3932
/* Open the file, read line by line and process */
3933
TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(element));
3934
input = _wfopen(element, L"rt,ccs=unicode");
3935
if (!input)
3936
{
3937
WCMD_print_error();
3938
WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), element);
3939
return errorlevel = ERROR_INVALID_FUNCTION; /* FOR loop aborts at first failure here */
3940
}
3941
return_code = for_control_execute_from_FILE(for_ctrl, input, node);
3942
fclose(input);
3943
}
3944
}
3945
3946
return return_code;
3947
}
3948
3949
static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *from_dir, size_t ref_len, CMD_NODE *node)
3950
{
3951
RETURN_CODE return_code = NO_ERROR;
3952
size_t len;
3953
WCHAR set[MAXSTRING];
3954
WCHAR buffer[MAX_PATH];
3955
int i;
3956
3957
if (from_dir)
3958
{
3959
len = wcslen(from_dir) + 1;
3960
wcscpy(buffer, from_dir);
3961
wcscat(buffer, L"\\");
3962
}
3963
else
3964
len = 0;
3965
3966
wcscpy(set, for_ctrl->set);
3967
handleExpansion(set, TRUE);
3968
for (i = 0; !WCMD_is_break(return_code); i++)
3969
{
3970
WCHAR *element = WCMD_parameter(set, i, NULL, TRUE, FALSE);
3971
if (!element || !*element) break;
3972
if (len + wcslen(element) + 1 >= ARRAY_SIZE(buffer)) continue;
3973
3974
wcscpy(&buffer[len], element);
3975
3976
TRACE("Doing set element %ls\n", buffer);
3977
3978
if (wcspbrk(element, L"?*"))
3979
{
3980
WIN32_FIND_DATAW fd;
3981
HANDLE hff = FindFirstFileW(buffer, &fd);
3982
size_t insert_pos = (wcsrchr(buffer, L'\\') ? wcsrchr(buffer, L'\\') + 1 - buffer : 0);
3983
3984
if (hff == INVALID_HANDLE_VALUE)
3985
{
3986
TRACE("Couldn't FindFirstFile on %ls\n", buffer);
3987
continue;
3988
}
3989
do
3990
{
3991
TRACE("Considering %ls\n", fd.cFileName);
3992
if (!lstrcmpW(fd.cFileName, L"..") || !lstrcmpW(fd.cFileName, L".")) continue;
3993
if (!(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_FILES) &&
3994
!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
3995
continue;
3996
if (!(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES) &&
3997
(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
3998
continue;
3999
4000
if (insert_pos + wcslen(fd.cFileName) + 1 >= ARRAY_SIZE(buffer)) continue;
4001
wcscpy(&buffer[insert_pos], fd.cFileName);
4002
WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer);
4003
return_code = node_execute(node);
4004
} while (!WCMD_is_break(return_code) && FindNextFileW(hff, &fd) != 0);
4005
FindClose(hff);
4006
}
4007
else
4008
{
4009
WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer);
4010
return_code = node_execute(node);
4011
}
4012
}
4013
return return_code;
4014
}
4015
4016
static RETURN_CODE for_control_execute_walk_files(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
4017
{
4018
DIRECTORY_STACK *dirs_to_walk;
4019
size_t ref_len;
4020
RETURN_CODE return_code = NO_ERROR;
4021
4022
if (for_ctrl->root_dir)
4023
{
4024
WCHAR buffer[MAXSTRING];
4025
4026
wcscpy(buffer, for_ctrl->root_dir);
4027
handleExpansion(buffer, TRUE);
4028
dirs_to_walk = WCMD_dir_stack_create(buffer, NULL);
4029
}
4030
else dirs_to_walk = WCMD_dir_stack_create(NULL, NULL);
4031
ref_len = wcslen(dirs_to_walk->dirName);
4032
4033
while (!WCMD_is_break(return_code) && dirs_to_walk)
4034
{
4035
TRACE("About to walk %p %ls for %s\n", dirs_to_walk, dirs_to_walk->dirName, debugstr_for_control(for_ctrl));
4036
if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE)
4037
WCMD_add_dirstowalk(dirs_to_walk);
4038
4039
return_code = for_control_execute_set(for_ctrl, dirs_to_walk->dirName, ref_len, node);
4040
/* If we are walking directories, move on to any which remain */
4041
dirs_to_walk = WCMD_dir_stack_free(dirs_to_walk);
4042
}
4043
TRACE("Finished all directories.\n");
4044
4045
return return_code;
4046
}
4047
4048
static RETURN_CODE for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
4049
{
4050
RETURN_CODE return_code = NO_ERROR;
4051
WCHAR set[MAXSTRING];
4052
int numbers[3] = {0, 0, 0}, var;
4053
int i;
4054
4055
if (for_ctrl->set)
4056
{
4057
wcscpy(set, for_ctrl->set);
4058
handleExpansion(set, TRUE);
4059
}
4060
else
4061
set[0] = L'\0';
4062
4063
/* Note: native doesn't check the actual number of parameters, and set
4064
* them by default to 0.
4065
* so (-10 1) is interpreted as (-10 1 0)
4066
* and (10) loops for ever !!!
4067
*/
4068
for (i = 0; i < ARRAY_SIZE(numbers); i++)
4069
{
4070
WCHAR *element = WCMD_parameter(set, i, NULL, FALSE, FALSE);
4071
if (!element || !*element) break;
4072
/* native doesn't no error handling */
4073
numbers[i] = wcstol(element, NULL, 0);
4074
}
4075
4076
for (var = numbers[0];
4077
!WCMD_is_break(return_code) && ((numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2]);
4078
var += numbers[1])
4079
{
4080
WCHAR tmp[32];
4081
4082
swprintf(tmp, ARRAY_SIZE(tmp), L"%d", var);
4083
WCMD_set_for_loop_variable(for_ctrl->variable_index, tmp);
4084
TRACE("Processing FOR number %s\n", wine_dbgstr_w(tmp));
4085
return_code = node_execute(node);
4086
}
4087
return return_code;
4088
}
4089
4090
static RETURN_CODE for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
4091
{
4092
RETURN_CODE return_code;
4093
4094
if (!for_ctrl->set && for_ctrl->operator != CMD_FOR_NUMBERS) return NO_ERROR;
4095
4096
WCMD_save_for_loop_context(FALSE);
4097
4098
switch (for_ctrl->operator)
4099
{
4100
case CMD_FOR_FILETREE:
4101
if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE)
4102
return_code = for_control_execute_walk_files(for_ctrl, node);
4103
else
4104
return_code = for_control_execute_set(for_ctrl, NULL, 0, node);
4105
break;
4106
case CMD_FOR_FILE_SET:
4107
return_code = for_control_execute_fileset(for_ctrl, node);
4108
break;
4109
case CMD_FOR_NUMBERS:
4110
return_code = for_control_execute_numbers(for_ctrl, node);
4111
break;
4112
default:
4113
return_code = NO_ERROR;
4114
break;
4115
}
4116
WCMD_restore_for_loop_context();
4117
return return_code;
4118
}
4119
4120
RETURN_CODE node_execute(CMD_NODE *node)
4121
{
4122
HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
4123
GetStdHandle (STD_OUTPUT_HANDLE),
4124
GetStdHandle (STD_ERROR_HANDLE)};
4125
static DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE};
4126
4127
RETURN_CODE return_code;
4128
int i, test;
4129
4130
if (!node) return NO_ERROR;
4131
if (!set_std_redirections(node->redirects))
4132
{
4133
WCMD_print_error();
4134
return_code = ERROR_INVALID_FUNCTION;
4135
}
4136
else switch (node->op)
4137
{
4138
case CMD_SINGLE:
4139
if (node->command[0] != ':')
4140
return_code = execute_single_command(node->command);
4141
else return_code = NO_ERROR;
4142
break;
4143
case CMD_CONCAT:
4144
return_code = node_execute(node->left);
4145
if (!WCMD_is_break(return_code))
4146
return_code = node_execute(node->right);
4147
break;
4148
case CMD_ONSUCCESS:
4149
return_code = node_execute(node->left);
4150
if (return_code == NO_ERROR)
4151
return_code = node_execute(node->right);
4152
break;
4153
case CMD_ONFAILURE:
4154
return_code = node_execute(node->left);
4155
if (return_code != NO_ERROR && !WCMD_is_break(return_code))
4156
{
4157
/* that's needed for commands (POPD, RMDIR) that don't set errorlevel in case of failure. */
4158
errorlevel = return_code;
4159
return_code = node_execute(node->right);
4160
}
4161
break;
4162
case CMD_PIPE:
4163
{
4164
static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE};
4165
WCHAR temp_path[MAX_PATH];
4166
WCHAR filename[MAX_PATH];
4167
CMD_REDIRECTION *output;
4168
HANDLE saved_output;
4169
struct batch_context *saved_context = context;
4170
4171
/* pipe LHS & RHS are run outside of any batch context */
4172
context = NULL;
4173
/* FIXME: a real pipe instead of writing to an intermediate file would be
4174
* better.
4175
* But waiting for completion of commands will require more work.
4176
*/
4177
/* FIXME check precedence (eg foo > a | more)
4178
* with following code, | has higher precedence than > a
4179
* (which is likely wrong IIRC, and not what previous code was doing)
4180
*/
4181
/* Generate a unique temporary filename */
4182
GetTempPathW(ARRAY_SIZE(temp_path), temp_path);
4183
GetTempFileNameW(temp_path, L"CMD", 0, filename);
4184
TRACE("Using temporary file of %ls\n", filename);
4185
4186
saved_output = GetStdHandle(STD_OUTPUT_HANDLE);
4187
/* set output for left hand side command */
4188
output = redirection_create_file(REDIR_WRITE_TO, 1, filename);
4189
if (set_std_redirections(output))
4190
{
4191
RETURN_CODE return_code_left = node_execute(node->left);
4192
CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
4193
SetStdHandle(STD_OUTPUT_HANDLE, saved_output);
4194
4195
if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context)
4196
ExitProcess(255);
4197
return_code = ERROR_INVALID_FUNCTION;
4198
if (!WCMD_is_break(return_code_left) && errorlevel != RETURN_CODE_CANT_LAUNCH)
4199
{
4200
HANDLE h = CreateFileW(filename, GENERIC_READ,
4201
FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
4202
FILE_ATTRIBUTE_NORMAL, NULL);
4203
if (h != INVALID_HANDLE_VALUE)
4204
{
4205
SetStdHandle(STD_INPUT_HANDLE, h);
4206
return_code = node_execute(node->right);
4207
if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context)
4208
ExitProcess(255);
4209
}
4210
}
4211
DeleteFileW(filename);
4212
errorlevel = return_code;
4213
}
4214
else return_code = ERROR_INVALID_FUNCTION;
4215
redirection_dispose_list(output);
4216
context = saved_context;
4217
}
4218
break;
4219
case CMD_IF:
4220
if (if_condition_evaluate(&node->condition, &test))
4221
return_code = node_execute(test ? node->then_block : node->else_block);
4222
else
4223
return_code = ERROR_INVALID_FUNCTION;
4224
break;
4225
case CMD_FOR:
4226
return_code = for_control_execute(&node->for_ctrl, node->do_block);
4227
break;
4228
default:
4229
FIXME("Unexpected operator %u\n", node->op);
4230
return_code = ERROR_INVALID_FUNCTION;
4231
}
4232
/* Restore old handles */
4233
for (i = 0; i < 3; i++)
4234
{
4235
if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i]))
4236
{
4237
CloseHandle(GetStdHandle(idx_stdhandles[i]));
4238
SetStdHandle(idx_stdhandles[i], old_stdhandles[i]);
4239
}
4240
}
4241
return return_code;
4242
}
4243
4244
4245
RETURN_CODE WCMD_ctrlc_status(void)
4246
{
4247
return (WAIT_OBJECT_0 == WaitForSingleObject(control_c_event, 0)) ? STATUS_CONTROL_C_EXIT : NO_ERROR;
4248
}
4249
4250
static BOOL WINAPI my_event_handler(DWORD ctrl)
4251
{
4252
WCMD_output(L"\n");
4253
if (ctrl == CTRL_C_EVENT)
4254
{
4255
SetEvent(control_c_event);
4256
}
4257
return ctrl == CTRL_C_EVENT;
4258
}
4259
4260
static BOOL query_default_color_key(HKEY from_key, DWORD *value)
4261
{
4262
HKEY key;
4263
BOOL ret = FALSE;
4264
4265
if (RegOpenKeyExW(from_key, L"Software\\Microsoft\\Command Processor", 0, KEY_READ, &key) == ERROR_SUCCESS)
4266
{
4267
DWORD size, type;
4268
4269
/* See if DWORD or REG_SZ */
4270
if (RegQueryValueExW(key, L"DefaultColor", NULL, &type, NULL, NULL) == ERROR_SUCCESS)
4271
{
4272
if (type == REG_DWORD)
4273
{
4274
size = sizeof(DWORD);
4275
ret = RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)value, &size);
4276
}
4277
else if (type == REG_SZ)
4278
{
4279
WCHAR strvalue[4];
4280
4281
size = sizeof(strvalue);
4282
if (RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size))
4283
{
4284
WCHAR *end;
4285
*value = wcstoul(strvalue, &end, 10);
4286
ret = *end == L'\0';
4287
}
4288
}
4289
}
4290
RegCloseKey(key);
4291
}
4292
return ret;
4293
}
4294
4295
static void set_console_default_color(unsigned color)
4296
{
4297
if (color >= 0x100) /* no value from command line */
4298
{
4299
/* Check HKCU\Software\Microsoft\Command Processor
4300
* Then HKLM\Software\Microsoft\Command Processor
4301
* for defaultcolor value
4302
* Note Can be supplied as DWORD or REG_SZ
4303
* Note2 When supplied as REG_SZ it's in decimal!!!
4304
*/
4305
DWORD value;
4306
if (query_default_color_key(HKEY_CURRENT_USER, &value) ||
4307
query_default_color_key(HKEY_LOCAL_MACHINE, &value))
4308
color = value;
4309
}
4310
if (color >= 0x100 || ((color >> 4) == (color & 0xf)))
4311
color = 7;
4312
swprintf(param1, ARRAY_SIZE(param1), L"%x", color);
4313
WCMD_color();
4314
}
4315
4316
struct cmd_parameters
4317
{
4318
unsigned default_color;
4319
BOOL opt_c, opt_q, opt_k;
4320
WCHAR* initial_command;
4321
};
4322
4323
static void parse_command_line_parameters(struct cmd_parameters *parameters)
4324
{
4325
WCHAR *cmd_line, *arg;
4326
BOOL opt_s;
4327
4328
parameters->default_color = 0x100;
4329
parameters->opt_c = parameters->opt_k = parameters->opt_q = FALSE;
4330
parameters->initial_command = NULL;
4331
4332
opt_s = FALSE;
4333
/* Can't use argc/argv as it will have stripped quotes from parameters
4334
* meaning cmd.exe /C echo "quoted string" is impossible
4335
*/
4336
cmd_line = GetCommandLineW();
4337
WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmd_line));
4338
4339
while (*cmd_line && *cmd_line != L'/') ++cmd_line;
4340
4341
for (arg = cmd_line; *arg; ++arg)
4342
{
4343
if (arg[0] != L'/')
4344
continue;
4345
4346
switch (towlower(arg[1]))
4347
{
4348
case L'a':
4349
unicodeOutput = FALSE;
4350
break;
4351
case L'c':
4352
parameters->opt_c = TRUE;
4353
break;
4354
case L'k':
4355
parameters->opt_k = TRUE;
4356
break;
4357
case L'q':
4358
parameters->opt_q = TRUE;
4359
break;
4360
case L's':
4361
opt_s = TRUE;
4362
break;
4363
case L't':
4364
if (arg[2] == ':')
4365
{
4366
WCHAR *end;
4367
unsigned long v = wcstoul(arg + 3, &end, 16);
4368
if (end == arg + 5 && !*end)
4369
parameters->default_color = v;
4370
}
4371
break;
4372
case L'u':
4373
unicodeOutput = TRUE;
4374
break;
4375
case L'v':
4376
if (arg[2] == L':')
4377
delayedsubst = wcsnicmp(&arg[3], L"OFF", 3);
4378
break;
4379
}
4380
4381
if (parameters->opt_c || parameters->opt_k)
4382
{
4383
arg += 2;
4384
break;
4385
}
4386
}
4387
4388
while (*arg && wcschr(L" \t,=;", *arg)) arg++;
4389
4390
if (parameters->opt_c || parameters->opt_k)
4391
{
4392
WCHAR *q1 = NULL, *q2 = NULL, *p;
4393
4394
/* Take a copy */
4395
parameters->initial_command = xstrdupW(arg);
4396
4397
/* opt_s left unflagged if the command starts with and contains exactly
4398
* one quoted string (exactly two quote characters). The quoted string
4399
* must be an executable name that has whitespace and must not have the
4400
* following characters: &<>()@^|
4401
*/
4402
4403
/* 1. Confirm there is at least one quote */
4404
if (!opt_s && !(q1 = wcschr(arg, L'"'))) opt_s = TRUE;
4405
/* 2. Confirm there is a second quote */
4406
if (!opt_s && !(q2 = wcschr(q1 + 1, L'"'))) opt_s = TRUE;
4407
/* 3. Ensure there are no more quotes */
4408
if (!opt_s && wcschr(q2 + 1, L'"')) opt_s = TRUE;
4409
4410
/* check first parameter for a space and invalid characters. There must not be any
4411
* invalid characters, but there must be one or more whitespace
4412
*/
4413
if (!opt_s)
4414
{
4415
opt_s = TRUE;
4416
for (p = q1; p != q2; p++)
4417
{
4418
if (wcschr(L"&<>()@^'", *p))
4419
{
4420
opt_s = TRUE;
4421
break;
4422
}
4423
if (iswspace(*p)) opt_s = FALSE;
4424
}
4425
}
4426
4427
WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(parameters->initial_command));
4428
4429
/* Finally, we only stay in new mode IF the first parameter is quoted and
4430
* is a valid executable, i.e. must exist, otherwise drop back to old mode
4431
*/
4432
if (!opt_s)
4433
{
4434
struct search_command sc;
4435
4436
if (search_command(parameters->initial_command, &sc, TRUE) != NO_ERROR) /* no command found */
4437
{
4438
WINE_TRACE("Binary not found, dropping back to old behaviour\n");
4439
opt_s = TRUE;
4440
}
4441
}
4442
4443
/* strip first and last quote characters if opt_s; check for invalid
4444
* executable is done later */
4445
if (opt_s && *parameters->initial_command == L'\"')
4446
WCMD_strip_quotes(parameters->initial_command);
4447
}
4448
}
4449
4450
static void WCMD_setup(void)
4451
{
4452
WCHAR string[MAX_PATH];
4453
RTL_OSVERSIONINFOEXW osv;
4454
char osver[50];
4455
WCHAR *cmd;
4456
4457
srand(time(NULL));
4458
4459
/* initialize some env variables */
4460
if (!GetEnvironmentVariableW(L"COMSPEC", string, ARRAY_SIZE(string)))
4461
{
4462
GetSystemDirectoryW(string, ARRAY_SIZE(string) - ARRAY_SIZE(L"\\cmd.exe"));
4463
lstrcatW(string, L"\\cmd.exe");
4464
SetEnvironmentVariableW(L"COMSPEC", string);
4465
}
4466
SetEnvironmentVariableW(L"PROMPT", L"$P$G");
4467
4468
/* Save cwd into appropriate env var (Must be before the /c processing */
4469
GetCurrentDirectoryW(ARRAY_SIZE(string), string);
4470
if (IsCharAlphaW(string[0]) && string[1] == ':')
4471
{
4472
WCHAR envvar[4];
4473
wsprintfW(envvar, L"=%c:", string[0]);
4474
SetEnvironmentVariableW(envvar, string);
4475
WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
4476
}
4477
4478
/* Get the windows version being emulated */
4479
osv.dwOSVersionInfoSize = sizeof(osv);
4480
RtlGetVersion(&osv);
4481
4482
/* Pre initialize some messages */
4483
lstrcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
4484
sprintf(osver, "%ld.%ld.%ld", osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber);
4485
cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
4486
lstrcpyW(version_string, cmd);
4487
LocalFree(cmd);
4488
4489
/* init for loop context */
4490
forloopcontext = NULL;
4491
WCMD_save_for_loop_context(TRUE);
4492
}
4493
4494
/*****************************************************************************
4495
* Main entry point. This is a console application so we have a main() not a
4496
* winmain().
4497
*/
4498
int __cdecl wmain(int argc, WCHAR *argvW[])
4499
{
4500
struct cmd_parameters parameters;
4501
CMD_NODE *toExecute = NULL;
4502
STARTUPINFOW startupInfo;
4503
enum read_parse_line rpl_status;
4504
4505
WCMD_setup();
4506
4507
parse_command_line_parameters(&parameters);
4508
if (parameters.opt_q) WCMD_echo(L"OFF");
4509
4510
control_c_event = CreateEventW(NULL, TRUE, FALSE, NULL);
4511
SetConsoleCtrlHandler(my_event_handler, TRUE);
4512
4513
if (parameters.opt_c)
4514
{
4515
RETURN_CODE return_code = WCMD_call_batch(NULL, parameters.initial_command);
4516
if (return_code == RETURN_CODE_GOTO) return NO_ERROR;
4517
if (return_code != RETURN_CODE_ABORTED && return_code != RETURN_CODE_EXITED && return_code != NO_ERROR)
4518
return return_code;
4519
return errorlevel;
4520
}
4521
4522
GetStartupInfoW(&startupInfo);
4523
SetConsoleTitleW(startupInfo.lpTitle ? startupInfo.lpTitle : WCMD_LoadMessage(WCMD_CONSTITLE));
4524
4525
/* Note: cmd.exe /c dir does not get a new color, /k dir does */
4526
set_console_default_color(parameters.default_color);
4527
4528
if (parameters.opt_k)
4529
{
4530
RETURN_CODE return_code = WCMD_call_batch(NULL, parameters.initial_command);
4531
if (return_code == RETURN_CODE_ABORTED)
4532
return errorlevel;
4533
}
4534
else
4535
WCMD_output_asis(version_string);
4536
4537
/* Loop forever getting commands and executing them. */
4538
if (echo_mode) WCMD_output_asis(L"\r\n");
4539
/* Read until EOF (which for std input is never, but if redirect in place, may occur */
4540
while ((rpl_status = WCMD_ReadAndParseLine(&toExecute)) != RPL_EOF)
4541
{
4542
if (rpl_status == RPL_SUCCESS && toExecute)
4543
{
4544
ResetEvent(control_c_event);
4545
node_execute(toExecute);
4546
node_dispose_tree(toExecute);
4547
if (echo_mode) WCMD_output_asis(L"\r\n");
4548
}
4549
}
4550
4551
return 0;
4552
}
4553
4554