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