Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/programs/cmd/batch.c
8545 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
270
if (!WCMD_read_console(h, buf, noChars, &charsRead))
271
{
272
LARGE_INTEGER filepos;
273
char *bufA, *p;
274
275
bufA = xalloc(noChars);
276
277
/* Save current file position */
278
filepos.QuadPart = 0;
279
if (GetFileType(h) == FILE_TYPE_DISK && SetFilePointerEx(h, filepos, &filepos, FILE_CURRENT))
280
{
281
if (!ReadFile(h, bufA, noChars, &charsRead, NULL) || charsRead == 0)
282
{
283
free(bufA);
284
return NULL;
285
}
286
287
/* Find first EOL */
288
for (p = bufA; p < bufA + charsRead; p = CharNextExA(code_page, p, 0))
289
{
290
if (p[0] == L'\n') break;
291
}
292
if (p < bufA + charsRead)
293
{
294
/* Sets file pointer to the start of the next line, if any */
295
filepos.QuadPart += p - bufA + 1;
296
SetFilePointerEx(h, filepos, NULL, FILE_BEGIN);
297
}
298
}
299
else
300
{
301
for (p = bufA; p < bufA + noChars; p++)
302
{
303
if (!ReadFile(h, p, 1, &charsRead, NULL) || (!charsRead && p == bufA))
304
{
305
free(bufA);
306
return NULL;
307
}
308
/* FIXME: not multibyte charset compliant */
309
if (!charsRead || p[0] == '\n') break;
310
}
311
}
312
313
charsRead = MultiByteToWideChar(code_page, 0, bufA, p - bufA, buf, noChars - 1);
314
free(bufA);
315
}
316
317
while (charsRead && (buf[charsRead - 1] == L'\n' || buf[charsRead - 1] == L'\r')) charsRead--;
318
buf[charsRead] = L'\0';
319
320
return buf;
321
}
322
323
static UINT get_current_code_page(void)
324
{
325
UINT code_page = GetConsoleOutputCP();
326
return code_page ? code_page : GetOEMCP();
327
}
328
329
WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h)
330
{
331
return WCMD_fgets_helper(buf, noChars, h, get_current_code_page());
332
}
333
334
/****************************************************************************
335
* WCMD_HandleTildeModifiers
336
*
337
* Handle the ~ modifiers when expanding %0-9 or (%a-z/A-Z in for command)
338
* %~xxxxxV (V=0-9 or A-Z, a-z)
339
* Where xxxx is any combination of:
340
* ~ - Removes quotes
341
* f - Fully qualified path (assumes current dir if not drive\dir)
342
* d - drive letter
343
* p - path
344
* n - filename
345
* x - file extension
346
* s - path with shortnames
347
* a - attributes
348
* t - date/time
349
* z - size
350
* $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
351
* qualified path
352
*
353
* To work out the length of the modifier:
354
*
355
* Note: In the case of %0-9 knowing the end of the modifier is easy,
356
* but in a for loop, the for end WCHARacter may also be a modifier
357
* eg. for %a in (c:\a.a) do echo XXX
358
* where XXX = %~a (just ~)
359
* %~aa (~ and attributes)
360
* %~aaxa (~, attributes and extension)
361
* BUT %~aax (~ and attributes followed by 'x')
362
*
363
* Hence search forwards until find an invalid modifier, and then
364
* backwards until find for variable or 0-9
365
*/
366
void WCMD_HandleTildeModifiers(WCHAR **start, BOOL atExecute)
367
{
368
static const WCHAR *validmodifiers = L"~fdpnxsatz$";
369
370
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
371
WCHAR outputparam[MAXSTRING];
372
WCHAR finaloutput[MAXSTRING];
373
WCHAR fullfilename[MAX_PATH];
374
WCHAR thisoutput[MAX_PATH];
375
WCHAR *filepart = NULL;
376
WCHAR *pos = *start+1;
377
WCHAR *firstModifier = pos;
378
WCHAR *lastModifier = pos++;
379
int modifierLen = 0;
380
BOOL exists = TRUE;
381
BOOL skipFileParsing = FALSE;
382
BOOL doneModifier = FALSE;
383
384
/* Search forwards until find invalid character modifier */
385
for (; *lastModifier && wcschr(validmodifiers, towlower(*lastModifier)); lastModifier = pos++) {
386
/* Special case '$' to skip until : found */
387
if (*lastModifier == L'$') {
388
if (!(pos = wcschr(pos, L':'))) return; /* Invalid syntax */
389
pos++;
390
}
391
}
392
393
while (lastModifier > firstModifier) {
394
WINE_TRACE("Looking backwards for parameter id: %s\n",
395
wine_dbgstr_w(lastModifier));
396
397
if (!atExecute && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
398
/* Its a valid parameter identifier - OK */
399
break;
400
401
} else {
402
/* Its a valid parameter identifier - OK */
403
if (for_var_is_valid(*lastModifier) && forloopcontext->variable[*lastModifier] != NULL) break;
404
405
/* Its not a valid parameter identifier - step backwards */
406
lastModifier--;
407
}
408
}
409
if (lastModifier == firstModifier) return; /* Invalid syntax */
410
/* put all modifiers in lowercase */
411
for (pos = firstModifier; pos < lastModifier && *pos != L'$'; pos++)
412
*pos = towlower(*pos);
413
414
/* So now, firstModifier points to beginning of modifiers, lastModifier
415
points to the variable just after the modifiers. Process modifiers
416
in a specific order, remembering there could be duplicates */
417
modifierLen = lastModifier - firstModifier;
418
finaloutput[0] = 0x00;
419
420
/* Extract the parameter to play with
421
Special case param 0 - With %~0 you get the batch label which was called
422
whereas if you start applying other modifiers to it, you get the filename
423
the batch label is in */
424
if (*lastModifier == '0' && modifierLen > 1 && context->batch_file) {
425
lstrcpyW(outputparam, context->batch_file->path_name);
426
} else if ((*lastModifier >= '0' && *lastModifier <= '9')) {
427
lstrcpyW(outputparam,
428
WCMD_parameter (context -> command,
429
*lastModifier-'0' + context -> shift_count[*lastModifier-'0'],
430
NULL, FALSE, TRUE));
431
} else {
432
if (for_var_is_valid(*lastModifier))
433
lstrcpyW(outputparam, forloopcontext->variable[*lastModifier]);
434
}
435
436
/* 1. Handle '~' : Strip surrounding quotes */
437
if (outputparam[0]=='"' &&
438
wmemchr(firstModifier, '~', modifierLen) != NULL) {
439
int len = lstrlenW(outputparam);
440
if (outputparam[len-1] == '"') {
441
outputparam[len-1]=0x00;
442
len = len - 1;
443
}
444
memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
445
}
446
447
/* 2. Handle the special case of a $ */
448
if (wmemchr(firstModifier, '$', modifierLen) != NULL) {
449
/* Special Case: Search envar specified in $[envvar] for outputparam
450
Note both $ and : are guaranteed otherwise check above would fail */
451
WCHAR *begin = wcschr(firstModifier, '$') + 1;
452
WCHAR *end = wcschr(firstModifier, ':');
453
WCHAR env[MAX_PATH];
454
DWORD size;
455
456
/* Extract the env var */
457
memcpy(env, begin, (end-begin) * sizeof(WCHAR));
458
env[(end-begin)] = 0x00;
459
460
size = GetEnvironmentVariableW(env, NULL, 0);
461
if (size > 0) {
462
WCHAR *fullpath = malloc(size * sizeof(WCHAR));
463
if (!fullpath || (GetEnvironmentVariableW(env, fullpath, size) == 0) ||
464
(SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0))
465
size = 0;
466
free(fullpath);
467
}
468
469
if (!size) {
470
/* If env var not found, return empty string */
471
finaloutput[0] = 0x00;
472
outputparam[0] = 0x00;
473
skipFileParsing = TRUE;
474
}
475
}
476
477
/* After this, we need full information on the file,
478
which is valid not to exist. */
479
if (!skipFileParsing) {
480
if (!WCMD_get_fullpath(outputparam, MAX_PATH, fullfilename, &filepart)) {
481
exists = FALSE;
482
fullfilename[0] = 0x00;
483
} else {
484
exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
485
&fileInfo);
486
}
487
488
/* 2. Handle 'a' : Output attributes (File doesn't have to exist) */
489
if (wmemchr(firstModifier, 'a', modifierLen) != NULL) {
490
491
doneModifier = TRUE;
492
493
if (exists) {
494
lstrcpyW(thisoutput, L"---------");
495
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
496
thisoutput[0]='d';
497
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
498
thisoutput[1]='r';
499
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
500
thisoutput[2]='a';
501
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
502
thisoutput[3]='h';
503
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
504
thisoutput[4]='s';
505
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
506
thisoutput[5]='c';
507
/* FIXME: What are 6 and 7? */
508
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
509
thisoutput[8]='l';
510
lstrcatW(finaloutput, thisoutput);
511
}
512
}
513
514
/* 3. Handle 't' : Date+time (File doesn't have to exist) */
515
if (wmemchr(firstModifier, 't', modifierLen) != NULL) {
516
517
SYSTEMTIME systime;
518
int datelen;
519
520
doneModifier = TRUE;
521
522
if (exists) {
523
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
524
525
/* Format the time */
526
FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
527
GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
528
NULL, thisoutput, MAX_PATH);
529
lstrcatW(thisoutput, L" ");
530
datelen = lstrlenW(thisoutput);
531
GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
532
NULL, (thisoutput+datelen), MAX_PATH-datelen);
533
lstrcatW(finaloutput, thisoutput);
534
}
535
}
536
537
/* 4. Handle 'z' : File length (File doesn't have to exist) */
538
if (wmemchr(firstModifier, 'z', modifierLen) != NULL) {
539
doneModifier = TRUE;
540
if (exists) {
541
ULONG64 fullsize = ((ULONG64)fileInfo.nFileSizeHigh << 32) | fileInfo.nFileSizeLow;
542
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
543
wsprintfW(thisoutput, L"%I64u", fullsize);
544
lstrcatW(finaloutput, thisoutput);
545
}
546
}
547
548
/* 4. Handle 's' : Use short paths (File doesn't have to exist) */
549
if (wmemchr(firstModifier, 's', modifierLen) != NULL) {
550
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
551
552
/* Convert fullfilename's path to a short path - Save filename away as
553
only path is valid, name may not exist which causes GetShortPathName
554
to fail if it is provided */
555
if (filepart) {
556
lstrcpyW(thisoutput, filepart);
557
*filepart = 0x00;
558
GetShortPathNameW(fullfilename, fullfilename, ARRAY_SIZE(fullfilename));
559
lstrcatW(fullfilename, thisoutput);
560
}
561
}
562
563
/* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
564
/* Note this overrides d,p,n,x */
565
if (wmemchr(firstModifier, 'f', modifierLen) != NULL) {
566
doneModifier = TRUE;
567
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
568
lstrcatW(finaloutput, fullfilename);
569
} else {
570
571
WCHAR drive[10];
572
WCHAR dir[MAX_PATH];
573
WCHAR fname[MAX_PATH];
574
WCHAR ext[MAX_PATH];
575
BOOL doneFileModifier = FALSE;
576
BOOL addSpace = (finaloutput[0] != 0x00);
577
578
/* Split into components */
579
_wsplitpath(fullfilename, drive, dir, fname, ext);
580
581
/* 5. Handle 'd' : Drive Letter */
582
if (wmemchr(firstModifier, 'd', modifierLen) != NULL) {
583
if (addSpace) {
584
lstrcatW(finaloutput, L" ");
585
addSpace = FALSE;
586
}
587
588
lstrcatW(finaloutput, drive);
589
doneModifier = TRUE;
590
doneFileModifier = TRUE;
591
}
592
593
/* 6. Handle 'p' : Path */
594
if (wmemchr(firstModifier, 'p', modifierLen) != NULL) {
595
if (addSpace) {
596
lstrcatW(finaloutput, L" ");
597
addSpace = FALSE;
598
}
599
600
lstrcatW(finaloutput, dir);
601
doneModifier = TRUE;
602
doneFileModifier = TRUE;
603
}
604
605
/* 7. Handle 'n' : Name */
606
if (wmemchr(firstModifier, 'n', modifierLen) != NULL) {
607
if (addSpace) {
608
lstrcatW(finaloutput, L" ");
609
addSpace = FALSE;
610
}
611
612
lstrcatW(finaloutput, fname);
613
doneModifier = TRUE;
614
doneFileModifier = TRUE;
615
}
616
617
/* 8. Handle 'x' : Ext */
618
if (wmemchr(firstModifier, 'x', modifierLen) != NULL) {
619
if (addSpace) {
620
lstrcatW(finaloutput, L" ");
621
addSpace = FALSE;
622
}
623
624
lstrcatW(finaloutput, ext);
625
doneModifier = TRUE;
626
doneFileModifier = TRUE;
627
}
628
629
/* If 's' but no other parameter, dump the whole thing */
630
if (!doneFileModifier &&
631
wmemchr(firstModifier, 's', modifierLen) != NULL) {
632
doneModifier = TRUE;
633
if (finaloutput[0] != 0x00) lstrcatW(finaloutput, L" ");
634
lstrcatW(finaloutput, fullfilename);
635
}
636
}
637
}
638
639
/* If No other modifier processed, just add in parameter */
640
if (!doneModifier) lstrcpyW(finaloutput, outputparam);
641
642
/* Finish by inserting the replacement into the string */
643
WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
644
}
645
646
extern void WCMD_expand(const WCHAR *, WCHAR *);
647
648
/*******************************************************************
649
* WCMD_call - processes a batch call statement
650
*
651
* If there is a leading ':', calls within this batch program
652
* otherwise launches another program.
653
*/
654
RETURN_CODE WCMD_call(WCHAR *command)
655
{
656
RETURN_CODE return_code;
657
WCHAR buffer[MAXSTRING];
658
WCHAR *start;
659
660
WCMD_expand(command, buffer);
661
662
/* (call) shall return 1, while (call ) returns 0 */
663
start = WCMD_skip_leading_spaces(buffer);
664
if (*start == L'\0')
665
return_code = errorlevel = start == buffer ? ERROR_INVALID_FUNCTION : NO_ERROR;
666
/* Run other program if no leading ':' */
667
else if (*start != ':')
668
{
669
WCMD_call_command(start);
670
/* If the thing we try to run does not exist, call returns 1 */
671
if (errorlevel == RETURN_CODE_CANT_LAUNCH)
672
errorlevel = ERROR_INVALID_FUNCTION;
673
return_code = errorlevel;
674
}
675
else if (WCMD_is_in_context(NULL))
676
{
677
WCHAR gotoLabel[MAX_PATH];
678
679
lstrcpyW(gotoLabel, param1);
680
681
/* Save the for variable context, then start with an empty context
682
as for loop variables do not survive a call */
683
WCMD_save_for_loop_context(TRUE);
684
685
context = push_batch_context(buffer, context->batch_file, context->file_position.QuadPart);
686
687
/* FIXME as commands here can temper with param1 global variable (ugly) */
688
lstrcpyW(param1, gotoLabel);
689
WCMD_goto();
690
691
WCMD_batch_main_loop();
692
693
context = pop_batch_context(context);
694
return_code = errorlevel;
695
696
/* Restore the for loop context */
697
WCMD_restore_for_loop_context();
698
}
699
else
700
{
701
WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT));
702
return_code = ERROR_INVALID_FUNCTION;
703
}
704
return return_code;
705
}
706
707
void WCMD_set_label_end(WCHAR *string)
708
{
709
static const WCHAR labelEndsW[] = L"><|& :\t";
710
WCHAR *p;
711
712
/* Label ends at whitespace or redirection characters */
713
if ((p = wcspbrk(string, labelEndsW))) *p = L'\0';
714
}
715
716
static BOOL find_next_label(HANDLE h, ULONGLONG end, WCHAR candidate[MAXSTRING], UINT code_page)
717
{
718
while (WCMD_fgets_helper(candidate, MAXSTRING, h, code_page))
719
{
720
WCHAR *str = candidate;
721
722
/* Ignore leading whitespace or no-echo character */
723
while (*str == L'@' || iswspace(*str)) str++;
724
725
/* If the first real character is a : then this is a label */
726
if (*str == L':')
727
{
728
/* Skip spaces between : and label */
729
for (str++; iswspace(*str); str++) {}
730
memmove(candidate, str, (wcslen(str) + 1) * sizeof(WCHAR));
731
WCMD_set_label_end(candidate);
732
733
return TRUE;
734
}
735
if (end)
736
{
737
LARGE_INTEGER li = {.QuadPart = 0}, curli;
738
if (!SetFilePointerEx(h, li, &curli, FILE_CURRENT)) return FALSE;
739
if (curli.QuadPart > end) break;
740
}
741
}
742
return FALSE;
743
}
744
745
static LARGE_INTEGER li_not_found = {.QuadPart = 0x7fffffffffffffffll};
746
747
static void insert_label_cache_entry(const WCHAR *label, LARGE_INTEGER from, LARGE_INTEGER at)
748
{
749
struct batch_file *batchfile = context->batch_file;
750
unsigned int i, worst_index = ~0u, worst_age = 0;
751
752
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
753
if (batchfile->cache[i].label)
754
batchfile->cache[i].age++;
755
else
756
worst_index = i;
757
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
758
{
759
if (batchfile->cache[i].label && !lstrcmpiW(batchfile->cache[i].label, label) &&
760
batchfile->cache[i].position.QuadPart == at.QuadPart)
761
{
762
batchfile->cache[i].age = 0;
763
/* decrease 'from' position if we have a larger match */
764
if (batchfile->cache[i].from.QuadPart > from.QuadPart)
765
batchfile->cache[i].from.QuadPart = from.QuadPart;
766
return;
767
}
768
}
769
if (worst_index == ~0u) /* all cache lines are used, find lru */
770
{
771
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
772
{
773
if (batchfile->cache[i].age > worst_age)
774
{
775
worst_index = i;
776
worst_age = batchfile->cache[i].age;
777
}
778
}
779
}
780
free((void*)batchfile->cache[worst_index].label);
781
batchfile->cache[worst_index].label = xstrdupW(label);
782
batchfile->cache[worst_index].from = from;
783
batchfile->cache[worst_index].position = at;
784
batchfile->cache[worst_index].age = 0;
785
}
786
787
static BOOL find_label_cache_entry(const WCHAR *label, LARGE_INTEGER from, LARGE_INTEGER *at)
788
{
789
struct batch_file *batchfile = context->batch_file;
790
unsigned int i;
791
792
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
793
batchfile->cache[i].age++;
794
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
795
{
796
if (batchfile->cache[i].label && !lstrcmpiW(batchfile->cache[i].label, label) &&
797
batchfile->cache[i].from.QuadPart <= from.QuadPart &&
798
from.QuadPart <= batchfile->cache[i].position.QuadPart)
799
{
800
*at = batchfile->cache[i].position;
801
batchfile->cache[i].age = 0;
802
return TRUE;
803
}
804
}
805
return FALSE;
806
}
807
808
static void check_if_valid_label_cache(HANDLE h)
809
{
810
struct batch_file *batchfile = context->batch_file;
811
FILETIME last;
812
unsigned int i;
813
814
if (!GetFileTime(h, NULL, NULL, &last) ||
815
batchfile->last_modified.dwHighDateTime != last.dwHighDateTime ||
816
batchfile->last_modified.dwLowDateTime != last.dwLowDateTime)
817
{
818
TRACE("Invalidating cache\n");
819
batchfile->last_modified = last;
820
for (i = 0; i < ARRAY_SIZE(batchfile->cache); i++)
821
{
822
free((void *)batchfile->cache[i].label);
823
batchfile->cache[i].label = NULL;
824
}
825
}
826
}
827
828
BOOL WCMD_find_label(HANDLE h, const WCHAR *label, LARGE_INTEGER *pos)
829
{
830
LARGE_INTEGER where = *pos, zeroli = {.QuadPart = 0};
831
WCHAR candidate[MAXSTRING];
832
UINT code_page = get_current_code_page();
833
834
if (!*label) return FALSE;
835
836
check_if_valid_label_cache(h);
837
838
if (!SetFilePointerEx(h, *pos, NULL, FILE_BEGIN)) return FALSE;
839
if (find_label_cache_entry(label, *pos, pos))
840
{
841
if (pos->QuadPart != li_not_found.QuadPart) return TRUE;
842
}
843
else
844
{
845
while (find_next_label(h, ~(ULONGLONG)0, candidate, code_page))
846
{
847
TRACE("comparing found label %s\n", wine_dbgstr_w(candidate));
848
if (!lstrcmpiW(candidate, label))
849
{
850
BOOL ret = SetFilePointerEx(h, zeroli, pos, FILE_CURRENT);
851
if (ret)
852
insert_label_cache_entry(label, where, *pos);
853
return ret;
854
}
855
}
856
insert_label_cache_entry(label, where, li_not_found);
857
}
858
TRACE("Label not found, trying from beginning of file\n");
859
if (!SetFilePointerEx(h, zeroli, NULL, FILE_BEGIN)) return FALSE;
860
if (find_label_cache_entry(label, zeroli, pos))
861
{
862
if (pos->QuadPart != li_not_found.QuadPart) return TRUE;
863
}
864
else
865
{
866
while (find_next_label(h, where.QuadPart, candidate, code_page))
867
{
868
TRACE("comparing found label %s\n", wine_dbgstr_w(candidate));
869
if (!lstrcmpiW(candidate, label))
870
{
871
BOOL ret = SetFilePointerEx(h, zeroli, pos, FILE_CURRENT);
872
if (ret)
873
insert_label_cache_entry(label, zeroli, *pos);
874
return ret;
875
}
876
}
877
insert_label_cache_entry(label, where, li_not_found);
878
}
879
TRACE("Reached wrap point, label not found\n");
880
return FALSE;
881
}
882
883