update for HEAD-2003050101
[reactos.git] / subsys / system / cmd / copy.c
1 /* $Id$
2  *
3  *  COPY.C -- copy internal command.
4  *
5  *
6  *  History:
7  *
8  *    01-Aug-98 (Rob Lake z63rrl@morgan.ucs.mun.ca)
9  *        started
10  *
11  *    13-Aug-1998 (John P. Price)
12  *        fixed memory leak problem in copy function.
13  *        fixed copy function so it would work with wildcards in the source
14  *
15  *    13-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
16  *        Added COPY command to CMD.
17  *
18  *    26-Jan-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
19  *        Replaced CRT io functions by Win32 io functions.
20  *
21  *    27-Oct-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
22  *        Disabled prompting when used in batch mode.
23  */
24
25 #include "config.h"
26
27 #ifdef INCLUDE_CMD_COPY
28
29 #include <windows.h>
30 #include <tchar.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <ctype.h>
34
35 #include "cmd.h"
36 #include "batch.h"
37
38
39 #define VERIFY  1               /* VERIFY Switch */
40 #define BINARY  2               /* File is to be copied as BINARY */
41 #define ASCII   4               /* File is to be copied as ASCII */
42 #define PROMPT  8               /* Prompt before overwriting files */
43 #define NPROMPT 16              /* Do not prompt before overwriting files */
44 #define HELP    32              /* Help was asked for */
45 #define SOURCE  128             /* File is a source */
46
47
48 typedef struct tagFILES
49 {
50         struct tagFILES *next;
51         TCHAR szFile[MAX_PATH];
52         DWORD dwFlag;                   /* BINARY -xor- ASCII */
53 } FILES, *LPFILES;
54
55
56 static BOOL DoSwitches (LPTSTR, LPDWORD);
57 static BOOL AddFile (LPFILES, char *, int *, int *, LPDWORD);
58 static BOOL AddFiles (LPFILES, char *, int *, int *, int *, LPDWORD);
59 static BOOL GetDestination (LPFILES, LPFILES);
60 static INT  ParseCommand (LPFILES, int, char **, LPDWORD);
61 static VOID DeleteFileList (LPFILES);
62 static INT  Overwrite (LPTSTR);
63
64
65
66 static BOOL
67 IsDirectory (LPTSTR fn)
68 {
69         if (!IsValidFileName (fn))
70                 return FALSE;
71         return (GetFileAttributes (fn) & FILE_ATTRIBUTE_DIRECTORY);
72 }
73
74
75 static BOOL
76 DoSwitches (LPTSTR arg, LPDWORD lpdwFlags)
77 {
78         if (!_tcsicmp (arg, _T("/-Y")))
79         {
80                 *lpdwFlags |= PROMPT;
81                 *lpdwFlags &= ~NPROMPT;
82                 return TRUE;
83         }
84         else if (_tcslen (arg) > 2)
85         {
86                 error_too_many_parameters ("");
87                 return FALSE;
88         }
89
90         switch (_totupper (arg[1]))
91         {
92                 case _T('V'):
93                         *lpdwFlags |= VERIFY;
94                         break;
95
96                 case _T('A'):
97                         *lpdwFlags |= ASCII;
98                         *lpdwFlags &= ~BINARY;
99                         break;
100
101                 case _T('B'):
102                         *lpdwFlags |= BINARY;
103                         *lpdwFlags &= ~ASCII;
104                         break;
105
106                 case _T('Y'):
107                         *lpdwFlags &= ~PROMPT;
108                         *lpdwFlags |= NPROMPT;
109                         break;
110
111                 default:
112                         error_invalid_switch (arg[1]);
113                         return FALSE;
114         }
115         return TRUE;
116 }
117
118
119 static BOOL
120 AddFile (LPFILES f, char *arg, int *source, int *dest, LPDWORD flags)
121 {
122         if (*dest)
123         {
124                 error_too_many_parameters ("");
125                 return FALSE;
126         }
127         if (*source)
128         {
129                 *dest = 1;
130                 f->dwFlag = 0;
131         }
132         else
133         {
134                 *source = 1;
135                 f->dwFlag = SOURCE;
136         }
137         _tcscpy(f->szFile, arg);
138         f->dwFlag |= *flags & ASCII ? ASCII : BINARY;
139         if ((f->next = (LPFILES)malloc (sizeof (FILES))) == NULL)
140         {
141                 error_out_of_memory ();
142                 return FALSE;
143         }
144         f = f->next;
145         f->dwFlag = 0;
146         f->next = NULL;
147         return TRUE;
148 }
149
150
151 static BOOL
152 AddFiles (LPFILES f, char *arg, int *source, int *dest,
153                   int *count, LPDWORD flags)
154 {
155         char t[128];
156         int j;
157         int k;
158
159         if (*dest)
160         {
161                 error_too_many_parameters ("");
162                 return FALSE;
163         }
164
165         j = 0;
166         k = 0;
167
168         while (arg[j] == _T('+'))
169                 j++;
170
171         while (arg[j] != _T('\0'))
172         {
173                 t[k] = arg[j++];
174                 if (t[k] == '+' || arg[j] == _T('\0'))
175                 {
176                         if (!k)
177                                 continue;
178                         if (arg[j] == _T('\0') && t[k] != _T('+'))
179                                 k++;
180                         t[k] = _T('\0');
181                         *count += 1;
182                         _tcscpy (f->szFile, t);
183                         *source = 1;
184                         if (*flags & ASCII)
185                                 f->dwFlag |= *flags | SOURCE | ASCII;
186                         else
187                                 f->dwFlag |= *flags | BINARY | SOURCE;
188
189                         if ((f->next = (LPFILES)malloc (sizeof (FILES))) == NULL)
190                         {
191                                 error_out_of_memory ();
192                                 return FALSE;
193                         }
194                         f = f->next;
195                         f->next = NULL;
196                         k = 0;
197                         f->dwFlag = 0;
198                         continue;
199                 }
200                 k++;
201         }
202
203         if (arg[--j] == _T('+'))
204                 *source = 0;
205
206         return 1;
207 }
208
209
210 static BOOL
211 GetDestination (LPFILES f, LPFILES dest)
212 {
213         LPFILES p = NULL;
214         LPFILES start = f;
215
216         while (f->next != NULL)
217         {
218                 p = f;
219                 f = f->next;
220         }
221
222         f = p;
223
224         if ((f->dwFlag & SOURCE) == 0)
225         {
226                 free (p->next);
227                 p->next = NULL;
228                 _tcscpy (dest->szFile, f->szFile);
229                 dest->dwFlag = f->dwFlag;
230                 dest->next = NULL;
231                 f = start;
232                 return TRUE;
233         }
234
235         return FALSE;
236 }
237
238
239 static INT
240 ParseCommand (LPFILES f, int argc, char **arg, LPDWORD lpdwFlags)
241 {
242         INT i;
243         INT dest;
244         INT source;
245         INT count;
246
247         dest = 0;
248         source = 0;
249         count = 0;
250
251         for (i = 0; i < argc; i++)
252         {
253                 if (arg[i][0] == _T('/'))
254                 {
255                         if (!DoSwitches (arg[i], lpdwFlags))
256                                 return -1;
257                 }
258                 else
259                 {
260                         if (!_tcscmp(arg[i], _T("+")))
261                                 source = 0;
262                         else if (!_tcschr(arg[i], _T('+')) && source)
263                         {
264                                 if (!AddFile(f, arg[i], &source, &dest, lpdwFlags))
265                                         return -1;
266                                 f = f->next;
267                                 count++;
268                         }
269                         else
270                         {
271                                 if (!AddFiles(f, arg[i], &source, &dest, &count, lpdwFlags))
272                                         return -1;
273                                 while (f->next != NULL)
274                                         f = f->next;
275                         }
276                 }
277         }
278
279 #ifdef _DEBUG
280         DebugPrintf ("ParseCommand: flags has %s\n",
281                                  *lpdwFlags & ASCII ? "ASCII" : "BINARY");
282 #endif
283         return count;
284 }
285
286
287 static VOID
288 DeleteFileList (LPFILES f)
289 {
290         LPFILES temp;
291
292         while (f != NULL)
293         {
294                 temp = f;
295                 f = f->next;
296                 free (temp);
297         }
298 }
299
300
301 static INT
302 Overwrite (LPTSTR fn)
303 {
304         TCHAR inp[10];
305         LPTSTR p;
306
307         ConOutPrintf (_T("Overwrite %s (Yes/No/All)? "), fn);
308         ConInString (inp, 10);
309         ConOutPuts (_T(""));
310
311         _tcsupr (inp);
312         for (p = inp; _istspace (*p); p++)
313                 ;
314
315         if (*p != _T('Y') && *p != _T('A'))
316                 return 0;
317         if (*p == _T('A'))
318                 return 2;
319
320         return 1;
321 }
322
323
324 #define BUFF_SIZE 16384         /* 16k = max buffer size */
325
326
327 int copy (LPTSTR source, LPTSTR dest, int append, LPDWORD lpdwFlags)
328 {
329         FILETIME srctime;
330         HANDLE hFileSrc;
331         HANDLE hFileDest;
332         LPBYTE buffer;
333         DWORD  dwAttrib;
334         DWORD  dwRead;
335         DWORD  dwWritten;
336         DWORD  i;
337         BOOL   bEof = FALSE;
338
339 #ifdef _DEBUG
340         DebugPrintf ("checking mode\n");
341 #endif
342
343         dwAttrib = GetFileAttributes (source);
344
345         hFileSrc = CreateFile (source, GENERIC_READ, FILE_SHARE_READ,
346                                                    NULL, OPEN_EXISTING, 0, NULL);
347         if (hFileSrc == INVALID_HANDLE_VALUE)
348         {
349                 ConErrPrintf (_T("Error: Cannot open source - %s!\n"), source);
350                 return 0;
351         }
352
353 #ifdef _DEBUG
354         DebugPrintf (_T("getting time\n"));
355 #endif
356
357         GetFileTime (hFileSrc, &srctime, NULL, NULL);
358
359 #ifdef _DEBUG
360         DebugPrintf (_T("copy: flags has %s\n"),
361                                  *lpdwFlags & ASCII ? "ASCII" : "BINARY");
362 #endif
363
364         if (!IsValidFileName (dest))
365         {
366 #ifdef _DEBUG
367                 DebugPrintf (_T("opening/creating\n"));
368 #endif
369                 hFileDest =
370                         CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
371         }
372         else if (!append)
373         {
374                 if (!_tcscmp (dest, source))
375                 {
376                         ConErrPrintf (_T("Error: Can't copy onto itself!\n"));
377                         CloseHandle (hFileSrc);
378                         return 0;
379                 }
380
381 #ifdef _DEBUG
382                 DebugPrintf (_T("SetFileAttributes (%s, FILE_ATTRIBUTE_NORMAL);\n"), dest);
383 #endif
384                 SetFileAttributes (dest, FILE_ATTRIBUTE_NORMAL);
385
386 #ifdef _DEBUG
387                 DebugPrintf (_T("DeleteFile (%s);\n"), dest);
388 #endif
389                 DeleteFile (dest);
390
391                 hFileDest =
392                         CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
393         }
394         else
395         {
396                 LONG lFilePosHigh = 0;
397
398                 if (!_tcscmp (dest, source))
399                 {
400                         CloseHandle (hFileSrc);
401                         return 0;
402                 }
403
404 #ifdef _DEBUG
405                 DebugPrintf (_T("opening/appending\n"));
406 #endif
407                 hFileDest =
408                         CreateFile (dest, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
409                 SetFilePointer (hFileDest, 0, &lFilePosHigh,FILE_END);
410         }
411
412         if (hFileDest == INVALID_HANDLE_VALUE)
413         {
414                 CloseHandle (hFileSrc);
415                 error_path_not_found ();
416                 return 0;
417         }
418
419         buffer = (LPBYTE)malloc (BUFF_SIZE);
420         if (buffer == NULL)
421         {
422                 CloseHandle (hFileDest);
423                 CloseHandle (hFileSrc);
424                 error_out_of_memory ();
425                 return 0;
426         }
427
428         do
429         {
430                 ReadFile (hFileSrc, buffer, BUFF_SIZE, &dwRead, NULL);
431                 if (*lpdwFlags & ASCII)
432                 {
433                         for (i = 0; i < dwRead; i++)
434                         {
435                                 if (((LPTSTR)buffer)[i] == 0x1A)
436                                 {
437                                         bEof = TRUE;
438                                         break;
439                                 }
440                         }
441                         dwRead = i;
442                 }
443
444                 if (dwRead == 0)
445                         break;
446
447                 WriteFile (hFileDest, buffer, dwRead, &dwWritten, NULL);
448                 if (dwWritten != dwRead)
449                 {
450                         ConErrPrintf (_T("Error writing destination!\n"));
451                         free (buffer);
452                         CloseHandle (hFileDest);
453                         CloseHandle (hFileSrc);
454                         return 0;
455                 }
456         }
457         while (dwRead && !bEof);
458
459 #ifdef _DEBUG
460         DebugPrintf (_T("setting time\n"));
461 #endif
462         SetFileTime (hFileDest, &srctime, NULL, NULL);
463
464         if (*lpdwFlags & ASCII)
465         {
466                 ((LPTSTR)buffer)[0] = 0x1A;
467                 ((LPTSTR)buffer)[1] = _T('\0');
468 #ifdef _DEBUG
469                 DebugPrintf (_T("appending ^Z\n"));
470 #endif
471                 WriteFile (hFileDest, buffer, sizeof(TCHAR), &dwWritten, NULL);
472         }
473
474         free (buffer);
475         CloseHandle (hFileDest);
476         CloseHandle (hFileSrc);
477
478 #ifdef _DEBUG
479         DebugPrintf (_T("setting mode\n"));
480 #endif
481         SetFileAttributes (dest, dwAttrib);
482
483         return 1;
484 }
485
486
487 static INT
488 SetupCopy (LPFILES sources, char **p, BOOL bMultiple,
489            char *drive_d, char *dir_d, char *file_d,
490            char *ext_d, int *append, LPDWORD lpdwFlags)
491 {
492         WIN32_FIND_DATA find;
493
494         char drive_s[_MAX_DRIVE];
495         CHAR dir_s[_MAX_DIR];
496         char file_s[_MAX_FNAME];
497         char ext_s[_MAX_EXT];
498         char from_merge[_MAX_PATH];
499
500         LPTSTR real_source;
501         LPTSTR real_dest;
502
503         INT  nCopied = 0;
504         BOOL bAll = FALSE;
505         BOOL bDone;
506         HANDLE hFind;
507
508 #ifdef _DEBUG
509         DebugPrintf (_T("SetupCopy\n"));
510 #endif
511
512         real_source = (LPTSTR)malloc (MAX_PATH);
513         real_dest = (LPTSTR)malloc (MAX_PATH);
514
515         if (!real_source || !real_dest)
516         {
517                 error_out_of_memory ();
518                 DeleteFileList (sources);
519                 free (real_source);
520                 free (real_dest);
521                 freep (p);
522                 return 0;
523         }
524
525         while (sources->next != NULL)
526         {
527                 _splitpath (sources->szFile, drive_s, dir_s, file_s, ext_s);
528                 hFind = FindFirstFile (sources->szFile, &find);
529                 if (hFind == INVALID_HANDLE_VALUE)
530                 {
531                         error_file_not_found();
532                         freep(p);
533                         free(real_source);
534                         free(real_dest);
535                         return 0;
536                 }
537
538                 do
539                 {
540                         if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
541                                 goto next;
542
543                         _makepath(from_merge, drive_d, dir_d, file_d, ext_d);
544                         if (from_merge[_tcslen(from_merge) - 1] == _T('\\'))
545                                 from_merge[_tcslen(from_merge) - 1] = 0;
546
547                         if (IsDirectory (from_merge))
548                         {
549                                 bMultiple = FALSE;
550                                 _tcscat (from_merge, _T("\\"));
551                                 _tcscat (from_merge, find.cFileName);
552                         }
553                         else
554                                 bMultiple = TRUE;
555
556                         _tcscpy (real_dest, from_merge);
557                         _makepath (real_source, drive_s, dir_s, find.cFileName, NULL);
558
559 #ifdef _DEBUG
560                         DebugPrintf (_T("copying %s -> %s (%sappending%s)\n"),
561                                                  real_source, real_dest,
562                                                  *append ? "" : "not ",
563                                                  sources->dwFlag & ASCII ? ", ASCII" : ", BINARY");
564 #endif
565
566                         if (IsValidFileName (real_dest) && !bAll)
567                         {
568                                 /* Don't prompt in a batch file */
569                                 if (bc != NULL)
570                                 {
571                                         bAll = TRUE;
572                                 }
573                                 else
574                                 {
575                                         int over;
576
577                                         over = Overwrite (real_dest);
578                                         if (over == 2)
579                                                 bAll = TRUE;
580                                         else if (over == 0)
581                                                 goto next;
582                                         else if (bMultiple)
583                                                 bAll = TRUE;
584                                 }
585                         }
586                         if (copy (real_source, real_dest, *append, lpdwFlags))
587                                 nCopied++;
588                 next:
589                         bDone = FindNextFile (hFind, &find);
590
591                         if (bMultiple)
592                                 *append = 1;
593                 }
594                 while (bDone);
595
596                 FindClose (hFind);
597                 sources = sources->next;
598
599         }
600         free (real_source);
601         free (real_dest);
602
603         return nCopied;
604 }
605
606
607 INT cmd_copy (LPTSTR first, LPTSTR rest)
608 {
609         char **p;
610         char drive_d[_MAX_DRIVE];
611         char dir_d[_MAX_DIR];
612         char file_d[_MAX_FNAME];
613         char ext_d[_MAX_EXT];
614
615         int argc;
616         int append;
617         int files;
618         int copied;
619
620         LPFILES sources = NULL;
621         LPFILES start = NULL;
622         FILES dest;
623         BOOL bMultiple;
624         BOOL bWildcards;
625         BOOL bDestFound;
626         DWORD dwFlags = 0;
627
628         if (!_tcsncmp (rest, _T("/?"), 2))
629         {
630                 ConOutPuts (_T("Copies one or more files to another location.\n"
631                                            "\n"
632                                            "COPY [/V][/Y|/-Y][/A|/B] source [/A|/B]\n"
633                                            "     [+ source [/A|/B] [+ ...]] [destination [/A|/B]]\n"
634                                            "\n"
635                                            "  source       Specifies the file or files to be copied.\n"
636                                            "  /A           Indicates an ASCII text file.\n"
637                                            "  /B           Indicates a binary file.\n"
638                                            "  destination  Specifies the directory and/or filename for the new file(s).\n"
639                                            "  /V           Verifies that new files are written correctly.\n"
640                                            "  /Y           Suppresses prompting to confirm you want to overwrite an\n"
641                                            "               existing destination file.\n"
642                                            "  /-Y          Causes prompting to confirm you want to overwrite an\n"
643                                            "               existing destination file.\n"
644                                            "\n"
645                                            "The switch /Y may be present in the COPYCMD environment variable.\n"
646                                            "..."));
647                 return 1;
648         }
649
650         p = split (rest, &argc, FALSE);
651
652         if (argc == 0)
653         {
654                 error_req_param_missing ();
655                 return 0;
656         }
657
658         sources = (LPFILES)malloc (sizeof (FILES));
659         if (!sources)
660         {
661                 error_out_of_memory ();
662                 return 0;
663         }
664         sources->next = NULL;
665         sources->dwFlag = 0;
666
667         if ((files = ParseCommand (sources, argc, p, &dwFlags)) == -1)
668         {
669                 DeleteFileList (sources);
670                 freep (p);
671                 return 0;
672         }
673         else if (files == 0)
674         {
675                 error_req_param_missing();
676                 DeleteFileList (sources);
677                 freep (p);
678                 return 0;
679         }
680         start = sources;
681
682         bDestFound = GetDestination (sources, &dest);
683         if (bDestFound)
684         {
685                 _splitpath (dest.szFile, drive_d, dir_d, file_d, ext_d);
686                 if (IsDirectory (dest.szFile))
687                 {
688                         _tcscat (dir_d, file_d);
689                         _tcscat (dir_d, ext_d);
690                         file_d[0] = _T('\0');
691                         ext_d[0] = _T('\0');
692                 }
693         }
694
695         if (_tcschr (dest.szFile, _T('*')) || _tcschr (dest.szFile, _T('?')))
696                 bWildcards = TRUE;
697         else
698                 bWildcards = FALSE;
699
700         if (strchr(rest, '+'))
701                 bMultiple = TRUE;
702         else
703                 bMultiple = FALSE;
704
705         append = 0;
706         copied = 0;
707
708         if (bDestFound && !bWildcards)
709         {
710                 copied = SetupCopy (sources, p, bMultiple, drive_d, dir_d, file_d, ext_d, &append, &dwFlags);
711         }
712         else if (bDestFound && bWildcards)
713         {
714                 ConErrPrintf (_T("Error: Not implemented yet!\n"));
715                 DeleteFileList (sources);
716                 freep (p);
717                 return 0;
718         }
719         else if (!bDestFound && !bMultiple)
720         {
721                 _splitpath (sources->szFile, drive_d, dir_d, file_d, ext_d);
722                 if (IsDirectory (sources->szFile))
723                 {
724                         _tcscat (dir_d, file_d);
725                         _tcscat (dir_d, ext_d);
726                         file_d[0] = _T('\0');
727                         ext_d[0] = _T('\0');
728                 }
729                 copied = SetupCopy (sources, p, FALSE, "", "", file_d, ext_d, &append, &dwFlags);
730         }
731         else
732         {
733                 _splitpath(sources->szFile, drive_d, dir_d, file_d, ext_d);
734                 if (IsDirectory (sources->szFile))
735                 {
736                         _tcscat (dir_d, file_d);
737                         _tcscat (dir_d, ext_d);
738                         file_d[0] = _T('\0');
739                         ext_d[0] = _T('\0');
740                 }
741
742                 ConOutPuts (sources->szFile);
743                 append = 1;
744                 copied = SetupCopy (sources->next, p, bMultiple, drive_d, dir_d, file_d, ext_d, &append, &dwFlags) + 1;
745         }
746
747         DeleteFileList (sources);
748         freep (p);
749         ConOutPrintf (_T("        %d file(s) copied\n"), copied);
750
751         return 1;
752 }
753 #endif /* INCLUDE_CMD_COPY */
754
755 /* EOF */