Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/programs/cmd/batch.c
4389 views
1
/*
2
* CMD - Wine-compatible command line interface - batch interface.
3
*
4
* Copyright (C) 1999 D A Pickles
5
* Copyright (C) 2007 J Edmeades
6
*
7
* This library is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public
9
* License as published by the Free Software Foundation; either
10
* version 2.1 of the License, or (at your option) any later version.
11
*
12
* This library is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
16
*
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with this library; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20
*/
21
22
#include "wcmd.h"
23
#include "wine/debug.h"
24
25
WINE_DEFAULT_DEBUG_CHANNEL(cmd);
26
27
static RETURN_CODE WCMD_batch_main_loop(void)
28
{
29
RETURN_CODE return_code = NO_ERROR;
30
enum read_parse_line rpl;
31
CMD_NODE *node;
32
33
/* Work through the file line by line until an exit is called. */
34
while ((rpl = WCMD_ReadAndParseLine(&node)) != RPL_EOF)
35
{
36
switch (rpl)
37
{
38
case RPL_EOF: break; /* never reached; get rid of warning */
39
case RPL_SUCCESS:
40
if (node)
41
{
42
return_code = node_execute(node);
43
node_dispose_tree(node);
44
}
45
break;
46
case RPL_SYNTAXERROR:
47
return_code = RETURN_CODE_SYNTAX_ERROR;
48
break;
49
}
50
}
51
52
/* If there are outstanding setlocal's to the current context, unwind them. */
53
if (WCMD_is_in_context(NULL))
54
while (WCMD_endlocal() == NO_ERROR) {}
55
56
return return_code;
57
}
58
59
static struct batch_file *find_or_alloc_batch_file(const WCHAR *file)
60
{
61
struct batch_file *batchfile;
62
struct batch_context *ctx;
63
HANDLE h;
64
unsigned int i;
65
66
if (!file) return NULL;
67
for (ctx = context; ctx; ctx = ctx->prev_context)
68
{
69
if (ctx->batch_file && !wcscmp(ctx->batch_file->path_name, file))
70
return ctx->batch_file;
71
}
72
batchfile = xalloc(sizeof(*batchfile));
73
batchfile->ref_count = 0;
74
batchfile->path_name = xstrdupW(file);
75
76
h = CreateFileW(file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
77
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
78
if (h == INVALID_HANDLE_VALUE || !GetFileTime(h, NULL, NULL, &batchfile->last_modified))
79
memset(&batchfile->last_modified, 0, sizeof(batchfile->last_modified));
80
CloseHandle(h);
81
82
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
83
{
84
batchfile->cache[i].label = NULL;
85
batchfile->cache[i].age = 0;
86
}
87
return batchfile;
88
}
89
90
static struct batch_context *push_batch_context(WCHAR *command, struct batch_file *batch_file, ULONGLONG pos)
91
{
92
struct batch_context *prev = context;
93
94
context = xalloc(sizeof(struct batch_context));
95
context->file_position.QuadPart = pos;
96
context->command = command;
97
memset(context->shift_count, 0x00, sizeof(context->shift_count));
98
context->prev_context = prev;
99
context->batch_file = batch_file;
100
if (batch_file) batch_file->ref_count++;
101
102
return context;
103
}
104
105
static struct batch_context *pop_batch_context(struct batch_context *ctx)
106
{
107
struct batch_context *prev = ctx->prev_context;
108
struct batch_file *batchfile = ctx->batch_file;
109
if (batchfile && --batchfile->ref_count == 0)
110
{
111
unsigned int i;
112
113
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
114
free((void *)batchfile->cache[i].label);
115
free(batchfile->path_name);
116
free(batchfile);
117
ctx->batch_file = NULL;
118
}
119
free(ctx);
120
return prev;
121
}
122
123
/****************************************************************************
124
* WCMD_call_batch
125
*
126
* Open and execute a batch file.
127
* On entry *command includes the complete command line beginning with the name
128
* of the batch file (if a CALL command was entered the CALL has been removed).
129
* *file is the name of the file, which might not exist and may not have the
130
* .BAT suffix on.
131
*
132
* We need to handle recursion correctly, since one batch program might call another.
133
* So parameters for this batch file are held in a BATCH_CONTEXT structure.
134
*/
135
RETURN_CODE WCMD_call_batch(const WCHAR *file, WCHAR *command)
136
{
137
RETURN_CODE return_code;
138
139
context = push_batch_context(command, find_or_alloc_batch_file(file), 0);
140
return_code = WCMD_batch_main_loop();
141
context = pop_batch_context(context);
142
143
return return_code;
144
}
145
146
/*******************************************************************
147
* WCMD_parameter_with_delims
148
*
149
* Extracts a delimited parameter from an input string, providing
150
* the delimiters characters to use
151
*
152
* PARAMS
153
* s [I] input string, non NULL
154
* n [I] # of the parameter to return, counted from 0
155
* start [O] Optional. Pointer to the first char of param n in s
156
* raw [I] TRUE to return the parameter in raw format (quotes maintained)
157
* FALSE to return the parameter with quotes stripped (including internal ones)
158
* wholecmdline [I] TRUE to indicate this routine is being used to parse the
159
* command line, and special logic for arg0->1 transition
160
* needs to be applied.
161
* delims[I] The delimiter characters to use
162
*
163
* RETURNS
164
* Success: The nth delimited parameter found in s
165
* if start != NULL, *start points to the start of the param (quotes maintained)
166
* Failure: An empty string if the param is not found.
167
* *start == NULL
168
*
169
* NOTES
170
* Return value is stored in static storage (i.e. overwritten after each call).
171
* By default, the parameter is returned with quotes removed, ready for use with
172
* other API calls, e.g. c:\"a b"\c is returned as c:\a b\c. However, some commands
173
* need to preserve the exact syntax (echo, for, etc) hence the raw option.
174
*/
175
WCHAR *WCMD_parameter_with_delims (WCHAR *s, int n, WCHAR **start,
176
BOOL raw, BOOL wholecmdline, const WCHAR *delims)
177
{
178
int curParamNb = 0;
179
static WCHAR param[MAXSTRING];
180
WCHAR *p = s, *begin;
181
182
if (start != NULL) *start = NULL;
183
param[0] = '\0';
184
185
while (TRUE) {
186
187
/* Absorb repeated word delimiters until we get to the next token (or the end!) */
188
while (*p && (wcschr(delims, *p) != NULL))
189
p++;
190
if (*p == '\0') return param;
191
192
/* If we have reached the token number we want, remember the beginning of it */
193
if (start != NULL && curParamNb == n) *start = p;
194
195
/* Return the whole word up to the next delimiter, handling quotes in the middle
196
of it, e.g. a"\b c\"d is a single parameter. */
197
begin = p;
198
199
/* Loop character by character, but just need to special case quotes */
200
while (*p) {
201
/* Once we have found a delimiter, break */
202
if (wcschr(delims, *p) != NULL) break;
203
204
/* Very odd special case - Seems as if a ( acts as a delimiter which is
205
not swallowed but is effective only when it comes between the program
206
name and the parameters. Need to avoid this triggering when used
207
to walk parameters generally. */
208
if (wholecmdline && curParamNb == 0 && *p=='(') break;
209
210
/* If we find a quote, copy until we get the end quote */
211
if (*p == '"') {
212
p++;
213
while (*p && *p != '"') p++;
214
}
215
216
/* Now skip the character / quote */
217
if (*p) p++;
218
}
219
220
if (curParamNb == n) {
221
/* Return the parameter in static storage either as-is (raw) or
222
suitable for use with other win32 api calls (quotes stripped) */
223
if (raw) {
224
memcpy(param, begin, (p - begin) * sizeof(WCHAR));
225
param[p-begin] = '\0';
226
} else {
227
int i=0;
228
while (begin < p) {
229
if (*begin != '"') param[i++] = *begin;
230
begin++;
231
}
232
param[i] = '\0';
233
}
234
return param;
235
}
236
curParamNb++;
237
}
238
}
239
240
/*******************************************************************
241
* WCMD_parameter
242
*
243
* Extracts a delimited parameter from an input string, using a
244
* default set of delimiter characters. For parameters, see the main
245
* function above.
246
*/
247
WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, BOOL raw,
248
BOOL wholecmdline)
249
{
250
return WCMD_parameter_with_delims (s, n, start, raw, wholecmdline, L" \t,=;");
251
}
252
253
/****************************************************************************
254
* WCMD_fgets_helper
255
*
256
* Gets one line from a file/console and puts it into buffer buf
257
* Pre: buf has size noChars
258
* 1 <= noChars <= MAXSTRING
259
* Post: buf is filled with at most noChars-1 characters, and gets nul-terminated
260
buf does not include EOL terminator
261
* Returns:
262
* buf on success
263
* NULL on error or EOF
264
*/
265
266
static WCHAR *WCMD_fgets_helper(WCHAR *buf, DWORD noChars, HANDLE h, UINT code_page)
267
{
268
DWORD charsRead;
269
BOOL status;
270
DWORD i;
271
272
/* We can't use the native f* functions because of the filename syntax differences
273
between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */
274
275
if (WCMD_read_console(h, buf, noChars, &charsRead) && charsRead) {
276
/* Find first EOL */
277
for (i = 0; i < charsRead; i++) {
278
if (buf[i] == '\n' || buf[i] == '\r')
279
break;
280
}
281
}
282
else {
283
LARGE_INTEGER filepos;
284
char *bufA;
285
const char *p;
286
287
bufA = xalloc(noChars);
288
289
/* Save current file position */
290
filepos.QuadPart = 0;
291
SetFilePointerEx(h, filepos, &filepos, FILE_CURRENT);
292
293
status = ReadFile(h, bufA, noChars, &charsRead, NULL);
294
if (!status || charsRead == 0) {
295
free(bufA);
296
return NULL;
297
}
298
299
/* Find first EOL */
300
for (p = bufA; p < (bufA + charsRead); p = CharNextExA(code_page, p, 0)) {
301
if (*p == '\n' || *p == '\r')
302
break;
303
}
304
305
/* Sets file pointer to the start of the next line, if any */
306
filepos.QuadPart += p - bufA + 1 + (*p == '\r' ? 1 : 0);
307
SetFilePointerEx(h, filepos, NULL, FILE_BEGIN);
308
309
i = MultiByteToWideChar(code_page, 0, bufA, p - bufA, buf, noChars);
310
free(bufA);
311
}
312
313
/* Truncate at EOL (or end of buffer) */
314
if (i == noChars)
315
i--;
316
317
buf[i] = '\0';
318
319
return buf;
320
}
321
322
static UINT get_current_code_page(void)
323
{
324
UINT code_page = GetConsoleOutputCP();
325
return code_page ? code_page : GetOEMCP();
326
}
327
328
WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h)
329
{
330
return WCMD_fgets_helper(buf, noChars, h, get_current_code_page());
331
}
332
333
/****************************************************************************
334
* WCMD_HandleTildeModifiers
335
*
336
* Handle the ~ modifiers when expanding %0-9 or (%a-z/A-Z in for command)
337
* %~xxxxxV (V=0-9 or A-Z, a-z)
338
* Where xxxx is any combination of:
339
* ~ - Removes quotes
340
* f - Fully qualified path (assumes current dir if not drive\dir)
341
* d - drive letter
342
* p - path
343
* n - filename
344
* x - file extension
345
* s - path with shortnames
346
* a - attributes
347
* t - date/time
348
* z - size
349
* $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
350
* qualified path
351
*
352
* To work out the length of the modifier:
353
*
354
* Note: In the case of %0-9 knowing the end of the modifier is easy,
355
* but in a for loop, the for end WCHARacter may also be a modifier
356
* eg. for %a in (c:\a.a) do echo XXX
357
* where XXX = %~a (just ~)
358
* %~aa (~ and attributes)
359
* %~aaxa (~, attributes and extension)
360
* BUT %~aax (~ and attributes followed by 'x')
361
*
362
* Hence search forwards until find an invalid modifier, and then
363
* backwards until find for variable or 0-9
364
*/
365
void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute)
366
{
367
static const WCHAR *validmodifiers = L"~fdpnxsatz$";
368
369
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
370
WCHAR outputparam[MAXSTRING];
371
WCHAR finaloutput[MAXSTRING];
372
WCHAR fullfilename[MAX_PATH];
373
WCHAR thisoutput[MAX_PATH];
374
WCHAR *filepart = NULL;
375
WCHAR *pos = *start+1;
376
WCHAR *firstModifier = pos;
377
WCHAR *lastModifier = pos++;
378
int modifierLen = 0;
379
BOOL exists = TRUE;
380
BOOL skipFileParsing = FALSE;
381
BOOL doneModifier = FALSE;
382
383
/* Search forwards until find invalid character modifier */
384
for (; *lastModifier && wcschr(validmodifiers, towlower(*lastModifier)); lastModifier = pos++) {
385
/* Special case '$' to skip until : found */
386
if (*lastModifier == L'$') {
387
if (!(pos = wcschr(pos, L':'))) return; /* Invalid syntax */
388
pos++;
389
}
390
}
391
392
while (lastModifier > firstModifier) {
393
WINE_TRACE("Looking backwards for parameter id: %s\n",
394
wine_dbgstr_w(lastModifier));
395
396
if (!atExecute && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
397
/* Its a valid parameter identifier - OK */
398
break;
399
400
} else {
401
/* Its a valid parameter identifier - OK */
402
if (for_var_is_valid(*lastModifier) && forloopcontext->variable[*lastModifier] != NULL) break;
403
404
/* Its not a valid parameter identifier - step backwards */
405
lastModifier--;
406
}
407
}
408
if (lastModifier == firstModifier) return; /* Invalid syntax */
409
/* put all modifiers in lowercase */
410
for (pos = firstModifier; pos < lastModifier && *pos != L'$'; pos++)
411
*pos = towlower(*pos);
412
413
/* So now, firstModifier points to beginning of modifiers, lastModifier
414
points to the variable just after the modifiers. Process modifiers
415
in a specific order, remembering there could be duplicates */
416
modifierLen = lastModifier - firstModifier;
417
finaloutput[0] = 0x00;
418
419
/* Extract the parameter to play with
420
Special case param 0 - With %~0 you get the batch label which was called
421
whereas if you start applying other modifiers to it, you get the filename
422
the batch label is in */
423
if (*lastModifier == '0' && modifierLen > 1 && context->batch_file) {
424
lstrcpyW(outputparam, context->batch_file->path_name);
425
} else if ((*lastModifier >= '0' && *lastModifier <= '9')) {
426
lstrcpyW(outputparam,
427
WCMD_parameter (context -> command,
428
*lastModifier-'0' + context -> shift_count[*lastModifier-'0'],
429
NULL, FALSE, TRUE));
430
} else {
431
if (for_var_is_valid(*lastModifier))
432
lstrcpyW(outputparam, forloopcontext->variable[*lastModifier]);
433
}
434
435
/* 1. Handle '~' : Strip surrounding quotes */
436
if (outputparam[0]=='"' &&
437
wmemchr(firstModifier, '~', modifierLen) != NULL) {
438
int len = lstrlenW(outputparam);
439
if (outputparam[len-1] == '"') {
440
outputparam[len-1]=0x00;
441
len = len - 1;
442
}
443
memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
444
}
445
446
/* 2. Handle the special case of a $ */
447
if (wmemchr(firstModifier, '$', modifierLen) != NULL) {
448
/* Special Case: Search envar specified in $[envvar] for outputparam
449
Note both $ and : are guaranteed otherwise check above would fail */
450
WCHAR *begin = wcschr(firstModifier, '$') + 1;
451
WCHAR *end = wcschr(firstModifier, ':');
452
WCHAR env[MAX_PATH];
453
DWORD size;
454
455
/* Extract the env var */
456
memcpy(env, begin, (end-begin) * sizeof(WCHAR));
457
env[(end-begin)] = 0x00;
458
459
size = GetEnvironmentVariableW(env, NULL, 0);
460
if (size > 0) {
461
WCHAR *fullpath = malloc(size * sizeof(WCHAR));
462
if (!fullpath || (GetEnvironmentVariableW(env, fullpath, size) == 0) ||
463
(SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0))
464
size = 0;
465
free(fullpath);
466
}
467
468
if (!size) {
469
/* If env var not found, return empty string */
470
finaloutput[0] = 0x00;
471
outputparam[0] = 0x00;
472
skipFileParsing = TRUE;
473
}
474
}
475
476
/* After this, we need full information on the file,
477
which is valid not to exist. */
478
if (!skipFileParsing) {
479
if (!WCMD_get_fullpath(outputparam, MAX_PATH, fullfilename, &filepart)) {
480
exists = FALSE;
481
fullfilename[0] = 0x00;
482
} else {
483
exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
484
&fileInfo);
485
}
486
487
/* 2. Handle 'a' : Output attributes (File doesn't have to exist) */
488
if (wmemchr(firstModifier, 'a', modifierLen) != NULL) {
489
490
doneModifier = TRUE;
491
492
if (exists) {
493
lstrcpyW(thisoutput, L"---------");
494
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
495
thisoutput[0]='d';
496
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
497
thisoutput[1]='r';
498
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
499
thisoutput[2]='a';
500
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
501
thisoutput[3]='h';
502
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
503
thisoutput[4]='s';
504
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
505
thisoutput[5]='c';
506
/* FIXME: What are 6 and 7? */
507
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
508
thisoutput[8]='l';
509
lstrcatW(finaloutput, thisoutput);
510
}
511
}
512
513
/* 3. Handle 't' : Date+time (File doesn't have to exist) */
514
if (wmemchr(firstModifier, 't', modifierLen) != NULL) {
515
516
SYSTEMTIME systime;
517
int datelen;
518
519
doneModifier = TRUE;
520
521
if (exists) {
522
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
523
524
/* Format the time */
525
FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
526
GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
527
NULL, thisoutput, MAX_PATH);
528
lstrcatW(thisoutput, L" ");
529
datelen = lstrlenW(thisoutput);
530
GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
531
NULL, (thisoutput+datelen), MAX_PATH-datelen);
532
lstrcatW(finaloutput, thisoutput);
533
}
534
}
535
536
/* 4. Handle 'z' : File length (File doesn't have to exist) */
537
if (wmemchr(firstModifier, 'z', modifierLen) != NULL) {
538
/* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
539
ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
540
fileInfo.nFileSizeLow;
541
542
doneModifier = TRUE;
543
if (exists) {
544
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
545
wsprintfW(thisoutput, L"%u", fullsize);
546
lstrcatW(finaloutput, thisoutput);
547
}
548
}
549
550
/* 4. Handle 's' : Use short paths (File doesn't have to exist) */
551
if (wmemchr(firstModifier, 's', modifierLen) != NULL) {
552
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
553
554
/* Convert fullfilename's path to a short path - Save filename away as
555
only path is valid, name may not exist which causes GetShortPathName
556
to fail if it is provided */
557
if (filepart) {
558
lstrcpyW(thisoutput, filepart);
559
*filepart = 0x00;
560
GetShortPathNameW(fullfilename, fullfilename, ARRAY_SIZE(fullfilename));
561
lstrcatW(fullfilename, thisoutput);
562
}
563
}
564
565
/* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
566
/* Note this overrides d,p,n,x */
567
if (wmemchr(firstModifier, 'f', modifierLen) != NULL) {
568
doneModifier = TRUE;
569
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
570
lstrcatW(finaloutput, fullfilename);
571
} else {
572
573
WCHAR drive[10];
574
WCHAR dir[MAX_PATH];
575
WCHAR fname[MAX_PATH];
576
WCHAR ext[MAX_PATH];
577
BOOL doneFileModifier = FALSE;
578
BOOL addSpace = (finaloutput[0] != 0x00);
579
580
/* Split into components */
581
_wsplitpath(fullfilename, drive, dir, fname, ext);
582
583
/* 5. Handle 'd' : Drive Letter */
584
if (wmemchr(firstModifier, 'd', modifierLen) != NULL) {
585
if (addSpace) {
586
lstrcatW(finaloutput, L" ");
587
addSpace = FALSE;
588
}
589
590
lstrcatW(finaloutput, drive);
591
doneModifier = TRUE;
592
doneFileModifier = TRUE;
593
}
594
595
/* 6. Handle 'p' : Path */
596
if (wmemchr(firstModifier, 'p', modifierLen) != NULL) {
597
if (addSpace) {
598
lstrcatW(finaloutput, L" ");
599
addSpace = FALSE;
600
}
601
602
lstrcatW(finaloutput, dir);
603
doneModifier = TRUE;
604
doneFileModifier = TRUE;
605
}
606
607
/* 7. Handle 'n' : Name */
608
if (wmemchr(firstModifier, 'n', modifierLen) != NULL) {
609
if (addSpace) {
610
lstrcatW(finaloutput, L" ");
611
addSpace = FALSE;
612
}
613
614
lstrcatW(finaloutput, fname);
615
doneModifier = TRUE;
616
doneFileModifier = TRUE;
617
}
618
619
/* 8. Handle 'x' : Ext */
620
if (wmemchr(firstModifier, 'x', modifierLen) != NULL) {
621
if (addSpace) {
622
lstrcatW(finaloutput, L" ");
623
addSpace = FALSE;
624
}
625
626
lstrcatW(finaloutput, ext);
627
doneModifier = TRUE;
628
doneFileModifier = TRUE;
629
}
630
631
/* If 's' but no other parameter, dump the whole thing */
632
if (!doneFileModifier &&
633
wmemchr(firstModifier, 's', modifierLen) != NULL) {
634
doneModifier = TRUE;
635
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
636
lstrcatW(finaloutput, fullfilename);
637
}
638
}
639
}
640
641
/* If No other modifier processed, just add in parameter */
642
if (!doneModifier) lstrcpyW(finaloutput, outputparam);
643
644
/* Finish by inserting the replacement into the string */
645
WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
646
}
647
648
extern void WCMD_expand(const WCHAR *, WCHAR *);
649
650
/*******************************************************************
651
* WCMD_call - processes a batch call statement
652
*
653
* If there is a leading ':', calls within this batch program
654
* otherwise launches another program.
655
*/
656
RETURN_CODE WCMD_call(WCHAR *command)
657
{
658
RETURN_CODE return_code;
659
WCHAR buffer[MAXSTRING];
660
WCMD_expand(command, buffer);
661
662
/* Run other program if no leading ':' */
663
if (*command != ':')
664
{
665
if (*WCMD_skip_leading_spaces(buffer) == L'\0')
666
/* FIXME it's incomplete as (call) should return 1, and (call ) should return 0...
667
* but we need to get the untouched string in command
668
*/
669
return_code = errorlevel = NO_ERROR;
670
else
671
{
672
WCMD_call_command(buffer);
673
/* If the thing we try to run does not exist, call returns 1 */
674
if (errorlevel == RETURN_CODE_CANT_LAUNCH)
675
errorlevel = ERROR_INVALID_FUNCTION;
676
return_code = errorlevel;
677
}
678
}
679
else if (context)
680
{
681
WCHAR gotoLabel[MAX_PATH];
682
683
lstrcpyW(gotoLabel, param1);
684
685
/* Save the for variable context, then start with an empty context
686
as for loop variables do not survive a call */
687
WCMD_save_for_loop_context(TRUE);
688
689
context = push_batch_context(buffer, context->batch_file, context->file_position.QuadPart);
690
691
/* FIXME as commands here can temper with param1 global variable (ugly) */
692
lstrcpyW(param1, gotoLabel);
693
WCMD_goto();
694
695
WCMD_batch_main_loop();
696
697
context = pop_batch_context(context);
698
return_code = errorlevel;
699
700
/* Restore the for loop context */
701
WCMD_restore_for_loop_context();
702
} else {
703
WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT));
704
return_code = ERROR_INVALID_FUNCTION;
705
}
706
return return_code;
707
}
708
709
void WCMD_set_label_end(WCHAR *string)
710
{
711
static const WCHAR labelEndsW[] = L"><|& :\t";
712
WCHAR *p;
713
714
/* Label ends at whitespace or redirection characters */
715
if ((p = wcspbrk(string, labelEndsW))) *p = L'\0';
716
}
717
718
static BOOL find_next_label(HANDLE h, ULONGLONG end, WCHAR candidate[MAXSTRING], UINT code_page)
719
{
720
while (WCMD_fgets_helper(candidate, MAXSTRING, h, code_page))
721
{
722
WCHAR *str = candidate;
723
724
/* Ignore leading whitespace or no-echo character */
725
while (*str == L'@' || iswspace(*str)) str++;
726
727
/* If the first real character is a : then this is a label */
728
if (*str == L':')
729
{
730
/* Skip spaces between : and label */
731
for (str++; iswspace(*str); str++) {}
732
memmove(candidate, str, (wcslen(str) + 1) * sizeof(WCHAR));
733
WCMD_set_label_end(candidate);
734
735
return TRUE;
736
}
737
if (end)
738
{
739
LARGE_INTEGER li = {.QuadPart = 0}, curli;
740
if (!SetFilePointerEx(h, li, &curli, FILE_CURRENT)) return FALSE;
741
if (curli.QuadPart > end) break;
742
}
743
}
744
return FALSE;
745
}
746
747
static LARGE_INTEGER li_not_found = {.QuadPart = 0x7fffffffffffffffll};
748
749
static void insert_label_cache_entry(const WCHAR *label, LARGE_INTEGER from, LARGE_INTEGER at)
750
{
751
struct batch_file *batchfile = context->batch_file;
752
unsigned int i, worst_index = ~0u, worst_age = 0;
753
754
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
755
if (batchfile->cache[i].label)
756
batchfile->cache[i].age++;
757
else
758
worst_index = i;
759
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
760
{
761
if (batchfile->cache[i].label && !lstrcmpiW(batchfile->cache[i].label, label) &&
762
batchfile->cache[i].position.QuadPart == at.QuadPart)
763
{
764
batchfile->cache[i].age = 0;
765
/* decrease 'from' position if we have a larger match */
766
if (batchfile->cache[i].from.QuadPart > from.QuadPart)
767
batchfile->cache[i].from.QuadPart = from.QuadPart;
768
return;
769
}
770
}
771
if (worst_index == ~0u) /* all cache lines are used, find lru */
772
{
773
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
774
{
775
if (batchfile->cache[i].age > worst_age)
776
{
777
worst_index = i;
778
worst_age = batchfile->cache[i].age;
779
}
780
}
781
}
782
free((void*)batchfile->cache[worst_index].label);
783
batchfile->cache[worst_index].label = xstrdupW(label);
784
batchfile->cache[worst_index].from = from;
785
batchfile->cache[worst_index].position = at;
786
batchfile->cache[worst_index].age = 0;
787
}
788
789
static BOOL find_label_cache_entry(const WCHAR *label, LARGE_INTEGER from, LARGE_INTEGER *at)
790
{
791
struct batch_file *batchfile = context->batch_file;
792
unsigned int i;
793
794
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
795
batchfile->cache[i].age++;
796
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
797
{
798
if (batchfile->cache[i].label && !lstrcmpiW(batchfile->cache[i].label, label) &&
799
batchfile->cache[i].from.QuadPart <= from.QuadPart &&
800
from.QuadPart <= batchfile->cache[i].position.QuadPart)
801
{
802
*at = batchfile->cache[i].position;
803
batchfile->cache[i].age = 0;
804
return TRUE;
805
}
806
}
807
return FALSE;
808
}
809
810
static void check_if_valid_label_cache(HANDLE h)
811
{
812
struct batch_file *batchfile = context->batch_file;
813
FILETIME last;
814
unsigned int i;
815
816
if (!GetFileTime(h, NULL, NULL, &last) ||
817
batchfile->last_modified.dwHighDateTime != last.dwHighDateTime ||
818
batchfile->last_modified.dwLowDateTime != last.dwLowDateTime)
819
{
820
TRACE("Invalidating cache\n");
821
batchfile->last_modified = last;
822
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
823
{
824
free((void *)batchfile->cache[i].label);
825
batchfile->cache[i].label = NULL;
826
}
827
}
828
}
829
830
BOOL WCMD_find_label(HANDLE h, const WCHAR *label, LARGE_INTEGER *pos)
831
{
832
LARGE_INTEGER where = *pos, zeroli = {.QuadPart = 0};
833
WCHAR candidate[MAXSTRING];
834
UINT code_page = get_current_code_page();
835
836
if (!*label) return FALSE;
837
838
check_if_valid_label_cache(h);
839
840
if (!SetFilePointerEx(h, *pos, NULL, FILE_BEGIN)) return FALSE;
841
if (find_label_cache_entry(label, *pos, pos))
842
{
843
if (pos->QuadPart != li_not_found.QuadPart) return TRUE;
844
}
845
else
846
{
847
while (find_next_label(h, ~(ULONGLONG)0, candidate, code_page))
848
{
849
TRACE("comparing found label %s\n", wine_dbgstr_w(candidate));
850
if (!lstrcmpiW(candidate, label))
851
{
852
BOOL ret = SetFilePointerEx(h, zeroli, pos, FILE_CURRENT);
853
if (ret)
854
insert_label_cache_entry(label, where, *pos);
855
return ret;
856
}
857
}
858
insert_label_cache_entry(label, where, li_not_found);
859
}
860
TRACE("Label not found, trying from beginning of file\n");
861
if (!SetFilePointerEx(h, zeroli, NULL, FILE_BEGIN)) return FALSE;
862
if (find_label_cache_entry(label, zeroli, pos))
863
{
864
if (pos->QuadPart != li_not_found.QuadPart) return TRUE;
865
}
866
else
867
{
868
while (find_next_label(h, where.QuadPart, candidate, code_page))
869
{
870
TRACE("comparing found label %s\n", wine_dbgstr_w(candidate));
871
if (!lstrcmpiW(candidate, label))
872
{
873
BOOL ret = SetFilePointerEx(h, zeroli, pos, FILE_CURRENT);
874
if (ret)
875
insert_label_cache_entry(label, zeroli, *pos);
876
return ret;
877
}
878
}
879
insert_label_cache_entry(label, where, li_not_found);
880
}
881
TRACE("Reached wrap point, label not found\n");
882
return FALSE;
883
}
884
885