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