1 /* Copyright 1986-1992 Emmet P. Gray.
2 * Copyright 1994,1996-2002,2007-2009,2021 Alain Knaff.
3 * This file is part of mtools.
4 *
5 * Mtools is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * Mtools is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Mtools. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * mcopy.c
19 * Copy an MSDOS files to and from Unix
20 *
21 */
22
23
24 #include "sysincludes.h"
25 #include "msdos.h"
26 #include "mtools.h"
27 #include "vfat.h"
28 #include "mainloop.h"
29 #include "plain_io.h"
30 #include "nameclash.h"
31 #include "file.h"
32 #include "fs.h"
33
34
35 /*
36 * Preserve the file modification times after the fclose()
37 */
38
set_mtime(const char * target,time_t mtime)39 static void set_mtime(const char *target, time_t mtime)
40 {
41 if (target && strcmp(target, "-") && mtime != 0L) {
42 #ifdef HAVE_UTIMES
43 struct timeval tv[2];
44 tv[0].tv_sec = mtime;
45 tv[0].tv_usec = 0;
46 tv[1].tv_sec = mtime;
47 tv[1].tv_usec = 0;
48 utimes(target, tv);
49 #else
50 #ifdef HAVE_UTIME
51 struct utimbuf utbuf;
52
53 utbuf.actime = mtime;
54 utbuf.modtime = mtime;
55 utime(target, &utbuf);
56 #endif
57 #endif
58 }
59 return;
60 }
61
62 typedef struct Arg_t {
63 int recursive;
64 int preserveAttributes;
65 int preserveTime;
66 unsigned char attr;
67 char *path;
68 int textmode;
69 int needfilter;
70 int nowarn;
71 int verbose;
72 int type;
73 int convertCharset;
74 MainParam_t mp;
75 ClashHandling_t ch;
76 int noClobber;
77 } Arg_t;
78
79 static int _unix_write(MainParam_t *mp, int needfilter, const char *unixFile);
80
81 /* Write the Unix file */
unix_write(MainParam_t * mp,int needfilter)82 static int unix_write(MainParam_t *mp, int needfilter)
83 {
84 Arg_t *arg=(Arg_t *) mp->arg;
85
86 if(arg->type)
87 return _unix_write(mp, needfilter, "-");
88 else {
89 char *unixFile = mpBuildUnixFilename(mp);
90 int ret;
91 if(!unixFile) {
92 printOom();
93 return ERROR_ONE;
94 }
95 ret = _unix_write(mp, needfilter, unixFile);
96 free(unixFile);
97 return ret;
98 }
99 }
100
101
102 /* Write the Unix file */
_unix_write(MainParam_t * mp,int needfilter,const char * unixFile)103 static int _unix_write(MainParam_t *mp, int needfilter, const char *unixFile)
104 {
105 Arg_t *arg=(Arg_t *) mp->arg;
106 time_t mtime;
107 Stream_t *File=mp->File;
108 Stream_t *Target, *Source;
109 struct MT_STAT stbuf;
110 char errmsg[80];
111
112 File->Class->get_data(File, &mtime, 0, 0, 0);
113
114 if (!arg->preserveTime)
115 mtime = 0L;
116
117 /* if we are creating a file, check whether it already exists */
118 if(!arg->type) {
119 if (!arg->nowarn && !access(unixFile, 0)){
120 if(arg->noClobber) {
121 fprintf(stderr, "File \"%s\" exists. To overwrite, try again, and explicitly specify target directory\n",unixFile);
122 return ERROR_ONE;
123 }
124
125 /* sanity checking */
126 if (!MT_STAT(unixFile, &stbuf)) {
127 struct MT_STAT srcStbuf;
128 int sFd; /* Source file descriptor */
129 if(!S_ISREG(stbuf.st_mode)) {
130 fprintf(stderr,"\"%s\" is not a regular file\n",
131 unixFile);
132
133 return ERROR_ONE;
134 }
135 sFd = get_fd(File);
136 if(sFd == -1) {
137 } else if((!MT_FSTAT(sFd, &srcStbuf)) &&
138 stbuf.st_dev == srcStbuf.st_dev &&
139 stbuf.st_ino == srcStbuf.st_ino) {
140 fprintf(stderr, "Attempt to copy file on itself\n");
141 return ERROR_ONE;
142 }
143 }
144
145 if( ask_confirmation("File \"%s\" exists, overwrite (y/n) ? ",
146 unixFile)) {
147 return ERROR_ONE;
148 }
149
150 }
151 }
152
153 if(!arg->type && arg->verbose) {
154 fprintf(stderr,"Copying ");
155 mpPrintFilename(stderr,mp);
156 fprintf(stderr,"\n");
157 }
158
159 if(got_signal) {
160 return ERROR_ONE;
161 }
162
163 if ((Target = SimpleFileOpen(0, 0, unixFile,
164 O_WRONLY | O_CREAT | O_TRUNC,
165 errmsg, 0, 0, 0))) {
166 mt_off_t ret;
167 Source = COPY(File);
168 if(needfilter && arg->textmode)
169 Source = open_dos2unix(Source,arg->convertCharset);
170
171 if (Source)
172 ret = copyfile(Source, Target);
173 else
174 ret = -1;
175 FREE(&Source);
176 FREE(&Target);
177 if(ret < 0){
178 if(!arg->type)
179 unlink(unixFile);
180 return ERROR_ONE;
181 }
182 if(!arg->type)
183 set_mtime(unixFile, mtime);
184 return GOT_ONE;
185 } else {
186 fprintf(stderr,"%s\n", errmsg);
187 return ERROR_ONE;
188 }
189 }
190
makeUnixDir(char * filename)191 static int makeUnixDir(char *filename)
192 {
193 if(!mkdir(filename
194 #ifndef OS_mingw32msvc
195 , 0777
196 #endif
197 ))
198 return 0;
199 if(errno == EEXIST) {
200 struct MT_STAT buf;
201 if(MT_STAT(filename, &buf) < 0)
202 return -1;
203 if(S_ISDIR(buf.st_mode))
204 return 0;
205 errno = ENOTDIR;
206 }
207 return -1;
208 }
209
210 /* Copy a directory to Unix */
unix_copydir(direntry_t * entry,MainParam_t * mp)211 static int unix_copydir(direntry_t *entry, MainParam_t *mp)
212 {
213 Arg_t *arg=(Arg_t *) mp->arg;
214 time_t mtime;
215 Stream_t *File=mp->File;
216 int ret;
217 char *unixFile;
218
219 if (!arg->recursive && mp->basenameHasWildcard)
220 return 0;
221
222 File->Class->get_data(File, &mtime, 0, 0, 0);
223 if (!arg->preserveTime)
224 mtime = 0L;
225 if(!arg->type && arg->verbose) {
226 fprintf(stderr,"Copying ");
227 fprintPwd(stderr, entry,0);
228 fprintf(stderr, "\n");
229 }
230 if(got_signal)
231 return ERROR_ONE;
232 unixFile = mpBuildUnixFilename(mp);
233 if(!unixFile) {
234 printOom();
235 return ERROR_ONE;
236 }
237 if(arg->type || !*mpPickTargetName(mp) || !makeUnixDir(unixFile)) {
238 Arg_t newArg;
239
240 newArg = *arg;
241 newArg.mp.arg = (void *) &newArg;
242 newArg.mp.unixTarget = unixFile;
243 newArg.mp.targetName = 0;
244 newArg.mp.basenameHasWildcard = 1;
245
246 ret = mp->loop(File, &newArg.mp, "*");
247 set_mtime(unixFile, mtime);
248 free(unixFile);
249 return ret | GOT_ONE;
250 } else {
251 perror("mkdir");
252 fprintf(stderr,
253 "Failure to make directory %s\n",
254 unixFile);
255 free(unixFile);
256 return ERROR_ONE;
257 }
258 }
259
dos_to_unix(direntry_t * entry UNUSEDP,MainParam_t * mp)260 static int dos_to_unix(direntry_t *entry UNUSEDP, MainParam_t *mp)
261 {
262 return unix_write(mp, 1);
263 }
264
265
unix_to_unix(MainParam_t * mp)266 static int unix_to_unix(MainParam_t *mp)
267 {
268 return unix_write(mp, 0);
269 }
270
271
directory_dos_to_unix(direntry_t * entry,MainParam_t * mp)272 static int directory_dos_to_unix(direntry_t *entry, MainParam_t *mp)
273 {
274 return unix_copydir(entry, mp);
275 }
276
277 /*
278 * Open the named file for read, create the cluster chain, return the
279 * directory structure or NULL on error.
280 */
writeit(struct dos_name_t * dosname,char * longname,void * arg0,direntry_t * entry)281 static int writeit(struct dos_name_t *dosname,
282 char *longname,
283 void *arg0,
284 direntry_t *entry)
285 {
286 Stream_t *Target;
287 time_t now;
288 int type;
289 mt_off_t ret;
290 uint32_t fat;
291 time_t date;
292 mt_off_t filesize;
293 Arg_t *arg = (Arg_t *) arg0;
294 Stream_t *Source = COPY(arg->mp.File);
295
296 if (Source->Class->get_data(Source, &date, &filesize,
297 &type, 0) < 0 ){
298 fprintf(stderr, "Can't stat source file\n");
299 return -1;
300 }
301
302 if(fileTooBig(filesize)) {
303 fprintf(stderr, "File \"%s\" too big\n", longname);
304 return 1;
305 }
306
307 if (type){
308 if (arg->verbose)
309 fprintf(stderr, "\"%s\" is a directory\n", longname);
310 return -1;
311 }
312
313 /*if (!arg->single || arg->recursive)*/
314 if(arg->verbose)
315 fprintf(stderr,"Copying %s\n", longname);
316 if(got_signal)
317 return -1;
318
319 /* will it fit? */
320 if (!getfreeMinBytes(arg->mp.targetDir, filesize))
321 return -1;
322
323 /* preserve mod time? */
324 if (arg->preserveTime)
325 now = date;
326 else
327 getTimeNow(&now);
328
329 mk_entry(dosname, arg->attr, 1, 0, now, &entry->dir);
330
331 Target = OpenFileByDirentry(entry);
332 if(!Target){
333 fprintf(stderr,"Could not open Target\n");
334 exit(1);
335 }
336 if (arg->needfilter & arg->textmode) {
337 Source = open_unix2dos(Source,arg->convertCharset);
338 }
339
340 ret = copyfile(Source, Target);
341 GET_DATA(Target, 0, 0, 0, &fat);
342 FREE(&Source);
343 FREE(&Target);
344 if(ret < 0 ){
345 fat_free(arg->mp.targetDir, fat);
346 return -1;
347 } else {
348 mk_entry(dosname, arg->attr, fat, (uint32_t)ret,
349 now, &entry->dir);
350 return 0;
351 }
352 }
353
354
355
dos_write(direntry_t * entry,MainParam_t * mp,int needfilter)356 static int dos_write(direntry_t *entry, MainParam_t *mp, int needfilter)
357 /* write a messy dos file to another messy dos file */
358 {
359 int result;
360 Arg_t * arg = (Arg_t *) (mp->arg);
361 const char *targetName = mpPickTargetName(mp);
362
363 if(entry && arg->preserveAttributes)
364 arg->attr = entry->dir.attr;
365 else
366 arg->attr = ATTR_ARCHIVE;
367
368 arg->needfilter = needfilter;
369 if (entry && mp->targetDir == entry->Dir){
370 arg->ch.ignore_entry = -1;
371 arg->ch.source = entry->entry;
372 } else {
373 arg->ch.ignore_entry = -1;
374 arg->ch.source = -2;
375 }
376 result = mwrite_one(mp->targetDir, targetName, 0,
377 writeit, (void *)arg, &arg->ch);
378 if(result == 1)
379 return GOT_ONE;
380 else
381 return ERROR_ONE;
382 }
383
subDir(Stream_t * parent,const char * filename)384 static Stream_t *subDir(Stream_t *parent, const char *filename)
385 {
386 direntry_t entry;
387 initializeDirentry(&entry, parent);
388
389 switch(vfat_lookup_zt(&entry, filename, ACCEPT_DIR, 0, 0, 0, 0)) {
390 case 0:
391 return OpenFileByDirentry(&entry);
392 case -1:
393 return NULL;
394 default: /* IO Error */
395 return NULL;
396 }
397 }
398
dos_copydir(direntry_t * entry,MainParam_t * mp)399 static int dos_copydir(direntry_t *entry, MainParam_t *mp)
400 /* copyes a directory to Dos */
401 {
402 Arg_t * arg = (Arg_t *) (mp->arg);
403 Arg_t newArg;
404 time_t now;
405 time_t date;
406 int ret;
407 const char *targetName = mpPickTargetName(mp);
408
409 if (!arg->recursive && mp->basenameHasWildcard)
410 return 0;
411
412 if(entry && isSubdirOf(mp->targetDir, mp->File)) {
413 fprintf(stderr, "Cannot recursively copy directory ");
414 fprintPwd(stderr, entry,0);
415 fprintf(stderr, " into one of its own subdirectories ");
416 fprintPwd(stderr, getDirentry(mp->targetDir),0);
417 fprintf(stderr, "\n");
418 return ERROR_ONE;
419 }
420
421 if (arg->mp.File->Class->get_data(arg->mp.File,
422 & date, 0, 0, 0) < 0 ){
423 fprintf(stderr, "Can't stat source file\n");
424 return ERROR_ONE;
425 }
426
427 if(!arg->type && arg->verbose)
428 fprintf(stderr,"Copying %s\n", mpGetBasename(mp));
429
430 if(entry && arg->preserveAttributes)
431 arg->attr = entry->dir.attr;
432 else
433 arg->attr = 0;
434
435 if (entry && (mp->targetDir == entry->Dir)){
436 arg->ch.ignore_entry = -1;
437 arg->ch.source = entry->entry;
438 } else {
439 arg->ch.ignore_entry = -1;
440 arg->ch.source = -2;
441 }
442
443 /* preserve mod time? */
444 if (arg->preserveTime)
445 now = date;
446 else
447 getTimeNow(&now);
448
449 newArg = *arg;
450 newArg.mp.arg = &newArg;
451 newArg.mp.targetName = 0;
452 newArg.mp.basenameHasWildcard = 1;
453 if(*targetName) {
454 /* maybe the directory already exist. Use it */
455 newArg.mp.targetDir = subDir(mp->targetDir, targetName);
456 if(!newArg.mp.targetDir)
457 newArg.mp.targetDir = createDir(mp->targetDir,
458 targetName,
459 &arg->ch, arg->attr,
460 now);
461 } else
462 newArg.mp.targetDir = mp->targetDir;
463
464 if(!newArg.mp.targetDir)
465 return ERROR_ONE;
466
467 ret = mp->loop(mp->File, &newArg.mp, "*");
468 if(*targetName)
469 FREE(&newArg.mp.targetDir);
470 return ret | GOT_ONE;
471 }
472
473
dos_to_dos(direntry_t * entry,MainParam_t * mp)474 static int dos_to_dos(direntry_t *entry, MainParam_t *mp)
475 {
476 return dos_write(entry, mp, 0);
477 }
478
unix_to_dos(MainParam_t * mp)479 static int unix_to_dos(MainParam_t *mp)
480 {
481 return dos_write(0, mp, 1);
482 }
483
484 static void usage(int ret) NORETURN;
usage(int ret)485 static void usage(int ret)
486 {
487 fprintf(stderr,
488 "Mtools version %s, dated %s\n", mversion, mdate);
489 fprintf(stderr,
490 "Usage: %s [-spatnmQVBT] [-D clash_option] sourcefile targetfile\n", progname);
491 fprintf(stderr,
492 " %s [-spatnmQVBT] [-D clash_option] sourcefile [sourcefiles...] targetdirectory\n",
493 progname);
494 exit(ret);
495 }
496
497 void mcopy(int argc, char **argv, int mtype) NORETURN;
mcopy(int argc,char ** argv,int mtype)498 void mcopy(int argc, char **argv, int mtype)
499 {
500 Arg_t arg;
501 int c, fastquit;
502
503
504 /* get command line options */
505
506 init_clash_handling(& arg.ch);
507
508 /* get command line options */
509 arg.recursive = 0;
510 arg.preserveTime = 0;
511 arg.preserveAttributes = 0;
512 arg.nowarn = 0;
513 arg.textmode = 0;
514 arg.verbose = 0;
515 arg.convertCharset = 0;
516 arg.type = mtype;
517 fastquit = 0;
518 if(helpFlag(argc, argv))
519 usage(0);
520 while ((c = getopt(argc, argv, "i:abB/sptTnmvQD:oh")) != EOF) {
521 switch (c) {
522 case 'i':
523 set_cmd_line_image(optarg);
524 break;
525 case 's':
526 case '/':
527 arg.recursive = 1;
528 break;
529 case 'p':
530 arg.preserveAttributes = 1;
531 break;
532 case 'T':
533 arg.convertCharset = 1;
534 /*-fallthrough*/
535 case 'a':
536 case 't':
537 arg.textmode = 1;
538 break;
539 case 'n':
540 arg.nowarn = 1;
541 break;
542 case 'm':
543 arg.preserveTime = 1;
544 break;
545 case 'v':
546 arg.verbose = 1;
547 break;
548 case 'Q':
549 fastquit = 1;
550 break;
551 case 'B':
552 case 'b':
553 batchmode = 1;
554 break;
555 case 'o':
556 handle_clash_options(&arg.ch, (char) c);
557 break;
558 case 'D':
559 if(handle_clash_options(&arg.ch, *optarg))
560 usage(1);
561 break;
562 case 'h':
563 usage(0);
564 case '?':
565 usage(1);
566 default:
567 break;
568
569 }
570 }
571
572 if (argc - optind < 1)
573 usage(1);
574
575 init_mp(&arg.mp);
576 arg.mp.lookupflags = ACCEPT_PLAIN | ACCEPT_DIR | DO_OPEN | NO_DOTS;
577 arg.mp.fast_quit = fastquit;
578 arg.mp.arg = (void *) &arg;
579 arg.mp.openflags = O_RDONLY;
580 arg.noClobber = 0;
581
582 /* last parameter is "-", use mtype mode */
583 if(!mtype && !strcmp(argv[argc-1], "-")) {
584 arg.type = mtype = 1;
585 argc--;
586 }
587
588 if(mtype){
589 /* Mtype = copying to stdout */
590 arg.mp.targetName = strdup("-");
591 arg.mp.unixTarget = strdup("");
592 arg.mp.callback = dos_to_unix;
593 arg.mp.dirCallback = unix_copydir;
594 arg.mp.unixcallback = unix_to_unix;
595 } else {
596 const char *target;
597 if (argc - optind == 1) {
598 /* copying to the current directory */
599 target = ".";
600 arg.noClobber = 1;
601 } else {
602 /* target is the last item mentioned */
603 argc--;
604 target = argv[argc];
605 }
606
607 target_lookup(&arg.mp, target);
608 if(!arg.mp.targetDir && !arg.mp.unixTarget) {
609 fprintf(stderr,"Bad target %s\n", target);
610 exit(1);
611 }
612
613 /* callback functions */
614 if(arg.mp.unixTarget) {
615 arg.mp.callback = dos_to_unix;
616 arg.mp.dirCallback = directory_dos_to_unix;
617 arg.mp.unixcallback = unix_to_unix;
618 } else {
619 arg.mp.dirCallback = dos_copydir;
620 arg.mp.callback = dos_to_dos;
621 arg.mp.unixcallback = unix_to_dos;
622 }
623 }
624
625 exit(main_loop(&arg.mp, argv + optind, argc - optind));
626 }
627