Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/programs/cmd/builtins.c
4387 views
1
/*
2
* CMD - Wine-compatible command line interface - built-in functions.
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
/*
23
* FIXME:
24
* - No support for pipes, shell parameters
25
* - Lots of functionality missing from builtins
26
* - Messages etc need international support
27
*/
28
29
#define WIN32_LEAN_AND_MEAN
30
31
#include "wcmd.h"
32
#include <shellapi.h>
33
#include "wine/debug.h"
34
35
WINE_DEFAULT_DEBUG_CHANNEL(cmd);
36
37
extern BOOL echo_mode;
38
39
struct env_stack *pushd_directories;
40
const WCHAR inbuilt[][10] = {
41
L"CALL",
42
L"CD",
43
L"CHDIR",
44
L"CLS",
45
L"COPY",
46
L"",
47
L"DATE",
48
L"DEL",
49
L"DIR",
50
L"ECHO",
51
L"ERASE",
52
L"FOR",
53
L"GOTO",
54
L"HELP",
55
L"IF",
56
L"LABEL",
57
L"MD",
58
L"MKDIR",
59
L"MOVE",
60
L"PATH",
61
L"PAUSE",
62
L"PROMPT",
63
L"REM",
64
L"REN",
65
L"RENAME",
66
L"RD",
67
L"RMDIR",
68
L"SET",
69
L"SHIFT",
70
L"START",
71
L"TIME",
72
L"TITLE",
73
L"TYPE",
74
L"VERIFY",
75
L"VER",
76
L"VOL",
77
L"ENDLOCAL",
78
L"SETLOCAL",
79
L"PUSHD",
80
L"POPD",
81
L"ASSOC",
82
L"COLOR",
83
L"FTYPE",
84
L"MORE",
85
L"CHOICE",
86
L"MKLINK",
87
L"",
88
L"EXIT"
89
};
90
static const WCHAR externals[][10] = {
91
L"ATTRIB",
92
L"XCOPY"
93
};
94
95
static HINSTANCE hinst;
96
static struct env_stack *saved_environment;
97
static BOOL verify_mode = FALSE;
98
99
/* set /a routines work from single character operators, but some of the
100
operators are multiple character ones, especially the assignment ones.
101
Temporarily represent these using the values below on the operator stack */
102
#define OP_POSITIVE 'P'
103
#define OP_NEGATIVE 'N'
104
#define OP_ASSSIGNMUL 'a'
105
#define OP_ASSSIGNDIV 'b'
106
#define OP_ASSSIGNMOD 'c'
107
#define OP_ASSSIGNADD 'd'
108
#define OP_ASSSIGNSUB 'e'
109
#define OP_ASSSIGNAND 'f'
110
#define OP_ASSSIGNNOT 'g'
111
#define OP_ASSSIGNOR 'h'
112
#define OP_ASSSIGNSHL 'i'
113
#define OP_ASSSIGNSHR 'j'
114
115
/* This maintains a stack of operators, holding both the operator precedence
116
and the single character representation of the operator in question */
117
typedef struct _OPSTACK
118
{
119
int precedence;
120
WCHAR op;
121
struct _OPSTACK *next;
122
} OPSTACK;
123
124
/* This maintains a stack of values, where each value can either be a
125
numeric value, or a string representing an environment variable */
126
typedef struct _VARSTACK
127
{
128
BOOL isnum;
129
WCHAR *variable;
130
int value;
131
struct _VARSTACK *next;
132
} VARSTACK;
133
134
/* This maintains a mapping between the calculated operator and the
135
single character representation for the assignment operators. */
136
static struct
137
{
138
WCHAR op;
139
WCHAR calculatedop;
140
} calcassignments[] =
141
{
142
{'*', OP_ASSSIGNMUL},
143
{'/', OP_ASSSIGNDIV},
144
{'%', OP_ASSSIGNMOD},
145
{'+', OP_ASSSIGNADD},
146
{'-', OP_ASSSIGNSUB},
147
{'&', OP_ASSSIGNAND},
148
{'^', OP_ASSSIGNNOT},
149
{'|', OP_ASSSIGNOR},
150
{'<', OP_ASSSIGNSHL},
151
{'>', OP_ASSSIGNSHR},
152
{' ',' '}
153
};
154
155
DIRECTORY_STACK *WCMD_dir_stack_create(const WCHAR *dir, const WCHAR *file)
156
{
157
DIRECTORY_STACK *new = xalloc(sizeof(DIRECTORY_STACK));
158
159
new->next = NULL;
160
new->fileName = NULL;
161
if (!dir && !file)
162
{
163
DWORD sz = GetCurrentDirectoryW(0, NULL);
164
new->dirName = xalloc(sz * sizeof(WCHAR));
165
GetCurrentDirectoryW(sz, new->dirName);
166
}
167
else if (!file)
168
new->dirName = xstrdupW(dir);
169
else
170
{
171
new->dirName = xalloc((wcslen(dir) + 1 + wcslen(file) + 1) * sizeof(WCHAR));
172
wcscpy(new->dirName, dir);
173
wcscat(new->dirName, L"\\");
174
wcscat(new->dirName, file);
175
}
176
return new;
177
}
178
179
DIRECTORY_STACK *WCMD_dir_stack_free(DIRECTORY_STACK *dir)
180
{
181
DIRECTORY_STACK *next;
182
183
if (!dir) return NULL;
184
next = dir->next;
185
free(dir->dirName);
186
free(dir);
187
return next;
188
}
189
190
/**************************************************************************
191
* WCMD_ask_confirm
192
*
193
* Issue a message and ask for confirmation, waiting on a valid answer.
194
*
195
* Returns True if Y (or A) answer is selected
196
* If optionAll contains a pointer, ALL is allowed, and if answered
197
* set to TRUE
198
*
199
*/
200
static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
201
BOOL *optionAll) {
202
203
UINT msgid;
204
WCHAR confirm[MAXSTRING];
205
WCHAR options[MAXSTRING];
206
WCHAR Ybuffer[MAXSTRING];
207
WCHAR Nbuffer[MAXSTRING];
208
WCHAR Abuffer[MAXSTRING];
209
WCHAR answer[MAX_PATH] = {'\0'};
210
DWORD count = 0;
211
212
/* Load the translated valid answers */
213
if (showSureText)
214
LoadStringW(hinst, WCMD_CONFIRM, confirm, ARRAY_SIZE(confirm));
215
msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
216
LoadStringW(hinst, msgid, options, ARRAY_SIZE(options));
217
LoadStringW(hinst, WCMD_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
218
LoadStringW(hinst, WCMD_NO, Nbuffer, ARRAY_SIZE(Nbuffer));
219
LoadStringW(hinst, WCMD_ALL, Abuffer, ARRAY_SIZE(Abuffer));
220
221
/* Loop waiting on a valid answer */
222
if (optionAll)
223
*optionAll = FALSE;
224
while (1)
225
{
226
WCMD_output_asis (message);
227
if (showSureText)
228
WCMD_output_asis (confirm);
229
WCMD_output_asis (options);
230
if (!WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count) || !count)
231
return FALSE;
232
answer[0] = towupper(answer[0]);
233
if (answer[0] == Ybuffer[0])
234
return TRUE;
235
if (answer[0] == Nbuffer[0])
236
return FALSE;
237
if (optionAll && answer[0] == Abuffer[0])
238
{
239
*optionAll = TRUE;
240
return TRUE;
241
}
242
}
243
}
244
245
/****************************************************************************
246
* WCMD_clear_screen
247
*
248
* Clear the terminal screen.
249
*/
250
251
RETURN_CODE WCMD_clear_screen(void)
252
{
253
/* Emulate by filling the screen from the top left to bottom right with
254
spaces, then moving the cursor to the top left afterwards */
255
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
256
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
257
258
if (*quals)
259
return errorlevel = ERROR_INVALID_FUNCTION;
260
if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
261
{
262
COORD topLeft;
263
DWORD screenSize, written;
264
265
screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
266
267
topLeft.X = 0;
268
topLeft.Y = 0;
269
FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
270
FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
271
SetConsoleCursorPosition(hStdOut, topLeft);
272
}
273
return NO_ERROR;
274
}
275
276
/****************************************************************************
277
* WCMD_choice
278
*
279
*/
280
281
RETURN_CODE WCMD_choice(WCHAR *args)
282
{
283
RETURN_CODE return_code = NO_ERROR;
284
WCHAR answer[16];
285
WCHAR buffer[16];
286
WCHAR *ptr = NULL;
287
WCHAR *opt_c = NULL;
288
WCHAR *opt_m = NULL;
289
WCHAR opt_default = 0;
290
DWORD opt_timeout = -1;
291
WCHAR *end;
292
DWORD oldmode;
293
BOOL have_console;
294
BOOL opt_n = FALSE;
295
BOOL opt_cs = FALSE;
296
int argno;
297
298
for (argno = 0; ; argno++)
299
{
300
WCHAR *arg = WCMD_parameter(args, argno, NULL, FALSE, FALSE);
301
if (!*arg) break;
302
303
if (!wcsicmp(arg, L"/N")) opt_n = TRUE;
304
else if (!wcsicmp(arg, L"/CS")) opt_cs = TRUE;
305
else if (arg[0] == L'/' && wcschr(L"CDTM", towupper(arg[1])))
306
{
307
WCHAR opt = towupper(arg[1]);
308
if (arg[2] == L'\0')
309
{
310
arg = WCMD_parameter(args, ++argno, NULL, FALSE, FALSE);
311
if (!*arg)
312
{
313
return_code = ERROR_INVALID_FUNCTION;
314
break;
315
}
316
}
317
else if (arg[2] == L':')
318
arg += 3;
319
else
320
{
321
return_code = ERROR_INVALID_FUNCTION;
322
break;
323
}
324
switch (opt)
325
{
326
case L'C':
327
opt_c = wcsdup(arg);
328
break;
329
case L'M':
330
opt_m = wcsdup(arg);
331
break;
332
case L'D':
333
opt_default = *arg;
334
break;
335
case L'T':
336
opt_timeout = wcstol(arg, &end, 10);
337
if (end == arg || (*end && !iswspace(*end)))
338
opt_timeout = 10000;
339
break;
340
}
341
}
342
else
343
return_code = ERROR_INVALID_FUNCTION;
344
}
345
346
/* use default keys, when needed: localized versions of "Y"es and "No" */
347
if (!opt_c)
348
{
349
LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
350
LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
351
opt_c = buffer;
352
buffer[2] = L'\0';
353
}
354
/* validate various options */
355
if (!opt_cs) wcsupr(opt_c);
356
/* check that default is in the choices list */
357
if (!wcschr(opt_c, opt_cs ? opt_default : towupper(opt_default)))
358
return_code = ERROR_INVALID_FUNCTION;
359
/* check that there's no duplicates in the choices list */
360
for (ptr = opt_c; *ptr; ptr++)
361
if (wcschr(ptr + 1, opt_cs ? *ptr : towupper(*ptr)))
362
return_code = ERROR_INVALID_FUNCTION;
363
364
TRACE("CHOICE message(%s) choices(%s) timeout(%ld) default(%c)\n",
365
debugstr_w(opt_m), debugstr_w(opt_c), opt_timeout, opt_default ? opt_default : '?');
366
if (return_code != NO_ERROR ||
367
(opt_timeout == -1) != (opt_default == L'\0') ||
368
(opt_timeout != -1 && opt_timeout > 9999))
369
{
370
WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
371
errorlevel = 255;
372
if (opt_c != buffer) free(opt_c);
373
free(opt_m);
374
return ERROR_INVALID_FUNCTION;
375
}
376
377
have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
378
if (have_console)
379
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
380
381
/* print the question, when needed */
382
if (opt_m)
383
WCMD_output_asis(opt_m);
384
385
if (!opt_n)
386
{
387
/* print a list of all allowed answers inside brackets */
388
if (opt_m) WCMD_output_asis(L" ");
389
WCMD_output_asis(L"[");
390
answer[1] = L'\0';
391
for (ptr = opt_c; *ptr; ptr++)
392
{
393
if (ptr != opt_c)
394
WCMD_output_asis(L",");
395
answer[0] = *ptr;
396
WCMD_output_asis(answer);
397
}
398
WCMD_output_asis(L"]?");
399
}
400
401
while (return_code == NO_ERROR)
402
{
403
if (opt_timeout == 0)
404
answer[0] = opt_default;
405
else
406
{
407
LARGE_INTEGER li, zeroli = {0};
408
OVERLAPPED overlapped = {0};
409
DWORD count;
410
char choice;
411
412
overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
413
if (SetFilePointerEx(GetStdHandle(STD_INPUT_HANDLE), zeroli, &li, FILE_CURRENT))
414
{
415
overlapped.Offset = li.LowPart;
416
overlapped.OffsetHigh = li.HighPart;
417
}
418
if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), &choice, 1, NULL, &overlapped))
419
{
420
switch (WaitForSingleObject(overlapped.hEvent, opt_timeout == -1 ? INFINITE : opt_timeout * 1000))
421
{
422
case WAIT_OBJECT_0:
423
answer[0] = choice;
424
break;
425
case WAIT_TIMEOUT:
426
answer[0] = opt_default;
427
break;
428
default:
429
return_code = ERROR_INVALID_FUNCTION;
430
}
431
}
432
else if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), &choice, 1, &count, NULL))
433
{
434
if (count == 0)
435
{
436
if (opt_timeout != -1)
437
answer[0] = opt_default;
438
else
439
return_code = ERROR_INVALID_FUNCTION;
440
}
441
else
442
answer[0] = choice;
443
}
444
else
445
return_code = ERROR_INVALID_FUNCTION;
446
CloseHandle(overlapped.hEvent);
447
}
448
if (return_code != NO_ERROR)
449
{
450
errorlevel = 255;
451
break;
452
}
453
if (!opt_cs)
454
answer[0] = towupper(answer[0]);
455
456
answer[1] = L'\0'; /* terminate single character string */
457
ptr = wcschr(opt_c, answer[0]);
458
if (ptr)
459
{
460
WCMD_output_asis(answer);
461
WCMD_output_asis(L"\r\n");
462
463
return_code = errorlevel = (ptr - opt_c) + 1;
464
TRACE("answer: %d\n", return_code);
465
}
466
else
467
{
468
/* key not allowed: play the bell */
469
TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
470
WCMD_output_asis(L"\a");
471
}
472
}
473
if (have_console)
474
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
475
476
if (opt_c != buffer) free(opt_c);
477
free(opt_m);
478
return return_code;
479
}
480
481
/****************************************************************************
482
* WCMD_AppendEOF
483
*
484
* Adds an EOF onto the end of a file
485
* Returns TRUE on success
486
*/
487
static BOOL WCMD_AppendEOF(WCHAR *filename)
488
{
489
HANDLE h;
490
DWORD bytes_written;
491
492
char eof = '\x1a';
493
494
WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
495
h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
496
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
497
498
if (h == INVALID_HANDLE_VALUE) {
499
WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(filename), GetLastError());
500
return FALSE;
501
} else {
502
SetFilePointer (h, 0, NULL, FILE_END);
503
if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
504
WINE_ERR("Failed to append EOF to %s (%ld)\n", wine_dbgstr_w(filename), GetLastError());
505
CloseHandle(h);
506
return FALSE;
507
}
508
CloseHandle(h);
509
}
510
return TRUE;
511
}
512
513
/****************************************************************************
514
* WCMD_IsSameFile
515
*
516
* Checks if the two paths reference to the same file
517
*/
518
static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
519
{
520
BOOL ret = FALSE;
521
HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
522
BY_HANDLE_FILE_INFORMATION info1, info2;
523
524
file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
525
if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
526
goto end;
527
528
file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
529
if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
530
goto end;
531
532
ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
533
&& info1.nFileIndexHigh == info2.nFileIndexHigh
534
&& info1.nFileIndexLow == info2.nFileIndexLow;
535
end:
536
if (file1 != INVALID_HANDLE_VALUE)
537
CloseHandle(file1);
538
if (file2 != INVALID_HANDLE_VALUE)
539
CloseHandle(file2);
540
return ret;
541
}
542
543
/****************************************************************************
544
* WCMD_ManualCopy
545
*
546
* Copies from a file
547
* optionally reading only until EOF (ascii copy)
548
* optionally appending onto an existing file (append)
549
* Returns TRUE on success
550
*/
551
static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
552
{
553
HANDLE in,out;
554
BOOL ok;
555
DWORD bytesread, byteswritten;
556
557
WINE_TRACE("Manual Copying %s to %s (ascii: %u) (append: %u)\n",
558
wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), ascii, append);
559
560
in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
561
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
562
if (in == INVALID_HANDLE_VALUE) {
563
WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(srcname), GetLastError());
564
return FALSE;
565
}
566
567
/* Open the output file, overwriting if not appending */
568
out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
569
append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
570
if (out == INVALID_HANDLE_VALUE) {
571
WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(dstname), GetLastError());
572
CloseHandle(in);
573
return FALSE;
574
}
575
576
/* Move to end of destination if we are going to append to it */
577
if (append) {
578
SetFilePointer(out, 0, NULL, FILE_END);
579
}
580
581
/* Loop copying data from source to destination until EOF read */
582
do
583
{
584
char buffer[MAXSTRING];
585
586
ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
587
if (ok) {
588
589
/* Stop at first EOF */
590
if (ascii) {
591
char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
592
if (ptr) bytesread = (ptr - buffer);
593
}
594
595
if (bytesread) {
596
ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
597
if (!ok || byteswritten != bytesread) {
598
WINE_ERR("Unexpected failure writing to %s, rc=%ld\n",
599
wine_dbgstr_w(dstname), GetLastError());
600
}
601
}
602
} else {
603
WINE_ERR("Unexpected failure reading from %s, rc=%ld\n",
604
wine_dbgstr_w(srcname), GetLastError());
605
}
606
} while (ok && bytesread > 0);
607
608
CloseHandle(out);
609
CloseHandle(in);
610
return ok;
611
}
612
613
/****************************************************************************
614
* WCMD_copy
615
*
616
* Copy a file or wildcarded set.
617
* For ascii/binary type copies, it gets complex:
618
* Syntax on command line is
619
* ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
620
* Where first /a or /b sets 'mode in operation' until another is found
621
* once another is found, it applies to the file preceding the /a or /b
622
* In addition each filename can contain wildcards
623
* To make matters worse, the + may be in the same parameter (i.e. no
624
* whitespace) or with whitespace separating it
625
*
626
* ASCII mode on read == read and stop at first EOF
627
* ASCII mode on write == append EOF to destination
628
* Binary == copy as-is
629
*
630
* Design of this is to build up a list of files which will be copied into a
631
* list, then work through the list file by file.
632
* If no destination is specified, it defaults to the name of the first file in
633
* the list, but the current directory.
634
*
635
*/
636
637
RETURN_CODE WCMD_copy(WCHAR * args)
638
{
639
BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
640
WCHAR *thisparam;
641
int argno = 0;
642
WCHAR *rawarg;
643
WIN32_FIND_DATAW fd;
644
HANDLE hff = INVALID_HANDLE_VALUE;
645
int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
646
BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
647
BOOL anyconcats = FALSE; /* Have we found any + options */
648
BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
649
BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
650
BOOL prompt; /* Prompt before overwriting */
651
WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
652
BOOL destisdirectory = FALSE; /* Is the destination a directory? */
653
BOOL status;
654
WCHAR copycmd[4];
655
DWORD len;
656
BOOL dstisdevice = FALSE;
657
unsigned numcopied = 0;
658
659
typedef struct _COPY_FILES
660
{
661
struct _COPY_FILES *next;
662
BOOL concatenate;
663
WCHAR *name;
664
int binarycopy;
665
} COPY_FILES;
666
COPY_FILES *sourcelist = NULL;
667
COPY_FILES *lastcopyentry = NULL;
668
COPY_FILES *destination = NULL;
669
COPY_FILES *thiscopy = NULL;
670
COPY_FILES *prevcopy = NULL;
671
RETURN_CODE return_code;
672
673
/* Assume we were successful! */
674
return_code = NO_ERROR;
675
676
/* If no args supplied at all, report an error */
677
if (param1[0] == 0x00) {
678
WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
679
return errorlevel = ERROR_INVALID_FUNCTION;
680
}
681
682
opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
683
684
/* Walk through all args, building up a list of files to process */
685
thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
686
while (*(thisparam)) {
687
WCHAR *pos1, *pos2;
688
BOOL inquotes;
689
690
WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
691
692
/* Handle switches */
693
if (*thisparam == '/') {
694
while (*thisparam == '/') {
695
thisparam++;
696
if (towupper(*thisparam) == 'D') {
697
opt_d = TRUE;
698
if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
699
} else if (towupper(*thisparam) == 'Y') {
700
opt_y = TRUE;
701
} else if (towupper(*thisparam) == '-' && towupper(*(thisparam+1)) == 'Y') {
702
opt_noty = TRUE;
703
} else if (towupper(*thisparam) == 'V') {
704
opt_v = TRUE;
705
if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
706
} else if (towupper(*thisparam) == 'N') {
707
opt_n = TRUE;
708
if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
709
} else if (towupper(*thisparam) == 'Z') {
710
opt_z = TRUE;
711
if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
712
} else if (towupper(*thisparam) == 'A') {
713
if (binarymode != 0) {
714
binarymode = 0;
715
WINE_TRACE("Subsequent files will be handled as ASCII\n");
716
if (destination != NULL) {
717
WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
718
destination->binarycopy = binarymode;
719
} else if (lastcopyentry != NULL) {
720
WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
721
lastcopyentry->binarycopy = binarymode;
722
}
723
}
724
} else if (towupper(*thisparam) == 'B') {
725
if (binarymode != 1) {
726
binarymode = 1;
727
WINE_TRACE("Subsequent files will be handled as binary\n");
728
if (destination != NULL) {
729
WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
730
destination->binarycopy = binarymode;
731
} else if (lastcopyentry != NULL) {
732
WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
733
lastcopyentry->binarycopy = binarymode;
734
}
735
}
736
} else {
737
WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
738
}
739
thisparam++;
740
}
741
742
/* This parameter was purely switches, get the next one */
743
thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
744
continue;
745
}
746
747
/* We have found something which is not a switch. If could be anything of the form
748
sourcefilename (which could be destination too)
749
+ (when filename + filename syntex used)
750
sourcefilename+sourcefilename
751
+sourcefilename
752
+/b[tests show windows then ignores to end of parameter]
753
*/
754
755
if (*thisparam=='+') {
756
if (lastcopyentry == NULL) {
757
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
758
return_code = ERROR_INVALID_FUNCTION;
759
goto exitreturn;
760
} else {
761
concatnextfilename = TRUE;
762
anyconcats = TRUE;
763
}
764
765
/* Move to next thing to process */
766
thisparam++;
767
if (*thisparam == 0x00)
768
thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
769
continue;
770
}
771
772
/* We have found something to process - build a COPY_FILE block to store it */
773
thiscopy = xalloc(sizeof(COPY_FILES));
774
775
WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
776
thiscopy->concatenate = concatnextfilename;
777
thiscopy->binarycopy = binarymode;
778
thiscopy->next = NULL;
779
780
/* Time to work out the name. Allocate at least enough space (deliberately too much to
781
leave space to append \* to the end) , then copy in character by character. Strip off
782
quotes if we find them. */
783
len = lstrlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
784
thiscopy->name = xalloc(len * sizeof(WCHAR));
785
memset(thiscopy->name, 0x00, len);
786
787
pos1 = thisparam;
788
pos2 = thiscopy->name;
789
inquotes = FALSE;
790
while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
791
if (*pos1 == '"') {
792
inquotes = !inquotes;
793
pos1++;
794
} else *pos2++ = *pos1++;
795
}
796
*pos2 = 0;
797
WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
798
799
/* This is either the first source, concatenated subsequent source or destination */
800
if (sourcelist == NULL) {
801
WINE_TRACE("Adding as first source part\n");
802
sourcelist = thiscopy;
803
lastcopyentry = thiscopy;
804
} else if (concatnextfilename) {
805
WINE_TRACE("Adding to source file list to be concatenated\n");
806
lastcopyentry->next = thiscopy;
807
lastcopyentry = thiscopy;
808
} else if (destination == NULL) {
809
destination = thiscopy;
810
} else {
811
/* We have processed sources and destinations and still found more to do - invalid */
812
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
813
return_code = ERROR_INVALID_FUNCTION;
814
goto exitreturn;
815
}
816
concatnextfilename = FALSE;
817
818
/* We either need to process the rest of the parameter or move to the next */
819
if (*pos1 == '/' || *pos1 == '+') {
820
thisparam = pos1;
821
continue;
822
} else {
823
thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
824
}
825
}
826
827
/* Ensure we have at least one source file */
828
if (!sourcelist) {
829
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
830
return_code = ERROR_INVALID_FUNCTION;
831
goto exitreturn;
832
}
833
834
/* Default whether automatic overwriting is on. If we are interactive then
835
we prompt by default, otherwise we overwrite by default
836
/-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
837
if (opt_noty) prompt = TRUE;
838
else if (opt_y) prompt = FALSE;
839
else {
840
/* By default, we will force the overwrite in batch mode and ask for
841
* confirmation in interactive mode. */
842
prompt = !context;
843
/* If COPYCMD is set, then we force the overwrite with /Y and ask for
844
* confirmation with /-Y. If COPYCMD is neither of those, then we use the
845
* default behavior. */
846
len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
847
if (len && len < ARRAY_SIZE(copycmd)) {
848
if (!lstrcmpiW(copycmd, L"/Y"))
849
prompt = FALSE;
850
else if (!lstrcmpiW(copycmd, L"/-Y"))
851
prompt = TRUE;
852
}
853
}
854
855
/* Calculate the destination now - if none supplied, it's current dir +
856
filename of first file in list*/
857
if (destination == NULL) {
858
859
WINE_TRACE("No destination supplied, so need to calculate it\n");
860
861
lstrcpyW(destname, L".");
862
lstrcatW(destname, L"\\");
863
864
destination = xalloc(sizeof(COPY_FILES));
865
if (destination == NULL) goto exitreturn;
866
destination->concatenate = FALSE; /* Not used for destination */
867
destination->binarycopy = binarymode;
868
destination->next = NULL; /* Not used for destination */
869
destination->name = NULL; /* To be filled in */
870
destisdirectory = TRUE;
871
872
} else {
873
WCHAR *filenamepart;
874
DWORD attributes;
875
876
WINE_TRACE("Destination supplied, processing to see if file or directory\n");
877
878
/* Convert to fully qualified path/filename */
879
if (!WCMD_get_fullpath(destination->name, ARRAY_SIZE(destname), destname, &filenamepart))
880
return errorlevel = ERROR_INVALID_FUNCTION;
881
WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
882
883
/* If parameter is a directory, ensure it ends in \ */
884
attributes = GetFileAttributesW(destname);
885
if (ends_with_backslash( destname ) ||
886
((attributes != INVALID_FILE_ATTRIBUTES) &&
887
(attributes & FILE_ATTRIBUTE_DIRECTORY))) {
888
889
destisdirectory = TRUE;
890
if (!ends_with_backslash(destname)) lstrcatW(destname, L"\\");
891
WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
892
}
893
}
894
895
/* Normally, the destination is the current directory unless we are
896
concatenating, in which case it's current directory plus first filename.
897
Note that if the
898
In addition by default it is a binary copy unless concatenating, when
899
the copy defaults to an ascii copy (stop at EOF). We do not know the
900
first source part yet (until we search) so flag as needing filling in. */
901
902
if (anyconcats) {
903
/* We have found an a+b type syntax, so destination has to be a filename
904
and we need to default to ascii copying. If we have been supplied a
905
directory as the destination, we need to defer calculating the name */
906
if (destisdirectory) appendfirstsource = TRUE;
907
if (destination->binarycopy == -1) destination->binarycopy = 0;
908
909
} else if (!destisdirectory) {
910
/* We have been asked to copy to a filename. Default to ascii IF the
911
source contains wildcards (true even if only one match) */
912
if (wcspbrk(sourcelist->name, L"*?") != NULL) {
913
anyconcats = TRUE; /* We really are concatenating to a single file */
914
if (destination->binarycopy == -1) {
915
destination->binarycopy = 0;
916
}
917
} else {
918
if (destination->binarycopy == -1) {
919
destination->binarycopy = 1;
920
}
921
}
922
}
923
924
/* Save away the destination name*/
925
free(destination->name);
926
destination->name = xstrdupW(destname);
927
WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
928
wine_dbgstr_w(destname), appendfirstsource);
929
930
/* Remember if the destination is a device */
931
if (wcsncmp(destination->name, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
932
WINE_TRACE("Destination is a device\n");
933
dstisdevice = TRUE;
934
}
935
936
/* Now we need to walk the set of sources, and process each name we come to.
937
If anyconcats is true, we are writing to one file, otherwise we are using
938
the source name each time.
939
If destination exists, prompt for overwrite the first time (if concatenating
940
we ask each time until yes is answered)
941
The first source file we come across must exist (when wildcards expanded)
942
and if concatenating with overwrite prompts, each source file must exist
943
until a yes is answered. */
944
945
thiscopy = sourcelist;
946
prevcopy = NULL;
947
948
return_code = NO_ERROR;
949
while (thiscopy != NULL) {
950
951
WCHAR srcpath[MAX_PATH];
952
const WCHAR *srcname;
953
WCHAR *filenamepart;
954
DWORD attributes;
955
BOOL srcisdevice = FALSE;
956
BOOL havewildcards = FALSE;
957
BOOL displaynames = anyconcats; /* Display names if we are concatenating. */
958
959
/* If it was not explicit, we now know whether we are concatenating or not and
960
hence whether to copy as binary or ascii */
961
if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
962
963
/* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
964
to where the filename portion begins (used for wildcard expansion). */
965
if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart))
966
return errorlevel = ERROR_INVALID_FUNCTION;
967
WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
968
969
havewildcards = wcspbrk(srcpath, L"*?") ? TRUE : FALSE;
970
/* If we are not already displaying file names due to concatenation, then display them
971
if using wildards. */
972
if (!displaynames) {
973
displaynames = havewildcards;
974
}
975
976
/* If parameter is a directory, ensure it ends in \* */
977
attributes = GetFileAttributesW(srcpath);
978
if (ends_with_backslash( srcpath )) {
979
980
/* We need to know where the filename part starts, so append * and
981
recalculate the full resulting path */
982
lstrcatW(thiscopy->name, L"*");
983
displaynames = TRUE;
984
if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart))
985
return errorlevel = ERROR_INVALID_FUNCTION;
986
WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
987
988
} else if (!havewildcards &&
989
(attributes != INVALID_FILE_ATTRIBUTES) &&
990
(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
991
992
/* We need to know where the filename part starts, so append \* and
993
recalculate the full resulting path */
994
lstrcatW(thiscopy->name, L"\\*");
995
displaynames = TRUE;
996
if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart))
997
return errorlevel = ERROR_INVALID_FUNCTION;
998
WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
999
}
1000
1001
WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
1002
wine_dbgstr_w(srcpath), anyconcats);
1003
1004
/* If the source is a device, just use it, otherwise search */
1005
if (wcsncmp(srcpath, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
1006
WINE_TRACE("Source is a device\n");
1007
srcisdevice = TRUE;
1008
srcname = &srcpath[4]; /* After the \\.\ prefix */
1009
if (!wcsnicmp(srcname, L"CON", 3)) {
1010
thiscopy->binarycopy = FALSE;
1011
}
1012
} else {
1013
1014
/* Loop through all source files */
1015
WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
1016
hff = FindFirstFileW(srcpath, &fd);
1017
if (hff != INVALID_HANDLE_VALUE) {
1018
srcname = fd.cFileName;
1019
}
1020
}
1021
1022
if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
1023
do {
1024
WCHAR outname[MAX_PATH];
1025
BOOL overwrite;
1026
BOOL appendtofirstfile = FALSE;
1027
1028
/* Skip . and .., and directories */
1029
if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1030
WINE_TRACE("Skipping directories\n");
1031
} else {
1032
1033
/* Build final destination name */
1034
lstrcpyW(outname, destination->name);
1035
if (destisdirectory || appendfirstsource) lstrcatW(outname, srcname);
1036
1037
/* Build source name */
1038
if (!srcisdevice) lstrcpyW(filenamepart, srcname);
1039
1040
/* Do we just overwrite (we do if we are writing to a device) */
1041
overwrite = !prompt;
1042
if (dstisdevice || (anyconcats && writtenoneconcat)) {
1043
overwrite = TRUE;
1044
}
1045
1046
WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
1047
WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
1048
WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
1049
thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
1050
1051
if (!writtenoneconcat) {
1052
appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
1053
}
1054
1055
/* Prompt before overwriting */
1056
if (appendtofirstfile) {
1057
overwrite = TRUE;
1058
} else if (!overwrite) {
1059
DWORD attributes = GetFileAttributesW(outname);
1060
if (attributes != INVALID_FILE_ATTRIBUTES) {
1061
WCHAR* question;
1062
question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
1063
overwrite = WCMD_ask_confirm(question, FALSE, NULL);
1064
LocalFree(question);
1065
}
1066
else overwrite = TRUE;
1067
}
1068
1069
/* If we needed to save away the first filename, do it */
1070
if (appendfirstsource && overwrite) {
1071
free(destination->name);
1072
destination->name = xstrdupW(outname);
1073
WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
1074
appendfirstsource = FALSE;
1075
destisdirectory = FALSE;
1076
}
1077
1078
/* Do the copy as appropriate */
1079
if (overwrite) {
1080
if (displaynames) {
1081
WCMD_output_asis(srcpath);
1082
WCMD_output_asis(L"\r\n");
1083
}
1084
if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
1085
/* behavior is as Unix 'touch' (change last-written time only) */
1086
HANDLE file = CreateFileW(srcpath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
1087
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1088
if (file != INVALID_HANDLE_VALUE)
1089
{
1090
FILETIME file_time;
1091
SYSTEMTIME system_time;
1092
1093
GetSystemTime(&system_time);
1094
SystemTimeToFileTime(&system_time, &file_time);
1095
status = SetFileTime(file, NULL, NULL, &file_time);
1096
CloseHandle(file);
1097
}
1098
else status = FALSE;
1099
} else if (anyconcats && writtenoneconcat) {
1100
status = WCMD_ManualCopy(srcpath, outname, !thiscopy->binarycopy, TRUE);
1101
} else if (!thiscopy->binarycopy || srcisdevice) {
1102
status = WCMD_ManualCopy(srcpath, outname, !thiscopy->binarycopy, FALSE);
1103
} else {
1104
status = CopyFileW(srcpath, outname, FALSE);
1105
}
1106
if (!status) {
1107
WCMD_print_error ();
1108
return_code = ERROR_INVALID_FUNCTION;
1109
} else {
1110
WINE_TRACE("Copied successfully\n");
1111
if (anyconcats) {
1112
writtenoneconcat = TRUE;
1113
numcopied = 1;
1114
} else {
1115
numcopied++;
1116
}
1117
1118
/* Append EOF if ascii destination and we are not going to add more onto the end
1119
Note: Testing shows windows has an optimization whereas if you have a binary
1120
copy of a file to a single destination (ie concatenation) then it does not add
1121
the EOF, hence the check on the source copy type below. */
1122
if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1123
if (!WCMD_AppendEOF(outname)) {
1124
WCMD_print_error ();
1125
return_code = ERROR_INVALID_FUNCTION;
1126
}
1127
}
1128
}
1129
} else if (prompt)
1130
return_code = ERROR_INVALID_FUNCTION;
1131
}
1132
} while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1133
if (!srcisdevice) FindClose (hff);
1134
} else {
1135
/* Error if the first file was not found */
1136
if (!anyconcats || !writtenoneconcat) {
1137
WCMD_print_error ();
1138
return_code = ERROR_INVALID_FUNCTION;
1139
}
1140
}
1141
1142
/* Step on to the next supplied source */
1143
thiscopy = thiscopy -> next;
1144
}
1145
1146
/* Append EOF if ascii destination and we were concatenating */
1147
if (!return_code && !destination->binarycopy && anyconcats && writtenoneconcat) {
1148
if (!WCMD_AppendEOF(destination->name)) {
1149
WCMD_print_error ();
1150
return_code = ERROR_INVALID_FUNCTION;
1151
}
1152
}
1153
1154
if (numcopied) {
1155
WCMD_output(WCMD_LoadMessage(WCMD_NUMCOPIED), numcopied);
1156
}
1157
1158
/* Exit out of the routine, freeing any remaining allocated memory */
1159
exitreturn:
1160
1161
thiscopy = sourcelist;
1162
while (thiscopy != NULL) {
1163
prevcopy = thiscopy;
1164
/* Free up this block*/
1165
thiscopy = thiscopy -> next;
1166
free(prevcopy->name);
1167
free(prevcopy);
1168
}
1169
1170
/* Free up the destination memory */
1171
if (destination) {
1172
free(destination->name);
1173
free(destination);
1174
}
1175
1176
return errorlevel = return_code;
1177
}
1178
1179
/****************************************************************************
1180
* WCMD_create_dir
1181
*
1182
* Create a directory (and, if needed, any intermediate directories).
1183
*
1184
* Modifies its argument by replacing slashes temporarily with nulls.
1185
*/
1186
1187
static BOOL create_full_path(WCHAR* path)
1188
{
1189
WCHAR *p, *start;
1190
1191
/* don't mess with drive letter portion of path, if any */
1192
start = path;
1193
if (path[1] == ':')
1194
start = path+2;
1195
1196
/* Strip trailing slashes. */
1197
for (p = path + lstrlenW(path) - 1; p != start && *p == '\\'; p--)
1198
*p = 0;
1199
1200
/* Step through path, creating intermediate directories as needed. */
1201
/* First component includes drive letter, if any. */
1202
p = start;
1203
for (;;) {
1204
DWORD rv;
1205
/* Skip to end of component */
1206
while (*p == '\\') p++;
1207
while (*p && *p != '\\') p++;
1208
if (!*p) {
1209
/* path is now the original full path */
1210
return CreateDirectoryW(path, NULL);
1211
}
1212
/* Truncate path, create intermediate directory, and restore path */
1213
*p = 0;
1214
rv = CreateDirectoryW(path, NULL);
1215
*p = '\\';
1216
if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1217
return FALSE;
1218
}
1219
/* notreached */
1220
return FALSE;
1221
}
1222
1223
RETURN_CODE WCMD_create_dir(WCHAR *args)
1224
{
1225
int argno = 0;
1226
WCHAR *argN = args;
1227
RETURN_CODE return_code;
1228
1229
if (param1[0] == L'\0')
1230
{
1231
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1232
return errorlevel = ERROR_INVALID_FUNCTION;
1233
}
1234
return_code = NO_ERROR;
1235
/* Loop through all args */
1236
for (;;)
1237
{
1238
WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1239
if (!argN) break;
1240
if (!create_full_path(thisArg))
1241
{
1242
WCMD_print_error();
1243
return_code = ERROR_INVALID_FUNCTION;
1244
}
1245
}
1246
return errorlevel = return_code;
1247
}
1248
1249
/* Parse the /A options given by the user on the commandline
1250
* into a bitmask of wanted attributes (*wantSet),
1251
* and a bitmask of unwanted attributes (*wantClear).
1252
*/
1253
static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1254
WCHAR *p;
1255
1256
/* both are strictly 'out' parameters */
1257
*wantSet=0;
1258
*wantClear=0;
1259
1260
/* For each /A argument */
1261
for (p=wcsstr(quals, L"/A"); p != NULL; p=wcsstr(p, L"/A")) {
1262
/* Skip /A itself */
1263
p += 2;
1264
1265
/* Skip optional : */
1266
if (*p == ':') p++;
1267
1268
/* For each of the attribute specifier chars to this /A option */
1269
for (; *p != 0 && *p != '/'; p++) {
1270
BOOL negate = FALSE;
1271
DWORD mask = 0;
1272
1273
if (*p == '-') {
1274
negate=TRUE;
1275
p++;
1276
}
1277
1278
/* Convert the attribute specifier to a bit in one of the masks */
1279
switch (*p) {
1280
case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1281
case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1282
case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1283
case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1284
default:
1285
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1286
}
1287
if (negate)
1288
*wantClear |= mask;
1289
else
1290
*wantSet |= mask;
1291
}
1292
}
1293
}
1294
1295
/* If filename part of parameter is * or *.*,
1296
* and neither /Q nor /P options were given,
1297
* prompt the user whether to proceed.
1298
* Returns FALSE if user says no, TRUE otherwise.
1299
* *pPrompted is set to TRUE if the user is prompted.
1300
* (If /P supplied, del will prompt for individual files later.)
1301
*/
1302
static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1303
if ((wcsstr(quals, L"/Q") == NULL) && (wcsstr(quals, L"/P") == NULL)) {
1304
WCHAR drive[10];
1305
WCHAR dir[MAX_PATH];
1306
WCHAR fname[MAX_PATH];
1307
WCHAR ext[MAX_PATH];
1308
WCHAR fpath[MAX_PATH];
1309
1310
/* Convert path into actual directory spec */
1311
if (!WCMD_get_fullpath(filename, ARRAY_SIZE(fpath), fpath, NULL)) return FALSE;
1312
_wsplitpath(fpath, drive, dir, fname, ext);
1313
1314
/* Only prompt for * and *.*, not *a, a*, *.a* etc */
1315
if ((lstrcmpW(fname, L"*") == 0) && (*ext == 0x00 || (lstrcmpW(ext, L".*") == 0))) {
1316
1317
WCHAR question[MAXSTRING];
1318
1319
/* Caller uses this to suppress "file not found" warning later */
1320
*pPrompted = TRUE;
1321
1322
/* Ask for confirmation */
1323
wsprintfW(question, L"%s ", fpath);
1324
return WCMD_ask_confirm(question, TRUE, NULL);
1325
}
1326
}
1327
/* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1328
return TRUE;
1329
}
1330
1331
/* Helper function for WCMD_delete().
1332
* Deletes a single file, directory, or wildcard.
1333
* If /S was given, does it recursively.
1334
* Returns TRUE if a file was deleted.
1335
*/
1336
static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1337
DWORD wanted_attrs;
1338
DWORD unwanted_attrs;
1339
BOOL found = FALSE;
1340
WCHAR argCopy[MAX_PATH];
1341
WIN32_FIND_DATAW fd;
1342
HANDLE hff;
1343
WCHAR fpath[MAX_PATH];
1344
WCHAR *p;
1345
BOOL handleParm = TRUE;
1346
1347
WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1348
1349
lstrcpyW(argCopy, thisArg);
1350
WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1351
wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1352
1353
if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1354
/* Skip this arg if user declines to delete *.* */
1355
return FALSE;
1356
}
1357
1358
/* First, try to delete in the current directory */
1359
hff = FindFirstFileW(argCopy, &fd);
1360
if (hff == INVALID_HANDLE_VALUE) {
1361
handleParm = FALSE;
1362
found = wcschr(argCopy,'*') != NULL || wcschr(argCopy,'?') != NULL;
1363
} else {
1364
found = TRUE;
1365
}
1366
1367
/* Support del <dirname> by just deleting all files dirname\* */
1368
if (handleParm
1369
&& (wcschr(argCopy,'*') == NULL)
1370
&& (wcschr(argCopy,'?') == NULL)
1371
&& (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1372
{
1373
WCHAR modifiedParm[MAX_PATH];
1374
1375
lstrcpyW(modifiedParm, argCopy);
1376
lstrcatW(modifiedParm, L"\\*");
1377
FindClose(hff);
1378
found = TRUE;
1379
WCMD_delete_one(modifiedParm);
1380
1381
} else if (handleParm) {
1382
1383
/* Build the filename to delete as <supplied directory>\<findfirst filename> */
1384
lstrcpyW (fpath, argCopy);
1385
do {
1386
p = wcsrchr (fpath, '\\');
1387
if (p != NULL) {
1388
*++p = '\0';
1389
lstrcatW (fpath, fd.cFileName);
1390
}
1391
else lstrcpyW (fpath, fd.cFileName);
1392
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1393
BOOL ok;
1394
1395
/* Handle attribute matching (/A) */
1396
ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1397
&& ((fd.dwFileAttributes & unwanted_attrs) == 0);
1398
1399
/* /P means prompt for each file */
1400
if (ok && wcsstr(quals, L"/P") != NULL) {
1401
WCHAR* question;
1402
1403
/* Ask for confirmation */
1404
question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1405
ok = WCMD_ask_confirm(question, FALSE, NULL);
1406
LocalFree(question);
1407
}
1408
1409
/* Only proceed if ok to */
1410
if (ok) {
1411
1412
/* If file is read only, and /A:r or /F supplied, delete it */
1413
if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1414
((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1415
wcsstr(quals, L"/F") != NULL)) {
1416
SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1417
}
1418
1419
/* Now do the delete */
1420
if (!DeleteFileW(fpath)) WCMD_print_error ();
1421
}
1422
1423
}
1424
} while (FindNextFileW(hff, &fd) != 0);
1425
FindClose (hff);
1426
}
1427
1428
/* Now recurse into all subdirectories handling the parameter in the same way */
1429
if (wcsstr(quals, L"/S") != NULL) {
1430
1431
WCHAR thisDir[MAX_PATH];
1432
int cPos;
1433
1434
WCHAR drive[10];
1435
WCHAR dir[MAX_PATH];
1436
WCHAR fname[MAX_PATH];
1437
WCHAR ext[MAX_PATH];
1438
1439
/* Convert path into actual directory spec */
1440
if (!WCMD_get_fullpath(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL)) return FALSE;
1441
1442
_wsplitpath(thisDir, drive, dir, fname, ext);
1443
1444
lstrcpyW(thisDir, drive);
1445
lstrcatW(thisDir, dir);
1446
cPos = lstrlenW(thisDir);
1447
1448
WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1449
1450
/* Append '*' to the directory */
1451
thisDir[cPos] = '*';
1452
thisDir[cPos+1] = 0x00;
1453
1454
hff = FindFirstFileW(thisDir, &fd);
1455
1456
/* Remove residual '*' */
1457
thisDir[cPos] = 0x00;
1458
1459
if (hff != INVALID_HANDLE_VALUE) {
1460
DIRECTORY_STACK *allDirs = NULL;
1461
DIRECTORY_STACK *lastEntry = NULL;
1462
1463
do {
1464
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1465
(lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
1466
1467
DIRECTORY_STACK *nextDir;
1468
WCHAR subParm[MAX_PATH];
1469
1470
if (wcslen(thisDir) + wcslen(fd.cFileName) + 1 + wcslen(fname) + wcslen(ext) >= MAX_PATH)
1471
{
1472
WINE_TRACE("Skipping path too long %s%s\\%s%s\n",
1473
debugstr_w(thisDir), debugstr_w(fd.cFileName),
1474
debugstr_w(fname), debugstr_w(ext));
1475
continue;
1476
}
1477
/* Work out search parameter in sub dir */
1478
lstrcpyW (subParm, thisDir);
1479
lstrcatW (subParm, fd.cFileName);
1480
lstrcatW (subParm, L"\\");
1481
lstrcatW (subParm, fname);
1482
lstrcatW (subParm, ext);
1483
WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1484
1485
/* Allocate memory, add to list */
1486
nextDir = WCMD_dir_stack_create(subParm, NULL);
1487
if (allDirs == NULL) allDirs = nextDir;
1488
if (lastEntry != NULL) lastEntry->next = nextDir;
1489
lastEntry = nextDir;
1490
}
1491
} while (FindNextFileW(hff, &fd) != 0);
1492
FindClose (hff);
1493
1494
/* Go through each subdir doing the delete */
1495
while (allDirs != NULL) {
1496
found |= WCMD_delete_one (allDirs->dirName);
1497
allDirs = WCMD_dir_stack_free(allDirs);
1498
}
1499
}
1500
}
1501
1502
return found;
1503
}
1504
1505
/****************************************************************************
1506
* WCMD_delete
1507
*
1508
* Delete a file or wildcarded set.
1509
*
1510
* Note on /A:
1511
* - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1512
* - Each set is a pattern, eg /ahr /as-r means
1513
* readonly+hidden OR nonreadonly system files
1514
* - The '-' applies to a single field, ie /a:-hr means read only
1515
* non-hidden files
1516
*/
1517
1518
RETURN_CODE WCMD_delete(WCHAR *args)
1519
{
1520
int argno;
1521
WCHAR *argN;
1522
BOOL argsProcessed = FALSE;
1523
1524
errorlevel = NO_ERROR;
1525
1526
for (argno = 0; ; argno++)
1527
{
1528
WCHAR *thisArg;
1529
1530
argN = NULL;
1531
thisArg = WCMD_parameter(args, argno, &argN, FALSE, FALSE);
1532
if (!argN)
1533
break; /* no more parameters */
1534
if (argN[0] == '/')
1535
continue; /* skip options */
1536
1537
argsProcessed = TRUE;
1538
if (!WCMD_delete_one(thisArg))
1539
{
1540
errorlevel = ERROR_INVALID_FUNCTION;
1541
}
1542
}
1543
1544
/* Handle no valid args */
1545
if (!argsProcessed)
1546
{
1547
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1548
errorlevel = ERROR_INVALID_FUNCTION;
1549
}
1550
1551
return errorlevel;
1552
}
1553
1554
/****************************************************************************
1555
* WCMD_echo
1556
*
1557
* Echo input to the screen (or not). We don't try to emulate the bugs
1558
* in DOS (try typing "ECHO ON AGAIN" for an example).
1559
*/
1560
RETURN_CODE WCMD_echo(const WCHAR *args)
1561
{
1562
const WCHAR *toskip = L".:;/(";
1563
const WCHAR *skipped = NULL;
1564
WCHAR *trimmed;
1565
1566
if (iswspace(args[0]) || (args[0] && (skipped = wcschr(toskip, args[0])))) args++;
1567
1568
trimmed = WCMD_skip_leading_spaces((WCHAR *)args);
1569
1570
if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, trimmed, 2, L"ON", 2) == CSTR_EQUAL &&
1571
*WCMD_skip_leading_spaces(trimmed + 2) == L'\0')
1572
echo_mode = TRUE;
1573
else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, trimmed, 3, L"OFF", 3) == CSTR_EQUAL &&
1574
*WCMD_skip_leading_spaces(trimmed + 3) == L'\0')
1575
echo_mode = FALSE;
1576
else if (!trimmed[0] && !skipped)
1577
WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), echo_mode ? L"ON" : L"OFF");
1578
else
1579
{
1580
WCMD_output_asis(args);
1581
WCMD_output_asis(L"\r\n");
1582
}
1583
return NO_ERROR;
1584
}
1585
1586
/*****************************************************************************
1587
* WCMD_add_dirstowalk
1588
*
1589
* When recursing through directories (for /r), we need to add to the list of
1590
* directories still to walk, any subdirectories of the one we are processing.
1591
*
1592
* Parameters
1593
* options [I] The remaining list of directories still to process
1594
*
1595
* Note this routine inserts the subdirectories found between the entry being
1596
* processed, and any other directory still to be processed, mimicking what
1597
* Windows does
1598
*/
1599
void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk)
1600
{
1601
DIRECTORY_STACK *remainingDirs = dirsToWalk;
1602
WCHAR fullitem[MAX_PATH];
1603
WIN32_FIND_DATAW fd;
1604
HANDLE hff;
1605
1606
/* Build a generic search and add all directories on the list of directories
1607
still to walk */
1608
lstrcpyW(fullitem, dirsToWalk->dirName);
1609
lstrcatW(fullitem, L"\\*");
1610
if ((hff = FindFirstFileW(fullitem, &fd)) == INVALID_HANDLE_VALUE) return;
1611
1612
do
1613
{
1614
TRACE("Looking for subdirectories\n");
1615
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1616
(lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
1617
{
1618
/* Allocate memory, add to list */
1619
DIRECTORY_STACK *toWalk;
1620
if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH)
1621
{
1622
TRACE("Skipping too long path %s\\%s\n",
1623
debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName));
1624
continue;
1625
}
1626
toWalk = WCMD_dir_stack_create(dirsToWalk->dirName, fd.cFileName);
1627
TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1628
toWalk->next = remainingDirs->next;
1629
remainingDirs->next = toWalk;
1630
remainingDirs = toWalk;
1631
TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1632
toWalk, toWalk->next);
1633
}
1634
} while (FindNextFileW(hff, &fd) != 0);
1635
TRACE("Finished adding all subdirectories\n");
1636
FindClose(hff);
1637
}
1638
1639
static int find_in_array(const WCHAR array[][10], size_t sz, const WCHAR *what)
1640
{
1641
int i;
1642
1643
for (i = 0; i < sz; i++)
1644
if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1645
what, -1, array[i], -1) == CSTR_EQUAL)
1646
return i;
1647
return -1;
1648
}
1649
1650
/**************************************************************************
1651
* WCMD_give_help
1652
*
1653
* Simple on-line help. Help text is stored in the resource file.
1654
*/
1655
1656
RETURN_CODE WCMD_give_help(WCHAR *args)
1657
{
1658
WCHAR *help_on = WCMD_parameter(args, 0, NULL, FALSE, FALSE);
1659
1660
/* yes, return code / errorlevel look inverted, but native does it this way */
1661
if (!*help_on)
1662
WCMD_output_asis(WCMD_LoadMessage(WCMD_ALLHELP));
1663
else
1664
{
1665
int i;
1666
/* Display help message for builtin commands */
1667
if ((i = find_in_array(inbuilt, ARRAY_SIZE(inbuilt), help_on)) >= 0)
1668
WCMD_output_asis(WCMD_LoadMessage(i));
1669
else if ((i = find_in_array(externals, ARRAY_SIZE(externals), help_on)) >= 0)
1670
{
1671
WCHAR cmd[128];
1672
lstrcpyW(cmd, help_on);
1673
lstrcatW(cmd, L" /?");
1674
WCMD_run_builtin_command(WCMD_HELP, cmd);
1675
}
1676
else
1677
{
1678
WCMD_output(WCMD_LoadMessage(WCMD_NOCMDHELP), help_on);
1679
return errorlevel = NO_ERROR;
1680
}
1681
}
1682
return errorlevel = ERROR_INVALID_FUNCTION;
1683
}
1684
1685
/****************************************************************************
1686
* WCMD_goto
1687
*
1688
* Batch file jump instruction. Not the most efficient algorithm ;-)
1689
* Prints error message if the specified label cannot be found - the file pointer is
1690
* then at EOF, effectively stopping the batch file.
1691
* FIXME: DOS is supposed to allow labels with spaces - we don't.
1692
*/
1693
1694
RETURN_CODE WCMD_goto(void)
1695
{
1696
if (context != NULL)
1697
{
1698
WCHAR *paramStart = param1;
1699
HANDLE h;
1700
BOOL ret;
1701
1702
if (!param1[0])
1703
{
1704
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1705
return ERROR_INVALID_FUNCTION;
1706
}
1707
1708
if (!context->batch_file) return ERROR_INVALID_FUNCTION;
1709
/* Handle special :EOF label */
1710
if (lstrcmpiW(L":eof", param1) == 0)
1711
{
1712
context->file_position.QuadPart = WCMD_FILE_POSITION_EOF;
1713
return RETURN_CODE_GOTO;
1714
}
1715
h = CreateFileW(context->batch_file->path_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
1716
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1717
if (h == INVALID_HANDLE_VALUE)
1718
{
1719
SetLastError(ERROR_FILE_NOT_FOUND);
1720
WCMD_print_error();
1721
return ERROR_INVALID_FUNCTION;
1722
}
1723
1724
/* Support goto :label as well as goto label plus remove trailing chars */
1725
if (*paramStart == ':') paramStart++;
1726
WCMD_set_label_end(paramStart);
1727
TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
1728
1729
ret = WCMD_find_label(h, paramStart, &context->file_position);
1730
CloseHandle(h);
1731
if (ret) return RETURN_CODE_GOTO;
1732
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1733
context->file_position.QuadPart = WCMD_FILE_POSITION_EOF;
1734
}
1735
return ERROR_INVALID_FUNCTION;
1736
}
1737
1738
/*****************************************************************************
1739
* WCMD_pushd
1740
*
1741
* Push a directory onto the stack
1742
*/
1743
1744
RETURN_CODE WCMD_pushd(const WCHAR *args)
1745
{
1746
struct env_stack *curdir;
1747
WCHAR *thisdir;
1748
RETURN_CODE return_code;
1749
1750
if (!*args)
1751
return errorlevel = NO_ERROR;
1752
1753
if (wcschr(args, '/') != NULL) {
1754
SetLastError(ERROR_INVALID_PARAMETER);
1755
WCMD_print_error();
1756
return errorlevel = ERROR_INVALID_FUNCTION;
1757
}
1758
1759
curdir = xalloc(sizeof(struct env_stack));
1760
thisdir = xalloc(1024 * sizeof(WCHAR));
1761
1762
/* Change directory using CD code with /D parameter */
1763
lstrcpyW(quals, L"/D");
1764
GetCurrentDirectoryW (1024, thisdir);
1765
1766
return_code = WCMD_setshow_default(args);
1767
if (return_code != NO_ERROR)
1768
{
1769
free(curdir);
1770
free(thisdir);
1771
return errorlevel = ERROR_INVALID_FUNCTION;
1772
} else {
1773
curdir -> next = pushd_directories;
1774
curdir -> strings = thisdir;
1775
if (pushd_directories == NULL) {
1776
curdir -> u.stackdepth = 1;
1777
} else {
1778
curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1779
}
1780
pushd_directories = curdir;
1781
}
1782
return errorlevel = return_code;
1783
}
1784
1785
1786
/*****************************************************************************
1787
* WCMD_popd
1788
*
1789
* Pop a directory from the stack
1790
*/
1791
1792
RETURN_CODE WCMD_popd(void)
1793
{
1794
struct env_stack *temp = pushd_directories;
1795
1796
if (!pushd_directories)
1797
return ERROR_INVALID_FUNCTION;
1798
1799
/* pop the old environment from the stack, and make it the current dir */
1800
pushd_directories = temp->next;
1801
SetCurrentDirectoryW(temp->strings);
1802
free(temp->strings);
1803
free(temp);
1804
return NO_ERROR;
1805
}
1806
1807
/****************************************************************************
1808
* WCMD_move
1809
*
1810
* Move a file, directory tree or wildcarded set of files.
1811
*/
1812
1813
RETURN_CODE WCMD_move(void)
1814
{
1815
WIN32_FIND_DATAW fd;
1816
HANDLE hff;
1817
WCHAR input[MAX_PATH];
1818
WCHAR output[MAX_PATH];
1819
WCHAR drive[10];
1820
WCHAR dir[MAX_PATH];
1821
WCHAR fname[MAX_PATH];
1822
WCHAR ext[MAX_PATH];
1823
1824
if (param1[0] == 0x00) {
1825
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1826
return errorlevel = ERROR_INVALID_FUNCTION;
1827
}
1828
1829
/* If no destination supplied, assume current directory */
1830
if (param2[0] == 0x00) {
1831
lstrcpyW(param2, L".");
1832
}
1833
1834
/* If 2nd parm is directory, then use original filename */
1835
/* Convert partial path to full path */
1836
if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL) ||
1837
!WCMD_get_fullpath(param2, ARRAY_SIZE(output), output, NULL))
1838
return errorlevel = ERROR_INVALID_FUNCTION;
1839
WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1840
wine_dbgstr_w(param1), wine_dbgstr_w(output));
1841
1842
/* Split into components */
1843
_wsplitpath(input, drive, dir, fname, ext);
1844
1845
hff = FindFirstFileW(input, &fd);
1846
if (hff == INVALID_HANDLE_VALUE)
1847
return errorlevel = ERROR_INVALID_FUNCTION;
1848
1849
errorlevel = NO_ERROR;
1850
do {
1851
WCHAR dest[MAX_PATH];
1852
WCHAR src[MAX_PATH];
1853
DWORD attribs;
1854
BOOL ok = TRUE;
1855
DWORD flags = 0;
1856
1857
WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1858
1859
/* Build src & dest name */
1860
lstrcpyW(src, drive);
1861
lstrcatW(src, dir);
1862
1863
/* See if dest is an existing directory */
1864
attribs = GetFileAttributesW(output);
1865
if (attribs != INVALID_FILE_ATTRIBUTES &&
1866
(attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1867
lstrcpyW(dest, output);
1868
lstrcatW(dest, L"\\");
1869
lstrcatW(dest, fd.cFileName);
1870
} else {
1871
lstrcpyW(dest, output);
1872
}
1873
1874
lstrcatW(src, fd.cFileName);
1875
1876
WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1877
WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1878
1879
/* If destination exists, prompt unless /Y supplied */
1880
if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1881
BOOL force = FALSE;
1882
WCHAR copycmd[MAXSTRING];
1883
DWORD len;
1884
1885
/* Default whether automatic overwriting is on. If we are interactive then
1886
we prompt by default, otherwise we overwrite by default
1887
/-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1888
if (wcsstr(quals, L"/-Y"))
1889
force = FALSE;
1890
else if (wcsstr(quals, L"/Y"))
1891
force = TRUE;
1892
else {
1893
/* By default, we will force the overwrite in batch mode and ask for
1894
* confirmation in interactive mode. */
1895
force = !!context;
1896
/* If COPYCMD is set, then we force the overwrite with /Y and ask for
1897
* confirmation with /-Y. If COPYCMD is neither of those, then we use the
1898
* default behavior. */
1899
len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
1900
if (len && len < ARRAY_SIZE(copycmd)) {
1901
if (!lstrcmpiW(copycmd, L"/Y"))
1902
force = TRUE;
1903
else if (!lstrcmpiW(copycmd, L"/-Y"))
1904
force = FALSE;
1905
}
1906
}
1907
1908
/* Prompt if overwriting */
1909
if (!force) {
1910
WCHAR* question;
1911
1912
/* Ask for confirmation */
1913
question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1914
ok = WCMD_ask_confirm(question, FALSE, NULL);
1915
LocalFree(question);
1916
}
1917
1918
if (ok)
1919
flags |= MOVEFILE_REPLACE_EXISTING;
1920
}
1921
1922
if (!ok || !MoveFileExW(src, dest, flags))
1923
{
1924
if (!ok) WCMD_print_error();
1925
errorlevel = ERROR_INVALID_FUNCTION;
1926
}
1927
} while (FindNextFileW(hff, &fd) != 0);
1928
1929
FindClose(hff);
1930
return errorlevel;
1931
}
1932
1933
/****************************************************************************
1934
* WCMD_pause
1935
*
1936
* Suspend execution of a batch script until a key is typed
1937
*/
1938
1939
RETURN_CODE WCMD_pause(void)
1940
{
1941
RETURN_CODE return_code = NO_ERROR;
1942
WCMD_output_asis(anykey);
1943
return_code = WCMD_wait_for_input(GetStdHandle(STD_INPUT_HANDLE));
1944
WCMD_output_asis(L"\r\n");
1945
1946
return return_code;
1947
}
1948
1949
/****************************************************************************
1950
* WCMD_remove_dir
1951
*
1952
* Delete a directory.
1953
*/
1954
1955
RETURN_CODE WCMD_remove_dir(WCHAR *args)
1956
{
1957
int argno = 0;
1958
int argsProcessed = 0;
1959
WCHAR *argN = args;
1960
1961
/* Loop through all args */
1962
while (argN) {
1963
WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
1964
if (argN && argN[0] != '/') {
1965
WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1966
wine_dbgstr_w(quals));
1967
argsProcessed++;
1968
1969
/* If subdirectory search not supplied, just try to remove
1970
and report error if it fails (eg if it contains a file) */
1971
if (wcsstr(quals, L"/S") == NULL) {
1972
if (!RemoveDirectoryW(thisArg))
1973
{
1974
RETURN_CODE return_code = GetLastError();
1975
WCMD_print_error();
1976
return return_code;
1977
}
1978
1979
/* Otherwise use ShFileOp to recursively remove a directory */
1980
} else {
1981
1982
SHFILEOPSTRUCTW lpDir;
1983
1984
/* Ask first */
1985
if (wcsstr(quals, L"/Q") == NULL) {
1986
BOOL ok;
1987
WCHAR question[MAXSTRING];
1988
1989
/* Ask for confirmation */
1990
wsprintfW(question, L"%s ", thisArg);
1991
ok = WCMD_ask_confirm(question, TRUE, NULL);
1992
1993
/* Abort if answer is 'N' */
1994
if (!ok) return ERROR_INVALID_FUNCTION;
1995
}
1996
1997
/* Do the delete */
1998
lpDir.hwnd = NULL;
1999
lpDir.pTo = NULL;
2000
lpDir.pFrom = thisArg;
2001
lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2002
lpDir.wFunc = FO_DELETE;
2003
2004
/* SHFileOperationW needs file list with a double null termination */
2005
thisArg[lstrlenW(thisArg) + 1] = 0x00;
2006
2007
if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2008
}
2009
}
2010
}
2011
2012
/* Handle no valid args */
2013
if (argsProcessed == 0) {
2014
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2015
return ERROR_INVALID_FUNCTION;
2016
}
2017
return NO_ERROR;
2018
}
2019
2020
/****************************************************************************
2021
* WCMD_rename
2022
*
2023
* Rename a file.
2024
*/
2025
2026
RETURN_CODE WCMD_rename(void)
2027
{
2028
HANDLE hff;
2029
WIN32_FIND_DATAW fd;
2030
WCHAR input[MAX_PATH];
2031
WCHAR *dotDst = NULL;
2032
WCHAR drive[10];
2033
WCHAR dir[MAX_PATH];
2034
WCHAR fname[MAX_PATH];
2035
WCHAR ext[MAX_PATH];
2036
2037
errorlevel = NO_ERROR;
2038
2039
/* Must be at least two args */
2040
if (param1[0] == 0x00 || param2[0] == 0x00) {
2041
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2042
return errorlevel = ERROR_INVALID_FUNCTION;
2043
}
2044
2045
/* Destination cannot contain a drive letter or directory separator */
2046
if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
2047
SetLastError(ERROR_INVALID_PARAMETER);
2048
WCMD_print_error();
2049
return errorlevel = ERROR_INVALID_FUNCTION;
2050
}
2051
2052
/* Convert partial path to full path */
2053
if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL)) return errorlevel = ERROR_INVALID_FUNCTION;
2054
WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2055
wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2056
dotDst = wcschr(param2, '.');
2057
2058
/* Split into components */
2059
_wsplitpath(input, drive, dir, fname, ext);
2060
2061
hff = FindFirstFileW(input, &fd);
2062
if (hff == INVALID_HANDLE_VALUE)
2063
return errorlevel = ERROR_INVALID_FUNCTION;
2064
2065
errorlevel = NO_ERROR;
2066
do {
2067
WCHAR dest[MAX_PATH];
2068
WCHAR src[MAX_PATH];
2069
WCHAR *dotSrc = NULL;
2070
int dirLen;
2071
2072
WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2073
2074
/* FIXME: If dest name or extension is *, replace with filename/ext
2075
part otherwise use supplied name. This supports:
2076
ren *.fred *.jim
2077
ren jim.* fred.* etc
2078
However, windows has a more complex algorithm supporting eg
2079
?'s and *'s mid name */
2080
dotSrc = wcschr(fd.cFileName, '.');
2081
2082
/* Build src & dest name */
2083
lstrcpyW(src, drive);
2084
lstrcatW(src, dir);
2085
lstrcpyW(dest, src);
2086
dirLen = lstrlenW(src);
2087
lstrcatW(src, fd.cFileName);
2088
2089
/* Build name */
2090
if (param2[0] == '*') {
2091
lstrcatW(dest, fd.cFileName);
2092
if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2093
} else {
2094
lstrcatW(dest, param2);
2095
if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2096
}
2097
2098
/* Build Extension */
2099
if (dotDst && (*(dotDst+1)=='*')) {
2100
if (dotSrc) lstrcatW(dest, dotSrc);
2101
} else if (dotDst) {
2102
lstrcatW(dest, dotDst);
2103
}
2104
2105
WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2106
WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2107
2108
if (!MoveFileW(src, dest)) {
2109
WCMD_print_error ();
2110
errorlevel = ERROR_INVALID_FUNCTION;
2111
}
2112
} while (FindNextFileW(hff, &fd) != 0);
2113
2114
FindClose(hff);
2115
return errorlevel;
2116
}
2117
2118
/*****************************************************************************
2119
* WCMD_dupenv
2120
*
2121
* Make a copy of the environment.
2122
*/
2123
static WCHAR *WCMD_dupenv( const WCHAR *env )
2124
{
2125
WCHAR *env_copy;
2126
int len;
2127
2128
if( !env )
2129
return NULL;
2130
2131
len = 0;
2132
while ( env[len] )
2133
len += lstrlenW(&env[len]) + 1;
2134
len++;
2135
2136
env_copy = xalloc(len * sizeof (WCHAR));
2137
memcpy(env_copy, env, len*sizeof (WCHAR));
2138
2139
return env_copy;
2140
}
2141
2142
/*****************************************************************************
2143
* WCMD_setlocal
2144
*
2145
* setlocal pushes the environment onto a stack
2146
* Save the environment as unicode so we don't screw anything up.
2147
*/
2148
RETURN_CODE WCMD_setlocal(WCHAR *args)
2149
{
2150
WCHAR *env;
2151
struct env_stack *env_copy;
2152
WCHAR cwd[MAX_PATH];
2153
BOOL newdelay;
2154
int argno = 0;
2155
WCHAR *argN = args;
2156
2157
/* setlocal does nothing outside of batch programs */
2158
if (!WCMD_is_in_context(NULL))
2159
return NO_ERROR;
2160
newdelay = delayedsubst;
2161
while (argN)
2162
{
2163
WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2164
if (!thisArg || !*thisArg) break;
2165
if (!wcsicmp(thisArg, L"ENABLEDELAYEDEXPANSION"))
2166
newdelay = TRUE;
2167
else if (!wcsicmp(thisArg, L"DISABLEDELAYEDEXPANSION"))
2168
newdelay = FALSE;
2169
/* ENABLE/DISABLE EXTENSIONS ignored for now */
2170
else if (!wcsicmp(thisArg, L"ENABLEEXTENSIONS") || !wcsicmp(thisArg, L"DISABLEEXTENSIONS"))
2171
{}
2172
else
2173
return errorlevel = ERROR_INVALID_FUNCTION;
2174
TRACE("Setting delayed expansion to %d\n", newdelay);
2175
}
2176
2177
env_copy = xalloc( sizeof(struct env_stack));
2178
2179
env = GetEnvironmentStringsW ();
2180
env_copy->strings = WCMD_dupenv (env);
2181
if (env_copy->strings)
2182
{
2183
env_copy->context = context;
2184
env_copy->next = saved_environment;
2185
env_copy->delayedsubst = delayedsubst;
2186
delayedsubst = newdelay;
2187
saved_environment = env_copy;
2188
2189
/* Save the current drive letter */
2190
GetCurrentDirectoryW(MAX_PATH, cwd);
2191
env_copy->u.cwd = cwd[0];
2192
}
2193
else
2194
free(env_copy);
2195
2196
FreeEnvironmentStringsW (env);
2197
return errorlevel = NO_ERROR;
2198
}
2199
2200
/*****************************************************************************
2201
* WCMD_endlocal
2202
*
2203
* endlocal pops the environment off a stack
2204
* Note: When searching for '=', search from WCHAR position 1, to handle
2205
* special internal environment variables =C:, =D: etc
2206
*/
2207
RETURN_CODE WCMD_endlocal(void)
2208
{
2209
WCHAR *env, *old, *p;
2210
struct env_stack *temp;
2211
int len, n;
2212
2213
/* setlocal does nothing outside of batch programs */
2214
if (!WCMD_is_in_context(NULL)) return NO_ERROR;
2215
2216
/* setlocal needs a saved environment from within the same context (batch
2217
program) as it was saved in */
2218
if (!saved_environment || saved_environment->context != context)
2219
return ERROR_INVALID_FUNCTION;
2220
2221
/* pop the old environment from the stack */
2222
temp = saved_environment;
2223
saved_environment = temp->next;
2224
2225
/* delete the current environment, totally */
2226
env = GetEnvironmentStringsW ();
2227
old = WCMD_dupenv (env);
2228
len = 0;
2229
while (old[len]) {
2230
n = lstrlenW(&old[len]) + 1;
2231
p = wcschr(&old[len] + 1, '=');
2232
if (p)
2233
{
2234
*p++ = 0;
2235
SetEnvironmentVariableW (&old[len], NULL);
2236
}
2237
len += n;
2238
}
2239
free(old);
2240
FreeEnvironmentStringsW (env);
2241
2242
/* restore old environment */
2243
env = temp->strings;
2244
len = 0;
2245
delayedsubst = temp->delayedsubst;
2246
WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
2247
while (env[len]) {
2248
n = lstrlenW(&env[len]) + 1;
2249
p = wcschr(&env[len] + 1, '=');
2250
if (p)
2251
{
2252
*p++ = 0;
2253
SetEnvironmentVariableW (&env[len], p);
2254
}
2255
len += n;
2256
}
2257
2258
/* Restore current drive letter */
2259
if (IsCharAlphaW(temp->u.cwd)) {
2260
WCHAR envvar[4];
2261
WCHAR cwd[MAX_PATH];
2262
2263
wsprintfW(envvar, L"=%c:", temp->u.cwd);
2264
if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2265
WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2266
SetCurrentDirectoryW(cwd);
2267
}
2268
}
2269
2270
free(env);
2271
free(temp);
2272
return NO_ERROR;
2273
}
2274
2275
/*****************************************************************************
2276
* WCMD_setshow_default
2277
*
2278
* Set/Show the current default directory
2279
*/
2280
2281
RETURN_CODE WCMD_setshow_default(const WCHAR *args)
2282
{
2283
RETURN_CODE return_code;
2284
BOOL status;
2285
WCHAR string[1024];
2286
WCHAR cwd[1024];
2287
WCHAR *pos;
2288
WIN32_FIND_DATAW fd;
2289
HANDLE hff;
2290
2291
WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
2292
2293
/* Skip /D and trailing whitespace if on the front of the command line */
2294
if (lstrlenW(args) >= 2 &&
2295
CompareStringW(LOCALE_USER_DEFAULT,
2296
NORM_IGNORECASE | SORT_STRINGSORT,
2297
args, 2, L"/D", -1) == CSTR_EQUAL) {
2298
args += 2;
2299
while (*args && (*args==' ' || *args=='\t'))
2300
args++;
2301
}
2302
2303
GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
2304
return_code = NO_ERROR;
2305
2306
if (!*args) {
2307
lstrcatW(cwd, L"\r\n");
2308
WCMD_output_asis (cwd);
2309
}
2310
else {
2311
/* Remove any double quotes, which may be in the
2312
middle, eg. cd "C:\Program Files"\Microsoft is ok */
2313
pos = string;
2314
while (*args) {
2315
if (*args != '"') *pos++ = *args;
2316
args++;
2317
}
2318
while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2319
pos--;
2320
*pos = 0x00;
2321
2322
/* Search for appropriate directory */
2323
WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2324
hff = FindFirstFileW(string, &fd);
2325
if (hff != INVALID_HANDLE_VALUE) {
2326
do {
2327
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2328
WCHAR fpath[MAX_PATH];
2329
WCHAR drive[10];
2330
WCHAR dir[MAX_PATH];
2331
WCHAR fname[MAX_PATH];
2332
WCHAR ext[MAX_PATH];
2333
2334
/* Convert path into actual directory spec */
2335
if (!WCMD_get_fullpath(string, ARRAY_SIZE(fpath), fpath, NULL))
2336
return errorlevel = ERROR_INVALID_FUNCTION;
2337
2338
_wsplitpath(fpath, drive, dir, fname, ext);
2339
2340
/* Rebuild path */
2341
wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
2342
break;
2343
}
2344
} while (FindNextFileW(hff, &fd) != 0);
2345
FindClose(hff);
2346
}
2347
2348
/* Change to that directory */
2349
WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2350
2351
status = SetCurrentDirectoryW(string);
2352
if (!status) {
2353
WCMD_print_error ();
2354
return_code = ERROR_INVALID_FUNCTION;
2355
} else {
2356
2357
/* Save away the actual new directory, to store as current location */
2358
GetCurrentDirectoryW(ARRAY_SIZE(string), string);
2359
2360
/* Restore old directory if drive letter would change, and
2361
CD x:\directory /D (or pushd c:\directory) not supplied */
2362
if ((wcsstr(quals, L"/D") == NULL) &&
2363
(param1[1] == ':') && (towupper(param1[0]) != towupper(cwd[0]))) {
2364
SetCurrentDirectoryW(cwd);
2365
}
2366
}
2367
2368
/* Set special =C: type environment variable, for drive letter of
2369
change of directory, even if path was restored due to missing
2370
/D (allows changing drive letter when not resident on that
2371
drive */
2372
if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2373
WCHAR env[4];
2374
lstrcpyW(env, L"=");
2375
memcpy(env+1, string, 2 * sizeof(WCHAR));
2376
env[3] = 0x00;
2377
WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2378
SetEnvironmentVariableW(env, string);
2379
}
2380
2381
}
2382
return errorlevel = return_code;
2383
}
2384
2385
/****************************************************************************
2386
* WCMD_setshow_date
2387
*
2388
* Set/Show the system date
2389
* FIXME: Can't change date yet
2390
*/
2391
2392
RETURN_CODE WCMD_setshow_date(void)
2393
{
2394
RETURN_CODE return_code = NO_ERROR;
2395
WCHAR curdate[64], buffer[64];
2396
DWORD count;
2397
2398
if (!*param1) {
2399
if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
2400
WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2401
if (wcsstr(quals, L"/T") == NULL) {
2402
WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2403
if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count) &&
2404
count > 2) {
2405
WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2406
}
2407
}
2408
}
2409
else WCMD_print_error ();
2410
}
2411
else {
2412
return_code = ERROR_INVALID_FUNCTION;
2413
WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2414
}
2415
return errorlevel = return_code;
2416
}
2417
2418
/****************************************************************************
2419
* WCMD_compare
2420
* Note: Native displays 'fred' before 'fred ', so need to only compare up to
2421
* the equals sign.
2422
*/
2423
static int __cdecl WCMD_compare( const void *a, const void *b )
2424
{
2425
int r;
2426
const WCHAR * const *str_a = a, * const *str_b = b;
2427
r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2428
*str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
2429
if( r == CSTR_LESS_THAN ) return -1;
2430
if( r == CSTR_GREATER_THAN ) return 1;
2431
return 0;
2432
}
2433
2434
/****************************************************************************
2435
* WCMD_setshow_sortenv
2436
*
2437
* sort variables into order for display
2438
* Optionally only display those who start with a stub
2439
* returns the count displayed
2440
*/
2441
static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2442
{
2443
UINT count=0, len=0, i, displayedcount=0, stublen=0;
2444
const WCHAR **str;
2445
2446
if (stub) stublen = lstrlenW(stub);
2447
2448
/* count the number of strings, and the total length */
2449
while ( s[len] ) {
2450
len += lstrlenW(&s[len]) + 1;
2451
count++;
2452
}
2453
2454
/* add the strings to an array */
2455
str = xalloc(count * sizeof (WCHAR*) );
2456
str[0] = s;
2457
for( i=1; i<count; i++ )
2458
str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
2459
2460
/* sort the array */
2461
qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2462
2463
/* print it */
2464
for( i=0; i<count; i++ ) {
2465
if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2466
NORM_IGNORECASE | SORT_STRINGSORT,
2467
str[i], stublen, stub, -1) == CSTR_EQUAL) {
2468
/* Don't display special internal variables */
2469
if (str[i][0] != '=') {
2470
WCMD_output_asis(str[i]);
2471
WCMD_output_asis(L"\r\n");
2472
displayedcount++;
2473
}
2474
}
2475
}
2476
2477
free( str );
2478
return displayedcount;
2479
}
2480
2481
/****************************************************************************
2482
* WCMD_getprecedence
2483
* Return the precedence of a particular operator
2484
*/
2485
static int WCMD_getprecedence(const WCHAR in)
2486
{
2487
switch (in) {
2488
case '!':
2489
case '~':
2490
case OP_POSITIVE:
2491
case OP_NEGATIVE:
2492
return 8;
2493
case '*':
2494
case '/':
2495
case '%':
2496
return 7;
2497
case '+':
2498
case '-':
2499
return 6;
2500
case '<':
2501
case '>':
2502
return 5;
2503
case '&':
2504
return 4;
2505
case '^':
2506
return 3;
2507
case '|':
2508
return 2;
2509
case '=':
2510
case OP_ASSSIGNMUL:
2511
case OP_ASSSIGNDIV:
2512
case OP_ASSSIGNMOD:
2513
case OP_ASSSIGNADD:
2514
case OP_ASSSIGNSUB:
2515
case OP_ASSSIGNAND:
2516
case OP_ASSSIGNNOT:
2517
case OP_ASSSIGNOR:
2518
case OP_ASSSIGNSHL:
2519
case OP_ASSSIGNSHR:
2520
return 1;
2521
default:
2522
return 0;
2523
}
2524
}
2525
2526
/****************************************************************************
2527
* WCMD_pushnumber
2528
* Push either a number or name (environment variable) onto the supplied
2529
* stack
2530
*/
2531
static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
2532
VARSTACK *thisstack = xalloc(sizeof(VARSTACK));
2533
thisstack->isnum = (var == NULL);
2534
if (var) {
2535
thisstack->variable = var;
2536
WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
2537
} else {
2538
thisstack->value = num;
2539
WINE_TRACE("Pushed number %d\n", num);
2540
}
2541
thisstack->next = *varstack;
2542
*varstack = thisstack;
2543
}
2544
2545
/****************************************************************************
2546
* WCMD_peeknumber
2547
* Returns the value of the top number or environment variable on the stack
2548
* and leaves the item on the stack.
2549
*/
2550
static int WCMD_peeknumber(VARSTACK **varstack) {
2551
int result = 0;
2552
VARSTACK *thisvar;
2553
2554
if (varstack) {
2555
thisvar = *varstack;
2556
if (!thisvar->isnum) {
2557
WCHAR tmpstr[MAXSTRING];
2558
if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
2559
result = wcstol(tmpstr,NULL,0);
2560
}
2561
WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
2562
} else {
2563
result = thisvar->value;
2564
}
2565
}
2566
WINE_TRACE("Peeked number %d\n", result);
2567
return result;
2568
}
2569
2570
/****************************************************************************
2571
* WCMD_popnumber
2572
* Returns the value of the top number or environment variable on the stack
2573
* and removes the item from the stack.
2574
*/
2575
static int WCMD_popnumber(VARSTACK **varstack) {
2576
int result = 0;
2577
VARSTACK *thisvar;
2578
2579
if (varstack) {
2580
thisvar = *varstack;
2581
result = WCMD_peeknumber(varstack);
2582
if (!thisvar->isnum) free(thisvar->variable);
2583
*varstack = thisvar->next;
2584
free(thisvar);
2585
}
2586
WINE_TRACE("Popped number %d\n", result);
2587
return result;
2588
}
2589
2590
/****************************************************************************
2591
* WCMD_pushoperator
2592
* Push an operator onto the supplied stack
2593
*/
2594
static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
2595
OPSTACK *thisstack = xalloc(sizeof(OPSTACK));
2596
thisstack->precedence = precedence;
2597
thisstack->op = op;
2598
thisstack->next = *opstack;
2599
WINE_TRACE("Pushed operator %c\n", op);
2600
*opstack = thisstack;
2601
}
2602
2603
/****************************************************************************
2604
* WCMD_popoperator
2605
* Returns the operator from the top of the stack and removes the item from
2606
* the stack.
2607
*/
2608
static WCHAR WCMD_popoperator(OPSTACK **opstack) {
2609
WCHAR result = 0;
2610
OPSTACK *thisop;
2611
2612
if (opstack) {
2613
thisop = *opstack;
2614
result = thisop->op;
2615
*opstack = thisop->next;
2616
free(thisop);
2617
}
2618
WINE_TRACE("Popped operator %c\n", result);
2619
return result;
2620
}
2621
2622
/****************************************************************************
2623
* WCMD_reduce
2624
* Actions the top operator on the stack against the first and sometimes
2625
* second value on the variable stack, and pushes the result
2626
* Returns non-zero on error.
2627
*/
2628
static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
2629
WCHAR thisop;
2630
int var1,var2;
2631
int rc = 0;
2632
2633
if (!*opstack || !*varstack) {
2634
WINE_TRACE("No operators for the reduce\n");
2635
return WCMD_NOOPERATOR;
2636
}
2637
2638
/* Remove the top operator */
2639
thisop = WCMD_popoperator(opstack);
2640
WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
2641
2642
/* One variable operators */
2643
var1 = WCMD_popnumber(varstack);
2644
switch (thisop) {
2645
case '!': WCMD_pushnumber(NULL, !var1, varstack);
2646
break;
2647
case '~': WCMD_pushnumber(NULL, ~var1, varstack);
2648
break;
2649
case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
2650
break;
2651
case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
2652
break;
2653
}
2654
2655
/* Two variable operators */
2656
if (!*varstack) {
2657
WINE_TRACE("No operands left for the reduce?\n");
2658
return WCMD_NOOPERAND;
2659
}
2660
switch (thisop) {
2661
case '!':
2662
case '~':
2663
case OP_POSITIVE:
2664
case OP_NEGATIVE:
2665
break; /* Handled above */
2666
case '*': var2 = WCMD_popnumber(varstack);
2667
WCMD_pushnumber(NULL, var2*var1, varstack);
2668
break;
2669
case '/': var2 = WCMD_popnumber(varstack);
2670
if (var1 == 0) return WCMD_DIVIDEBYZERO;
2671
WCMD_pushnumber(NULL, var2/var1, varstack);
2672
break;
2673
case '+': var2 = WCMD_popnumber(varstack);
2674
WCMD_pushnumber(NULL, var2+var1, varstack);
2675
break;
2676
case '-': var2 = WCMD_popnumber(varstack);
2677
WCMD_pushnumber(NULL, var2-var1, varstack);
2678
break;
2679
case '&': var2 = WCMD_popnumber(varstack);
2680
WCMD_pushnumber(NULL, var2&var1, varstack);
2681
break;
2682
case '%': var2 = WCMD_popnumber(varstack);
2683
if (var1 == 0) return WCMD_DIVIDEBYZERO;
2684
WCMD_pushnumber(NULL, var2%var1, varstack);
2685
break;
2686
case '^': var2 = WCMD_popnumber(varstack);
2687
WCMD_pushnumber(NULL, var2^var1, varstack);
2688
break;
2689
case '<': var2 = WCMD_popnumber(varstack);
2690
/* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
2691
which differs from the compiler (for example gcc) so being explicit. */
2692
if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
2693
WCMD_pushnumber(NULL, 0, varstack);
2694
} else {
2695
WCMD_pushnumber(NULL, var2<<var1, varstack);
2696
}
2697
break;
2698
case '>': var2 = WCMD_popnumber(varstack);
2699
WCMD_pushnumber(NULL, var2>>var1, varstack);
2700
break;
2701
case '|': var2 = WCMD_popnumber(varstack);
2702
WCMD_pushnumber(NULL, var2|var1, varstack);
2703
break;
2704
2705
case OP_ASSSIGNMUL:
2706
case OP_ASSSIGNDIV:
2707
case OP_ASSSIGNMOD:
2708
case OP_ASSSIGNADD:
2709
case OP_ASSSIGNSUB:
2710
case OP_ASSSIGNAND:
2711
case OP_ASSSIGNNOT:
2712
case OP_ASSSIGNOR:
2713
case OP_ASSSIGNSHL:
2714
case OP_ASSSIGNSHR:
2715
{
2716
int i = 0;
2717
2718
/* The left of an equals must be one variable */
2719
if (!(*varstack) || (*varstack)->isnum) {
2720
return WCMD_NOOPERAND;
2721
}
2722
2723
/* Make the number stack grow by inserting the value of the variable */
2724
var2 = WCMD_peeknumber(varstack);
2725
WCMD_pushnumber(NULL, var2, varstack);
2726
WCMD_pushnumber(NULL, var1, varstack);
2727
2728
/* Make the operand stack grow by pushing the assign operator plus the
2729
operator to perform */
2730
while (calcassignments[i].op != ' ' &&
2731
calcassignments[i].calculatedop != thisop) {
2732
i++;
2733
}
2734
if (calcassignments[i].calculatedop == ' ') {
2735
WINE_ERR("Unexpected operator %c\n", thisop);
2736
return WCMD_NOOPERATOR;
2737
}
2738
WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
2739
WCMD_pushoperator(calcassignments[i].op,
2740
WCMD_getprecedence(calcassignments[i].op), opstack);
2741
break;
2742
}
2743
2744
case '=':
2745
{
2746
WCHAR result[MAXSTRING];
2747
2748
/* Build the result, then push it onto the stack */
2749
swprintf(result, ARRAY_SIZE(result), L"%d", var1);
2750
WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
2751
wine_dbgstr_w(result));
2752
SetEnvironmentVariableW((*varstack)->variable, result);
2753
var2 = WCMD_popnumber(varstack);
2754
WCMD_pushnumber(NULL, var1, varstack);
2755
break;
2756
}
2757
2758
default: WINE_ERR("Unrecognized operator %c\n", thisop);
2759
}
2760
2761
return rc;
2762
}
2763
2764
2765
/****************************************************************************
2766
* WCMD_handleExpression
2767
* Handles an expression provided to set /a - If it finds brackets, it uses
2768
* recursion to process the parts in brackets.
2769
*/
2770
static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
2771
{
2772
static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
2773
int rc = 0;
2774
WCHAR *pos;
2775
BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
2776
OPSTACK *opstackhead = NULL;
2777
VARSTACK *varstackhead = NULL;
2778
WCHAR foundhalf = 0;
2779
2780
/* Initialize */
2781
WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
2782
pos = *expr;
2783
2784
/* Iterate through until whole expression is processed */
2785
while (pos && *pos) {
2786
BOOL treatasnumber;
2787
2788
/* Skip whitespace to get to the next character to process*/
2789
while (*pos && (*pos==' ' || *pos=='\t')) pos++;
2790
if (!*pos) goto exprreturn;
2791
2792
/* If we have found anything other than an operator then it's a number/variable */
2793
if (wcschr(mathDelims, *pos) == NULL) {
2794
WCHAR *parmstart, *parm, *dupparm;
2795
WCHAR *nextpos;
2796
2797
/* Cannot have an expression with var/number twice, without an operator
2798
in-between, nor or number following a half constructed << or >> operator */
2799
if (lastwasnumber || foundhalf) {
2800
rc = WCMD_NOOPERATOR;
2801
goto exprerrorreturn;
2802
}
2803
lastwasnumber = TRUE;
2804
2805
if (iswdigit(*pos)) {
2806
/* For a number - just push it onto the stack */
2807
int num = wcstoul(pos, &nextpos, 0);
2808
WCMD_pushnumber(NULL, num, &varstackhead);
2809
pos = nextpos;
2810
2811
/* Verify the number was validly formed */
2812
if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
2813
rc = WCMD_BADHEXOCT;
2814
goto exprerrorreturn;
2815
}
2816
} else {
2817
2818
/* For a variable - just push it onto the stack */
2819
parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
2820
dupparm = xstrdupW(parm);
2821
WCMD_pushnumber(dupparm, 0, &varstackhead);
2822
pos = parmstart + lstrlenW(dupparm);
2823
}
2824
continue;
2825
}
2826
2827
/* We have found an operator. Some operators are one character, some two, and the minus
2828
and plus signs need special processing as they can be either operators or just influence
2829
the parameter which follows them */
2830
if (foundhalf && (*pos != foundhalf)) {
2831
/* Badly constructed operator pair */
2832
rc = WCMD_NOOPERATOR;
2833
goto exprerrorreturn;
2834
}
2835
2836
treatasnumber = FALSE; /* We are processing an operand */
2837
switch (*pos) {
2838
2839
/* > and < are special as they are double character operators (and spaces can be between them!)
2840
If we see these for the first time, set a flag, and second time around we continue.
2841
Note these double character operators are stored as just one of the characters on the stack */
2842
case '>':
2843
case '<': if (!foundhalf) {
2844
foundhalf = *pos;
2845
pos++;
2846
break;
2847
}
2848
/* We have found the rest, so clear up the knowledge of the half completed part and
2849
drop through to normal operator processing */
2850
foundhalf = 0;
2851
/* drop through */
2852
2853
case '=': if (*pos=='=') {
2854
/* = is special cased as if the last was an operator then we may have e.g. += or
2855
*= etc which we need to handle by replacing the operator that is on the stack
2856
with a calculated assignment equivalent */
2857
if (!lastwasnumber && opstackhead) {
2858
int i = 0;
2859
while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
2860
i++;
2861
}
2862
if (calcassignments[i].op == ' ') {
2863
rc = WCMD_NOOPERAND;
2864
goto exprerrorreturn;
2865
} else {
2866
/* Remove the operator on the stack, it will be replaced with a ?= equivalent
2867
when the general operator handling happens further down. */
2868
*pos = calcassignments[i].calculatedop;
2869
WCMD_popoperator(&opstackhead);
2870
}
2871
}
2872
}
2873
/* Drop though */
2874
2875
/* + and - are slightly special as they can be a numeric prefix, if they follow an operator
2876
so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
2877
case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
2878
/* drop through */
2879
case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
2880
/* drop through */
2881
2882
/* Normal operators - push onto stack unless precedence means we have to calculate it now */
2883
case '!': /* drop through */
2884
case '~': /* drop through */
2885
case '/': /* drop through */
2886
case '%': /* drop through */
2887
case '&': /* drop through */
2888
case '^': /* drop through */
2889
case '*': /* drop through */
2890
case '|':
2891
/* General code for handling most of the operators - look at the
2892
precedence of the top item on the stack, and see if we need to
2893
action the stack before we push something else onto it. */
2894
{
2895
int precedence = WCMD_getprecedence(*pos);
2896
WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
2897
precedence, !opstackhead?-1:opstackhead->precedence);
2898
2899
/* In general, for things with the same precedence, reduce immediately
2900
except for assignments and unary operators which do not */
2901
while (!rc && opstackhead &&
2902
((opstackhead->precedence > precedence) ||
2903
((opstackhead->precedence == precedence) &&
2904
(precedence != 1) && (precedence != 8)))) {
2905
rc = WCMD_reduce(&opstackhead, &varstackhead);
2906
}
2907
if (rc) goto exprerrorreturn;
2908
WCMD_pushoperator(*pos, precedence, &opstackhead);
2909
pos++;
2910
break;
2911
}
2912
2913
/* comma means start a new expression, ie calculate what we have */
2914
case ',':
2915
{
2916
int prevresult = -1;
2917
WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
2918
while (!rc && opstackhead) {
2919
rc = WCMD_reduce(&opstackhead, &varstackhead);
2920
}
2921
if (rc) goto exprerrorreturn;
2922
/* If we have anything other than one number left, error
2923
otherwise throw the number away */
2924
if (!varstackhead || varstackhead->next) {
2925
rc = WCMD_NOOPERATOR;
2926
goto exprerrorreturn;
2927
}
2928
prevresult = WCMD_popnumber(&varstackhead);
2929
WINE_TRACE("Expression resolved to %d\n", prevresult);
2930
free(varstackhead);
2931
varstackhead = NULL;
2932
pos++;
2933
break;
2934
}
2935
2936
/* Open bracket - use iteration to parse the inner expression, then continue */
2937
case '(' : {
2938
int exprresult = 0;
2939
pos++;
2940
rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
2941
if (rc) goto exprerrorreturn;
2942
WCMD_pushnumber(NULL, exprresult, &varstackhead);
2943
break;
2944
}
2945
2946
/* Close bracket - we have finished this depth, calculate and return */
2947
case ')' : {
2948
pos++;
2949
treatasnumber = TRUE; /* Things in brackets result in a number */
2950
if (depth == 0) {
2951
rc = WCMD_BADPAREN;
2952
goto exprerrorreturn;
2953
}
2954
goto exprreturn;
2955
}
2956
2957
default:
2958
WINE_ERR("Unrecognized operator %c\n", *pos);
2959
pos++;
2960
}
2961
lastwasnumber = treatasnumber;
2962
}
2963
2964
exprreturn:
2965
*expr = pos;
2966
2967
/* We need to reduce until we have a single number (or variable) on the
2968
stack and set the return value to that */
2969
while (!rc && opstackhead) {
2970
rc = WCMD_reduce(&opstackhead, &varstackhead);
2971
}
2972
if (rc) goto exprerrorreturn;
2973
2974
/* If we have anything other than one number left, error
2975
otherwise throw the number away */
2976
if (!varstackhead || varstackhead->next) {
2977
rc = WCMD_NOOPERATOR;
2978
goto exprerrorreturn;
2979
}
2980
2981
/* Now get the number (and convert if it's just a variable name) */
2982
*ret = WCMD_popnumber(&varstackhead);
2983
2984
exprerrorreturn:
2985
/* Free all remaining memory */
2986
while (opstackhead) WCMD_popoperator(&opstackhead);
2987
while (varstackhead) WCMD_popnumber(&varstackhead);
2988
2989
WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
2990
return rc;
2991
}
2992
2993
/****************************************************************************
2994
* WCMD_setshow_env
2995
*
2996
* Set/Show the environment variables
2997
*/
2998
2999
RETURN_CODE WCMD_setshow_env(WCHAR *s)
3000
{
3001
RETURN_CODE return_code = NO_ERROR;
3002
WCHAR *p;
3003
BOOL status;
3004
WCHAR string[MAXSTRING];
3005
3006
if (!*s) {
3007
WCHAR *env = GetEnvironmentStringsW();
3008
WCMD_setshow_sortenv( env, NULL );
3009
FreeEnvironmentStringsW(env);
3010
}
3011
3012
/* See if /P supplied, and if so echo the prompt, and read in a reply */
3013
else if (CompareStringW(LOCALE_USER_DEFAULT,
3014
NORM_IGNORECASE | SORT_STRINGSORT,
3015
s, 2, L"/P", -1) == CSTR_EQUAL) {
3016
DWORD count;
3017
3018
s += 2;
3019
while (*s && (*s==' ' || *s=='\t')) s++;
3020
/* set /P "var=value"jim ignores anything after the last quote */
3021
if (*s=='\"') {
3022
WCHAR *lastquote;
3023
lastquote = WCMD_strip_quotes(s);
3024
if (lastquote) *lastquote = 0x00;
3025
WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
3026
}
3027
3028
/* If no parameter, or no '=' sign, return an error */
3029
if (!(*s) || ((p = wcschr(s, '=')) == NULL )) {
3030
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3031
return_code = ERROR_INVALID_FUNCTION;
3032
}
3033
else
3034
{
3035
/* Output the prompt */
3036
*p++ = '\0';
3037
if (*p) {
3038
if (*p == L'"') {
3039
WCHAR* last = wcsrchr(p+1, L'"');
3040
p++;
3041
if (last) *last = L'\0';
3042
}
3043
WCMD_output_asis(p);
3044
}
3045
3046
/* Read the reply */
3047
if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count) && count > 1) {
3048
string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3049
if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3050
TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3051
wine_dbgstr_w(string));
3052
if (*string) SetEnvironmentVariableW(s, string);
3053
}
3054
}
3055
3056
/* See if /A supplied, and if so calculate the results of all the expressions */
3057
} else if (CompareStringW(LOCALE_USER_DEFAULT,
3058
NORM_IGNORECASE | SORT_STRINGSORT,
3059
s, 2, L"/A", -1) == CSTR_EQUAL) {
3060
/* /A supplied, so evaluate expressions and set variables appropriately */
3061
/* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
3062
/* of the final computation */
3063
int result = 0;
3064
int rc = 0;
3065
WCHAR *thisexpr;
3066
WCHAR *src,*dst;
3067
3068
/* Remove all quotes before doing any calculations */
3069
thisexpr = xalloc((wcslen(s + 2) + 1) * sizeof(WCHAR));
3070
src = s+2;
3071
dst = thisexpr;
3072
while (*src) {
3073
if (*src != '"') *dst++ = *src;
3074
src++;
3075
}
3076
*dst = 0;
3077
3078
/* Now calculate the results of the expression */
3079
src = thisexpr;
3080
rc = WCMD_handleExpression(&src, &result, 0);
3081
free(thisexpr);
3082
3083
/* If parsing failed, issue the error message */
3084
if (rc > 0) {
3085
WCMD_output_stderr(WCMD_LoadMessage(rc));
3086
return_code = ERROR_INVALID_FUNCTION;
3087
}
3088
/* If we have no context (interactive or cmd.exe /c) print the final result */
3089
else if (!WCMD_is_in_context(NULL)) {
3090
swprintf(string, ARRAY_SIZE(string), L"%d", result);
3091
WCMD_output_asis(string);
3092
}
3093
3094
} else {
3095
DWORD gle;
3096
3097
/* set "var=value"jim ignores anything after the last quote */
3098
if (*s=='\"') {
3099
WCHAR *lastquote;
3100
lastquote = WCMD_strip_quotes(s);
3101
if (lastquote) *lastquote = 0x00;
3102
WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
3103
}
3104
3105
p = wcschr (s, '=');
3106
if (p == NULL) {
3107
WCHAR *env = GetEnvironmentStringsW();
3108
if (WCMD_setshow_sortenv( env, s ) == 0) {
3109
WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
3110
return_code = ERROR_INVALID_FUNCTION;
3111
}
3112
FreeEnvironmentStringsW(env);
3113
}
3114
else
3115
{
3116
*p++ = '\0';
3117
3118
if (!*p) p = NULL;
3119
TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3120
wine_dbgstr_w(p));
3121
status = SetEnvironmentVariableW(s, p);
3122
gle = GetLastError();
3123
if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
3124
return_code = ERROR_INVALID_FUNCTION;
3125
} else if (!status) WCMD_print_error();
3126
}
3127
}
3128
return WCMD_is_in_context(L".bat") && return_code == NO_ERROR ?
3129
return_code : (errorlevel = return_code);
3130
}
3131
3132
/****************************************************************************
3133
* WCMD_setshow_path
3134
*
3135
* Set/Show the path environment variable
3136
*/
3137
3138
RETURN_CODE WCMD_setshow_path(const WCHAR *args)
3139
{
3140
WCHAR string[1024];
3141
3142
if (!*param1 && !*param2) {
3143
if (!GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string)))
3144
wcscpy(string, L"(null)");
3145
WCMD_output_asis(L"PATH=");
3146
WCMD_output_asis(string);
3147
WCMD_output_asis(L"\r\n");
3148
}
3149
else {
3150
if (*args == '=') args++; /* Skip leading '=' */
3151
if (args[0] == L';' && *WCMD_skip_leading_spaces((WCHAR *)(args + 1)) == L'\0') args = NULL;
3152
if (!SetEnvironmentVariableW(L"PATH", args))
3153
{
3154
WCMD_print_error();
3155
return errorlevel = ERROR_INVALID_FUNCTION;
3156
}
3157
}
3158
return WCMD_is_in_context(L".bat") ? NO_ERROR : (errorlevel = NO_ERROR);
3159
}
3160
3161
/****************************************************************************
3162
* WCMD_setshow_prompt
3163
*
3164
* Set or show the command prompt.
3165
*/
3166
3167
RETURN_CODE WCMD_setshow_prompt(void)
3168
{
3169
3170
WCHAR *s;
3171
3172
if (!*param1) {
3173
SetEnvironmentVariableW(L"PROMPT", NULL);
3174
}
3175
else {
3176
s = param1;
3177
while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
3178
if (!*s) {
3179
SetEnvironmentVariableW(L"PROMPT", NULL);
3180
}
3181
else SetEnvironmentVariableW(L"PROMPT", s);
3182
}
3183
return WCMD_is_in_context(L".bat") ? NO_ERROR : (errorlevel = NO_ERROR);
3184
}
3185
3186
/****************************************************************************
3187
* WCMD_setshow_time
3188
*
3189
* Set/Show the system time
3190
* FIXME: Can't change time yet
3191
*/
3192
3193
RETURN_CODE WCMD_setshow_time(void)
3194
{
3195
RETURN_CODE return_code = NO_ERROR;
3196
WCHAR curtime[64], buffer[64];
3197
DWORD count;
3198
SYSTEMTIME st;
3199
3200
if (!*param1) {
3201
GetLocalTime(&st);
3202
if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
3203
WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3204
if (wcsstr(quals, L"/T") == NULL) {
3205
WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3206
if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count) &&
3207
count > 2) {
3208
WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3209
}
3210
}
3211
}
3212
else WCMD_print_error ();
3213
}
3214
else {
3215
return_code = ERROR_INVALID_FUNCTION;
3216
WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3217
}
3218
return errorlevel = return_code;
3219
}
3220
3221
/****************************************************************************
3222
* WCMD_shift
3223
*
3224
* Shift batch parameters.
3225
* Optional /n says where to start shifting (n=0-8)
3226
*/
3227
3228
RETURN_CODE WCMD_shift(const WCHAR *args)
3229
{
3230
int start;
3231
3232
if (context != NULL) {
3233
WCHAR *pos = wcschr(args, '/');
3234
int i;
3235
3236
if (pos == NULL) {
3237
start = 0;
3238
} else if (*(pos+1)>='0' && *(pos+1)<='8') {
3239
start = (*(pos+1) - '0');
3240
} else {
3241
SetLastError(ERROR_INVALID_PARAMETER);
3242
WCMD_print_error();
3243
return errorlevel = ERROR_INVALID_FUNCTION;
3244
}
3245
3246
WINE_TRACE("Shifting variables, starting at %d\n", start);
3247
for (i=start;i<=8;i++) {
3248
context -> shift_count[i] = context -> shift_count[i+1] + 1;
3249
}
3250
context -> shift_count[9] = context -> shift_count[9] + 1;
3251
}
3252
return NO_ERROR;
3253
}
3254
3255
/****************************************************************************
3256
* WCMD_start
3257
*/
3258
RETURN_CODE WCMD_start(WCHAR *args)
3259
{
3260
RETURN_CODE return_code = NO_ERROR;
3261
int argno;
3262
int have_title;
3263
WCHAR file[MAX_PATH];
3264
WCHAR *cmdline, *cmdline_params;
3265
STARTUPINFOW st;
3266
PROCESS_INFORMATION pi;
3267
3268
GetSystemDirectoryW( file, MAX_PATH );
3269
lstrcatW(file, L"\\start.exe");
3270
cmdline = xalloc( (wcslen(file) + wcslen(args) + 8) * sizeof(WCHAR) );
3271
lstrcpyW( cmdline, file );
3272
lstrcatW(cmdline, L" ");
3273
cmdline_params = cmdline + lstrlenW(cmdline);
3274
3275
/* The start built-in has some special command-line parsing properties
3276
* which will be outlined here.
3277
*
3278
* both '\t' and ' ' are argument separators
3279
* '/' has a special double role as both separator and switch prefix, e.g.
3280
*
3281
* > start /low/i
3282
* or
3283
* > start "title"/i
3284
*
3285
* are valid ways to pass multiple options to start. In the latter case
3286
* '/i' is not a part of the title but parsed as a switch.
3287
*
3288
* However, '=', ';' and ',' are not separators:
3289
* > start "deus"=ex,machina
3290
*
3291
* will in fact open a console titled 'deus=ex,machina'
3292
*
3293
* The title argument parsing code is only interested in quotes themselves,
3294
* it does not respect escaping of any kind and all quotes are dropped
3295
* from the resulting title, therefore:
3296
*
3297
* > start "\"" hello"/low
3298
*
3299
* actually opens a console titled '\ hello' with low priorities.
3300
*
3301
* To not break compatibility with wine programs relying on
3302
* wine's separate 'start.exe', this program's peculiar console
3303
* title parsing is actually implemented in 'cmd.exe' which is the
3304
* application native Windows programs will use to invoke 'start'.
3305
*
3306
* WCMD_parameter_with_delims will take care of everything for us.
3307
*/
3308
/* FIXME: using an external start.exe has several caveats:
3309
* - cannot discriminate syntax error in arguments from child's return code
3310
* - need to access start.exe's child to get its running state
3311
* (not start.exe itself)
3312
*/
3313
have_title = FALSE;
3314
for (argno=0; ; argno++) {
3315
WCHAR *thisArg, *argN;
3316
3317
argN = NULL;
3318
thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
3319
3320
/* No more parameters */
3321
if (!argN)
3322
break;
3323
3324
/* Found the title */
3325
if (argN[0] == '"') {
3326
TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
3327
have_title = TRUE;
3328
3329
/* Copy all of the cmdline processed */
3330
memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
3331
cmdline_params[argN - args] = '\0';
3332
3333
/* Add quoted title */
3334
lstrcatW(cmdline_params, L"\"\\\"");
3335
lstrcatW(cmdline_params, thisArg);
3336
lstrcatW(cmdline_params, L"\\\"\"");
3337
3338
/* Concatenate remaining command-line */
3339
thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
3340
lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
3341
3342
break;
3343
}
3344
3345
/* Skipping a regular argument? */
3346
else if (argN != args && argN[-1] == '/') {
3347
continue;
3348
3349
/* Not an argument nor the title, start of program arguments,
3350
* stop looking for title.
3351
*/
3352
} else
3353
break;
3354
}
3355
3356
/* build command-line if not built yet */
3357
if (!have_title) {
3358
lstrcatW( cmdline, args );
3359
}
3360
3361
memset( &st, 0, sizeof(STARTUPINFOW) );
3362
st.cb = sizeof(STARTUPINFOW);
3363
3364
if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3365
{
3366
DWORD exit_code;
3367
WaitForSingleObject( pi.hProcess, INFINITE );
3368
GetExitCodeProcess( pi.hProcess, &exit_code );
3369
errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code;
3370
CloseHandle(pi.hProcess);
3371
CloseHandle(pi.hThread);
3372
}
3373
else
3374
{
3375
SetLastError(ERROR_FILE_NOT_FOUND);
3376
WCMD_print_error ();
3377
return_code = errorlevel = ERROR_INVALID_FUNCTION;
3378
}
3379
free(cmdline);
3380
return return_code;
3381
}
3382
3383
/****************************************************************************
3384
* WCMD_title
3385
*
3386
* Set the console title
3387
*/
3388
RETURN_CODE WCMD_title(const WCHAR *args)
3389
{
3390
SetConsoleTitleW(args);
3391
return NO_ERROR;
3392
}
3393
3394
/****************************************************************************
3395
* WCMD_type
3396
*
3397
* Copy a file to standard output.
3398
*/
3399
3400
RETURN_CODE WCMD_type(WCHAR *args)
3401
{
3402
RETURN_CODE return_code;
3403
int argno = 0;
3404
WCHAR *argN = args;
3405
BOOL writeHeaders = FALSE;
3406
3407
if (param1[0] == 0x00) {
3408
WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3409
return errorlevel = ERROR_INVALID_FUNCTION;
3410
}
3411
3412
if (param2[0] != 0x00) writeHeaders = TRUE;
3413
3414
/* Loop through all args */
3415
return_code = NO_ERROR;
3416
while (argN) {
3417
WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3418
3419
HANDLE h;
3420
WCHAR buffer[512];
3421
DWORD count;
3422
3423
if (!argN) break;
3424
3425
WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3426
h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
3427
FILE_ATTRIBUTE_NORMAL, NULL);
3428
if (h == INVALID_HANDLE_VALUE) {
3429
WCMD_print_error ();
3430
WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3431
return errorlevel = ERROR_INVALID_FUNCTION;
3432
} else {
3433
if (writeHeaders) {
3434
WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
3435
}
3436
while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
3437
if (count == 0) break; /* ReadFile reports success on EOF! */
3438
buffer[count] = 0;
3439
WCMD_output_asis (buffer);
3440
}
3441
CloseHandle (h);
3442
}
3443
}
3444
return errorlevel = return_code;
3445
}
3446
3447
/****************************************************************************
3448
* WCMD_more
3449
*
3450
* Output either a file or stdin to screen in pages
3451
*/
3452
3453
RETURN_CODE WCMD_more(WCHAR *args)
3454
{
3455
int argno = 0;
3456
WCHAR *argN = args;
3457
WCHAR moreStr[100];
3458
WCHAR moreStrPage[100];
3459
WCHAR buffer[512];
3460
DWORD count;
3461
RETURN_CODE return_code = NO_ERROR;
3462
3463
/* Prefix the NLS more with '-- ', then load the text */
3464
lstrcpyW(moreStr, L"-- ");
3465
LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
3466
3467
if (param1[0] == 0x00) {
3468
3469
/* Wine implements pipes via temporary files, and hence stdin is
3470
effectively reading from the file. This means the prompts for
3471
more are satisfied by the next line from the input (file). To
3472
avoid this, ensure stdin is to the console */
3473
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3474
HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
3475
FILE_SHARE_READ, NULL, OPEN_EXISTING,
3476
FILE_ATTRIBUTE_NORMAL, 0);
3477
WINE_TRACE("No parms - working probably in pipe mode\n");
3478
SetStdHandle(STD_INPUT_HANDLE, hConIn);
3479
3480
/* Warning: No easy way of ending the stream (ctrl+z on windows) so
3481
once you get in this bit unless due to a pipe, it's going to end badly... */
3482
wsprintfW(moreStrPage, L"%s --\n", moreStr);
3483
3484
WCMD_enter_paged_mode(moreStrPage);
3485
while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
3486
if (count == 0) break; /* ReadFile reports success on EOF! */
3487
buffer[count] = 0;
3488
WCMD_output_asis (buffer);
3489
}
3490
WCMD_leave_paged_mode();
3491
3492
/* Restore stdin to what it was */
3493
SetStdHandle(STD_INPUT_HANDLE, hstdin);
3494
CloseHandle(hConIn);
3495
WCMD_output_asis (L"\r\n");
3496
} else {
3497
BOOL needsPause = FALSE;
3498
3499
/* Loop through all args */
3500
WINE_TRACE("Parms supplied - working through each file\n");
3501
WCMD_enter_paged_mode(moreStrPage);
3502
3503
while (argN) {
3504
WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3505
HANDLE h;
3506
3507
if (!argN) break;
3508
3509
if (needsPause) {
3510
3511
/* Wait */
3512
wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
3513
WCMD_leave_paged_mode();
3514
WCMD_output_asis(moreStrPage);
3515
WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
3516
WCMD_enter_paged_mode(moreStrPage);
3517
}
3518
3519
3520
WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3521
h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3522
FILE_ATTRIBUTE_NORMAL, NULL);
3523
if (h == INVALID_HANDLE_VALUE) {
3524
WCMD_print_error ();
3525
WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3526
} else {
3527
ULONG64 curPos = 0;
3528
ULONG64 fileLen = 0;
3529
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3530
3531
/* Get the file size */
3532
GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3533
fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3534
3535
needsPause = TRUE;
3536
while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
3537
if (count == 0) break; /* ReadFile reports success on EOF! */
3538
buffer[count] = 0;
3539
curPos += count;
3540
3541
/* Update % count (would be used in WCMD_output_asis as prompt) */
3542
wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
3543
3544
WCMD_output_asis (buffer);
3545
}
3546
CloseHandle (h);
3547
}
3548
}
3549
3550
WCMD_leave_paged_mode();
3551
}
3552
return errorlevel = return_code;
3553
}
3554
3555
/****************************************************************************
3556
* WCMD_verify
3557
*
3558
* Display verify flag.
3559
* FIXME: We don't actually do anything with the verify flag other than toggle
3560
* it...
3561
*/
3562
3563
RETURN_CODE WCMD_verify(void)
3564
{
3565
RETURN_CODE return_code = NO_ERROR;
3566
3567
if (!param1[0])
3568
WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), verify_mode ? L"ON" : L"OFF");
3569
else if (lstrcmpiW(param1, L"ON") == 0)
3570
verify_mode = TRUE;
3571
else if (lstrcmpiW(param1, L"OFF") == 0)
3572
verify_mode = FALSE;
3573
else
3574
{
3575
WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3576
return_code = ERROR_INVALID_FUNCTION;
3577
}
3578
return errorlevel = return_code;
3579
}
3580
3581
/****************************************************************************
3582
* WCMD_version
3583
*
3584
* Display version info.
3585
*/
3586
3587
RETURN_CODE WCMD_version(void)
3588
{
3589
RETURN_CODE return_code;
3590
3591
WCMD_output_asis(L"\r\n");
3592
if (*quals)
3593
{
3594
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3595
return_code = ERROR_INVALID_FUNCTION;
3596
}
3597
else
3598
{
3599
WCMD_output_asis(version_string);
3600
return_code = NO_ERROR;
3601
}
3602
return errorlevel = return_code;
3603
}
3604
3605
BOOL WCMD_print_volume_information(const WCHAR *path)
3606
{
3607
WCHAR label[MAX_PATH];
3608
DWORD serial;
3609
3610
if (!GetVolumeInformationW(path, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0))
3611
return FALSE;
3612
if (label[0])
3613
WCMD_output(WCMD_LoadMessage(WCMD_VOLUMELABEL), path[0], label);
3614
else
3615
WCMD_output(WCMD_LoadMessage(WCMD_VOLUMENOLABEL), path[0]);
3616
3617
WCMD_output(WCMD_LoadMessage(WCMD_VOLUMESERIALNO), HIWORD(serial), LOWORD(serial));
3618
return TRUE;
3619
}
3620
3621
/****************************************************************************
3622
* WCMD_label
3623
*
3624
* Set volume label
3625
*/
3626
3627
RETURN_CODE WCMD_label(void)
3628
{
3629
DWORD count;
3630
WCHAR string[MAX_PATH], curdir[MAX_PATH];
3631
3632
/* FIXME incomplete implementation:
3633
* - no support for /MP qualifier,
3634
* - no support for passing label as parameter
3635
*/
3636
if (*quals)
3637
return errorlevel = ERROR_INVALID_FUNCTION;
3638
if (!*param1) {
3639
if (!GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir)) {
3640
WCMD_print_error();
3641
return errorlevel = ERROR_INVALID_FUNCTION;
3642
}
3643
}
3644
else if (param1[1] == ':' && !param1[2]) {
3645
curdir[0] = param1[0];
3646
curdir[1] = param1[1];
3647
} else {
3648
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3649
return errorlevel = ERROR_INVALID_FUNCTION;
3650
}
3651
curdir[2] = L'\\';
3652
curdir[3] = L'\0';
3653
if (!WCMD_print_volume_information(curdir)) {
3654
WCMD_print_error();
3655
return errorlevel = ERROR_INVALID_FUNCTION;
3656
}
3657
3658
if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count) &&
3659
count > 1) {
3660
string[count-1] = '\0'; /* ReadFile output is not null-terminatrred! */
3661
if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3662
}
3663
else return errorlevel = ERROR_INVALID_FUNCTION;
3664
if (*param1) {
3665
if (!SetVolumeLabelW(curdir, string))
3666
{
3667
errorlevel = GetLastError();
3668
WCMD_print_error();
3669
return errorlevel;
3670
}
3671
}
3672
return errorlevel = NO_ERROR;
3673
}
3674
3675
RETURN_CODE WCMD_volume(void)
3676
{
3677
WCHAR curdir[MAX_PATH];
3678
RETURN_CODE return_code = NO_ERROR;
3679
3680
if (*quals)
3681
return errorlevel = ERROR_INVALID_FUNCTION;
3682
if (!*param1)
3683
{
3684
if (!GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir))
3685
return errorlevel = ERROR_INVALID_FUNCTION;
3686
}
3687
else if (param1[1] == L':' && !param1[2])
3688
{
3689
memcpy(curdir, param1, 2 * sizeof(WCHAR));
3690
}
3691
else
3692
return errorlevel = ERROR_INVALID_FUNCTION;
3693
curdir[2] = L'\\';
3694
curdir[3] = L'\0';
3695
if (!WCMD_print_volume_information(curdir))
3696
{
3697
return_code = GetLastError();
3698
WCMD_print_error();
3699
}
3700
return errorlevel = return_code;
3701
}
3702
3703
/**************************************************************************
3704
* WCMD_exit
3705
*
3706
* Exit either the process, or just this batch program
3707
*
3708
*/
3709
3710
RETURN_CODE WCMD_exit(void)
3711
{
3712
int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
3713
3714
if (context && lstrcmpiW(quals, L"/B") == 0)
3715
{
3716
errorlevel = rc;
3717
context->file_position.QuadPart = WCMD_FILE_POSITION_EOF;
3718
return RETURN_CODE_ABORTED;
3719
}
3720
ExitProcess(rc);
3721
}
3722
3723
3724
/*****************************************************************************
3725
* WCMD_assoc
3726
*
3727
* Lists or sets file associations (assoc = TRUE)
3728
* Lists or sets file types (assoc = FALSE)
3729
*/
3730
RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc)
3731
{
3732
RETURN_CODE return_code;
3733
HKEY key;
3734
DWORD accessOptions = KEY_READ;
3735
WCHAR *newValue;
3736
LONG rc = ERROR_SUCCESS;
3737
WCHAR keyValue[MAXSTRING];
3738
DWORD valueLen;
3739
HKEY readKey;
3740
3741
/* See if parameter includes '=' */
3742
return_code = NO_ERROR;
3743
newValue = wcschr(args, '=');
3744
if (newValue) accessOptions |= KEY_WRITE;
3745
3746
/* Open a key to HKEY_CLASSES_ROOT for enumerating */
3747
if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
3748
WINE_FIXME("Unexpected failure opening HKCR key: %ld\n", GetLastError());
3749
return ERROR_INVALID_FUNCTION;
3750
}
3751
3752
/* If no parameters then list all associations */
3753
if (*args == 0x00) {
3754
int index = 0;
3755
3756
/* Enumerate all the keys */
3757
while (rc != ERROR_NO_MORE_ITEMS) {
3758
WCHAR keyName[MAXSTRING];
3759
DWORD nameLen;
3760
3761
/* Find the next value */
3762
nameLen = MAXSTRING;
3763
rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3764
3765
if (rc == ERROR_SUCCESS) {
3766
3767
/* Only interested in extension ones if assoc, or others
3768
if not assoc */
3769
if ((keyName[0] == '.' && assoc) ||
3770
(!(keyName[0] == '.') && (!assoc)))
3771
{
3772
WCHAR subkey[MAXSTRING];
3773
lstrcpyW(subkey, keyName);
3774
if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
3775
3776
if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3777
3778
valueLen = sizeof(keyValue);
3779
rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3780
WCMD_output_asis(keyName);
3781
WCMD_output_asis(L"=");
3782
/* If no default value found, leave line empty after '=' */
3783
if (rc == ERROR_SUCCESS) {
3784
WCMD_output_asis(keyValue);
3785
}
3786
WCMD_output_asis(L"\r\n");
3787
RegCloseKey(readKey);
3788
}
3789
}
3790
}
3791
}
3792
3793
} else {
3794
3795
/* Parameter supplied - if no '=' on command line, it's a query */
3796
if (newValue == NULL) {
3797
WCHAR *space;
3798
WCHAR subkey[MAXSTRING];
3799
3800
/* Query terminates the parameter at the first space */
3801
lstrcpyW(keyValue, args);
3802
space = wcschr(keyValue, ' ');
3803
if (space) *space=0x00;
3804
3805
/* Set up key name */
3806
lstrcpyW(subkey, keyValue);
3807
if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
3808
3809
valueLen = sizeof(keyValue);
3810
if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS &&
3811
RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen) == ERROR_SUCCESS) {
3812
WCMD_output_asis(args);
3813
WCMD_output_asis(L"=");
3814
WCMD_output_asis(keyValue);
3815
WCMD_output_asis(L"\r\n");
3816
RegCloseKey(readKey);
3817
return_code = NO_ERROR;
3818
} else {
3819
WCHAR msgbuffer[MAXSTRING];
3820
3821
/* Load the translated 'File association not found' */
3822
if (assoc) {
3823
LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
3824
} else {
3825
LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
3826
}
3827
WCMD_output_stderr(msgbuffer, keyValue);
3828
return_code = assoc ? ERROR_INVALID_FUNCTION : ERROR_FILE_NOT_FOUND;
3829
}
3830
3831
/* Not a query - it's a set or clear of a value */
3832
} else {
3833
3834
WCHAR subkey[MAXSTRING];
3835
3836
/* Get pointer to new value */
3837
*newValue = 0x00;
3838
newValue++;
3839
3840
/* Set up key name */
3841
lstrcpyW(subkey, args);
3842
if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
3843
3844
if (*newValue == 0x00) {
3845
3846
if (assoc)
3847
rc = RegDeleteKeyW(key, args);
3848
else {
3849
rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3850
accessOptions, NULL, &readKey, NULL);
3851
if (rc == ERROR_SUCCESS) {
3852
rc = RegDeleteValueW(readKey, NULL);
3853
RegCloseKey(readKey);
3854
}
3855
}
3856
if (rc == ERROR_SUCCESS) {
3857
WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
3858
3859
} else if (rc != ERROR_FILE_NOT_FOUND) {
3860
WCMD_print_error();
3861
return_code = ERROR_FILE_NOT_FOUND;
3862
3863
} else {
3864
WCHAR msgbuffer[MAXSTRING];
3865
3866
/* Load the translated 'File association not found' */
3867
if (assoc) {
3868
LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
3869
} else {
3870
LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
3871
}
3872
WCMD_output_stderr(msgbuffer, args);
3873
return_code = ERROR_FILE_NOT_FOUND;
3874
}
3875
3876
/* It really is a set value = contents */
3877
} else {
3878
rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3879
accessOptions, NULL, &readKey, NULL);
3880
if (rc == ERROR_SUCCESS) {
3881
rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3882
(LPBYTE)newValue,
3883
sizeof(WCHAR) * (lstrlenW(newValue) + 1));
3884
RegCloseKey(readKey);
3885
}
3886
3887
if (rc != ERROR_SUCCESS) {
3888
WCMD_print_error();
3889
return_code = ERROR_FILE_NOT_FOUND;
3890
} else {
3891
WCMD_output_asis(args);
3892
WCMD_output_asis(L"=");
3893
WCMD_output_asis(newValue);
3894
WCMD_output_asis(L"\r\n");
3895
}
3896
}
3897
}
3898
}
3899
3900
/* Clean up */
3901
RegCloseKey(key);
3902
return WCMD_is_in_context(L".bat") && return_code == NO_ERROR ?
3903
return_code : (errorlevel = return_code);
3904
}
3905
3906
/****************************************************************************
3907
* WCMD_color
3908
*
3909
* Colors the terminal screen.
3910
*/
3911
3912
RETURN_CODE WCMD_color(void)
3913
{
3914
RETURN_CODE return_code = ERROR_INVALID_FUNCTION;
3915
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3916
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3917
3918
if (param1[0] != 0x00 && lstrlenW(param1) > 2)
3919
{
3920
WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3921
}
3922
else if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3923
{
3924
COORD topLeft = {0, 0};
3925
DWORD screenSize;
3926
DWORD color;
3927
3928
screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3929
3930
/* Convert the color hex digits */
3931
color = wcstoul(param1, NULL, 16);
3932
3933
/* Fail if fg == bg color */
3934
if (((color & 0xF0) >> 4) != (color & 0x0F))
3935
{
3936
FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3937
SetConsoleTextAttribute(hStdOut, color);
3938
return_code = NO_ERROR;
3939
}
3940
}
3941
return errorlevel = return_code;
3942
}
3943
3944
/****************************************************************************
3945
* WCMD_mklink
3946
*/
3947
3948
RETURN_CODE WCMD_mklink(WCHAR *args)
3949
{
3950
int argno = 0;
3951
WCHAR *argN = args;
3952
BOOL isdir = FALSE;
3953
BOOL junction = FALSE;
3954
BOOL hard = FALSE;
3955
BOOL ret = FALSE;
3956
WCHAR file1[MAX_PATH];
3957
WCHAR file2[MAX_PATH];
3958
3959
file1[0] = 0;
3960
3961
while (argN) {
3962
WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3963
3964
if (!argN) break;
3965
3966
WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3967
3968
if (lstrcmpiW(thisArg, L"/D") == 0)
3969
isdir = TRUE;
3970
else if (lstrcmpiW(thisArg, L"/H") == 0)
3971
hard = TRUE;
3972
else if (lstrcmpiW(thisArg, L"/J") == 0)
3973
junction = TRUE;
3974
else if (*thisArg == L'/')
3975
{
3976
return errorlevel = ERROR_INVALID_FUNCTION;
3977
}
3978
else
3979
{
3980
if(!file1[0])
3981
lstrcpyW(file1, thisArg);
3982
else
3983
lstrcpyW(file2, thisArg);
3984
}
3985
}
3986
3987
if (*file1 && *file2)
3988
{
3989
if (hard)
3990
ret = CreateHardLinkW(file1, file2, NULL);
3991
else if(!junction)
3992
ret = CreateSymbolicLinkW(file1, file2, isdir);
3993
else
3994
TRACE("Junction links currently not supported.\n");
3995
}
3996
3997
if (ret) return errorlevel = NO_ERROR;
3998
3999
WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);
4000
return errorlevel = ERROR_INVALID_FUNCTION;
4001
}
4002
4003
RETURN_CODE WCMD_change_drive(WCHAR drive)
4004
{
4005
WCHAR envvar[4];
4006
WCHAR dir[MAX_PATH];
4007
4008
/* According to MSDN CreateProcess docs, special env vars record
4009
* the current directory on each drive, in the form =C:
4010
* so see if one specified, and if so go back to it
4011
*/
4012
envvar[0] = L'=';
4013
envvar[1] = drive;
4014
envvar[2] = L':';
4015
envvar[3] = L'\0';
4016
4017
if (GetEnvironmentVariableW(envvar, dir, ARRAY_SIZE(dir)) == 0)
4018
wcscpy(dir, envvar + 1);
4019
WINE_TRACE("Got directory for %lc: as %s\n", drive, wine_dbgstr_w(dir));
4020
if (!SetCurrentDirectoryW(dir))
4021
{
4022
WCMD_print_error();
4023
return errorlevel = ERROR_INVALID_FUNCTION;
4024
}
4025
return NO_ERROR;
4026
}
4027
4028