1/*  Copyright 1996-1998,2000-2002,2005,2007-2009 Alain Knaff.
2 *  This file is part of mtools.
3 *
4 *  Mtools is free software: you can redistribute it and/or modify
5 *  it under the terms of the GNU General Public License as published by
6 *  the Free Software Foundation, either version 3 of the License, or
7 *  (at your option) any later version.
8 *
9 *  Mtools is distributed in the hope that it will be useful,
10 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 *  GNU General Public License for more details.
13 *
14 *  You should have received a copy of the GNU General Public License
15 *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
16 *
17 * mmove.c
18 * Renames/moves an MSDOS file
19 *
20 */
21
22
23#define LOWERCASE
24
25#include "sysincludes.h"
26#include "msdos.h"
27#include "mtools.h"
28#include "vfat.h"
29#include "mainloop.h"
30#include "plain_io.h"
31#include "nameclash.h"
32#include "file.h"
33#include "fs.h"
34
35/*
36 * Preserve the file modification times after the fclose()
37 */
38
39typedef struct Arg_t {
40	const char *fromname;
41	int verbose;
42	MainParam_t mp;
43
44	direntry_t *entry;
45	ClashHandling_t ch;
46} Arg_t;
47
48
49/*
50 * Open the named file for read, create the cluster chain, return the
51 * directory structure or NULL on error.
52 */
53static int renameit(dos_name_t *dosname,
54		    char *longname,
55		    void *arg0,
56		    direntry_t *targetEntry)
57{
58	Arg_t *arg = (Arg_t *) arg0;
59	int fat;
60
61	targetEntry->dir = arg->entry->dir;
62	dosnameToDirentry(dosname, &targetEntry->dir);
63
64	if(IS_DIR(targetEntry)) {
65		direntry_t *movedEntry;
66
67		/* get old direntry. It is important that we do this
68		 * on the actual direntry which is stored in the file,
69		 * and not on a copy, because we will modify it, and the
70		 * modification should be visible at file
71		 * de-allocation time */
72		movedEntry = getDirentry(arg->mp.File);
73		if(movedEntry->Dir != targetEntry->Dir) {
74			/* we are indeed moving it to a new directory */
75			direntry_t subEntry;
76			Stream_t *oldDir;
77			/* we have a directory here. Change its parent link */
78
79			initializeDirentry(&subEntry, arg->mp.File);
80
81			switch(vfat_lookup(&subEntry, "..", 2, ACCEPT_DIR,
82					   NULL, NULL)) {
83			    case -1:
84				fprintf(stderr,
85					" Directory has no parent entry\n");
86				break;
87			    case -2:
88				return ERROR_ONE;
89			    case 0:
90				GET_DATA(targetEntry->Dir, 0, 0, 0, &fat);
91				if (fat == fat32RootCluster(targetEntry->Dir)) {
92				    fat = 0;
93				}
94
95				subEntry.dir.start[1] = (fat >> 8) & 0xff;
96				subEntry.dir.start[0] = fat & 0xff;
97				dir_write(&subEntry);
98				if(arg->verbose){
99					fprintf(stderr,
100						"Easy, isn't it? I wonder why DOS can't do this.\n");
101				}
102				break;
103			}
104
105			wipeEntry(movedEntry);
106
107			/* free the old parent, allocate the new one. */
108			oldDir = movedEntry->Dir;
109			*movedEntry = *targetEntry;
110			COPY(targetEntry->Dir);
111			FREE(&oldDir);
112			return 0;
113		}
114	}
115
116	/* wipe out original entry */
117	wipeEntry(arg->mp.direntry);
118	return 0;
119}
120
121
122
123static int rename_file(direntry_t *entry, MainParam_t *mp)
124/* rename a messy DOS file to another messy DOS file */
125{
126	int result;
127	Stream_t *targetDir;
128	char *shortname;
129	const char *longname;
130
131	Arg_t * arg = (Arg_t *) (mp->arg);
132
133	arg->entry = entry;
134	targetDir = mp->targetDir;
135
136	if (targetDir == entry->Dir){
137		arg->ch.ignore_entry = -1;
138		arg->ch.source = entry->entry;
139		arg->ch.source_entry = entry->entry;
140	} else {
141		arg->ch.ignore_entry = -1;
142		arg->ch.source = -2;
143	}
144
145	longname = mpPickTargetName(mp);
146	shortname = 0;
147	result = mwrite_one(targetDir, longname, shortname,
148			    renameit, (void *)arg, &arg->ch);
149	if(result == 1)
150		return GOT_ONE;
151	else
152		return ERROR_ONE;
153}
154
155
156static int rename_directory(direntry_t *entry, MainParam_t *mp)
157{
158	int ret;
159
160	/* moves a DOS dir */
161	if(isSubdirOf(mp->targetDir, mp->File)) {
162		fprintf(stderr, "Cannot move directory ");
163		fprintPwd(stderr, entry,0);
164		fprintf(stderr, " into one of its own subdirectories (");
165		fprintPwd(stderr, getDirentry(mp->targetDir),0);
166		fprintf(stderr, ")\n");
167		return ERROR_ONE;
168	}
169
170	if(entry->entry == -3) {
171		fprintf(stderr, "Cannot move a root directory: ");
172		fprintPwd(stderr, entry,0);
173		return ERROR_ONE;
174	}
175
176	ret = rename_file(entry, mp);
177	if(ret & ERROR_ONE)
178		return ret;
179
180	return ret;
181}
182
183static int rename_oldsyntax(direntry_t *entry, MainParam_t *mp)
184{
185	int result;
186	Stream_t *targetDir;
187	const char *shortname, *longname;
188
189	Arg_t * arg = (Arg_t *) (mp->arg);
190	arg->entry = entry;
191	targetDir = entry->Dir;
192
193	arg->ch.ignore_entry = -1;
194	arg->ch.source = entry->entry;
195	arg->ch.source_entry = entry->entry;
196
197#if 0
198	if(!strcasecmp(mp->shortname, arg->fromname)){
199		longname = mp->longname;
200		shortname = mp->targetName;
201	} else {
202#endif
203		longname = mp->targetName;
204		shortname = 0;
205#if 0
206	}
207#endif
208	result = mwrite_one(targetDir, longname, shortname,
209			    renameit, (void *)arg, &arg->ch);
210	if(result == 1)
211		return GOT_ONE;
212	else
213		return ERROR_ONE;
214}
215
216
217static void usage(int ret) NORETURN;
218static void usage(int ret)
219{
220	fprintf(stderr,
221		"Mtools version %s, dated %s\n", mversion, mdate);
222	fprintf(stderr,
223		"Usage: %s [-vV] [-D clash_option] file targetfile\n", progname);
224	fprintf(stderr,
225		"       %s [-vV] [-D clash_option] file [files...] target_directory\n",
226		progname);
227	exit(ret);
228}
229
230void mmove(int argc, char **argv, int oldsyntax)
231{
232	Arg_t arg;
233	int c;
234	char shortname[13];
235	char longname[VBUFSIZE];
236	char def_drive;
237	int i;
238
239	/* get command line options */
240
241	init_clash_handling(& arg.ch);
242
243	/* get command line options */
244	arg.verbose = 0;
245	if(helpFlag(argc, argv))
246		usage(0);
247	while ((c = getopt(argc, argv, "i:vD:oh")) != EOF) {
248		switch (c) {
249			case 'i':
250				set_cmd_line_image(optarg, 0);
251				break;
252			case 'v':	/* dummy option for mcopy */
253				arg.verbose = 1;
254				break;
255			case 'o':
256				handle_clash_options(&arg.ch, c);
257				break;
258			case 'D':
259				if(handle_clash_options(&arg.ch, *optarg))
260					usage(1);
261				break;
262			case 'h':
263				usage(0);
264			case '?':
265				usage(1);
266			default:
267				break;
268		}
269	}
270
271	if (argc - optind < 2)
272		usage(1);
273
274	init_mp(&arg.mp);
275	arg.mp.arg = (void *) &arg;
276	arg.mp.openflags = O_RDWR;
277
278	/* look for a default drive */
279	def_drive = '\0';
280	for(i=optind; i<argc; i++)
281		if(argv[i][0] && argv[i][1] == ':' ){
282			if(!def_drive)
283				def_drive = toupper(argv[i][0]);
284			else if(def_drive != toupper(argv[i][0])){
285				fprintf(stderr,
286					"Cannot move files across different drives\n");
287				exit(1);
288			}
289		}
290
291	if(def_drive)
292		*(arg.mp.mcwd) = def_drive;
293
294	if (oldsyntax && (argc - optind != 2 || strpbrk(":/", argv[argc-1])))
295		oldsyntax = 0;
296
297	arg.mp.lookupflags =
298	  ACCEPT_PLAIN | ACCEPT_DIR | DO_OPEN_DIRS | NO_DOTS | NO_UNIX;
299
300	if (!oldsyntax){
301		target_lookup(&arg.mp, argv[argc-1]);
302		arg.mp.callback = rename_file;
303		arg.mp.dirCallback = rename_directory;
304	} else {
305		/* do not look up the target; it will be the same dir as the
306		 * source */
307		arg.fromname = argv[optind];
308		if(arg.fromname[0] && arg.fromname[1] == ':')
309			arg.fromname += 2;
310		arg.fromname = _basename(arg.fromname);
311		arg.mp.targetName = strdup(argv[argc-1]);
312		arg.mp.callback = rename_oldsyntax;
313	}
314
315
316	arg.mp.longname = longname;
317	longname[0]='\0';
318
319	arg.mp.shortname = shortname;
320	shortname[0]='\0';
321
322	exit(main_loop(&arg.mp, argv + optind, argc - optind - 1));
323}
324