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