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