1/*  Copyright 1995-1998,2000-2003,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 * mk_direntry.c
18 * Make new directory entries, and handles name clashes
19 *
20 */
21
22/*
23 * This file is used by those commands that need to create new directory entries
24 */
25
26#include "sysincludes.h"
27#include "msdos.h"
28#include "mtools.h"
29#include "vfat.h"
30#include "nameclash.h"
31#include "fs.h"
32#include "stream.h"
33#include "mainloop.h"
34#include "file_name.h"
35
36/**
37 * Converts input to shortname
38 * @param un unix name (in Unix charset)
39 *
40 * @return 1 if name had to be mangled
41 */
42static __inline__ int convert_to_shortname(doscp_t *cp, ClashHandling_t *ch,
43					   const char *un, dos_name_t *dn)
44{
45	int mangled;
46
47	/* Then do conversion to dn */
48	ch->name_converter(cp, un, 0, &mangled, dn);
49	dn->sentinel = '\0';
50	return mangled;
51}
52
53static __inline__ void chomp(char *line)
54{
55	int l = strlen(line);
56	while(l > 0 && (line[l-1] == '\n' || line[l-1] == '\r')) {
57		line[--l] = '\0';
58	}
59}
60
61/**
62 * Asks for an alternative new name for a file, in case of a clash
63 */
64static __inline__ int ask_rename(doscp_t *cp, ClashHandling_t *ch,
65				 dos_name_t *shortname,
66				 char *longname,
67				 int isprimary)
68{
69	int mangled;
70
71	/* TODO: Would be nice to suggest "autorenamed" version of name, press
72	 * <Return> to get it.
73	 */
74#if 0
75	fprintf(stderr,"Entering ask_rename, isprimary=%d.\n", isprimary);
76#endif
77
78	if(!opentty(0))
79		return 0;
80
81#define maxsize (isprimary ?  MAX_VNAMELEN+1 : 11+1)
82#define name (isprimary ? argname : shortname)
83
84	mangled = 0;
85	do {
86		char tname[4*MAX_VNAMELEN+1];
87		fprintf(stderr, "New %s name for \"%s\": ",
88			isprimary ? "primary" : "secondary", longname);
89		fflush(stderr);
90		if (! fgets(tname, 4*MAX_VNAMELEN+1, opentty(0)))
91			return 0;
92		chomp(tname);
93		if (isprimary)
94			strcpy(longname, tname);
95		else
96			mangled = convert_to_shortname(cp,
97						       ch, tname, shortname);
98	} while (mangled & 1);
99	return 1;
100#undef maxsize
101#undef name
102}
103
104/**
105 * This function determines the action to be taken in case there is a problem
106 * with target name (clash, illegal characters, or reserved)
107 * The decision either comes from the default (ch), or the user will be
108 * prompted if there is no default
109 */
110static __inline__ clash_action ask_namematch(doscp_t *cp,
111					     dos_name_t *dosname,
112					     char *longname,
113					     int isprimary,
114					     ClashHandling_t *ch,
115					     int no_overwrite,
116					     int reason)
117{
118	/* User's answer letter (from keyboard). Only first letter is used,
119	 * but we allocate space for 10 in order to account for extra garbage
120	 * that user may enter
121	 */
122	char ans[10];
123
124	/**
125	 * Return value: action to be taken
126	 */
127	clash_action a;
128
129	/**
130	 * Should this decision be made permanent (do no longer ask same
131	 * question)
132	 */
133	int perm;
134
135	/**
136	 * Buffer for shortname
137	 */
138	char name_buffer[4*13];
139
140	/**
141	 * Name to be printed
142	 */
143	char *name;
144
145#define EXISTS 0
146#define RESERVED 1
147#define ILLEGALS 2
148
149	static const char *reasons[]= {
150		"already exists",
151		"is reserved",
152		"contains illegal character(s)"};
153
154	a = ch->action[isprimary];
155
156	if(a == NAMEMATCH_NONE && !opentty(1)) {
157		/* no default, and no tty either . Skip the troublesome file */
158		return NAMEMATCH_SKIP;
159	}
160
161	if (!isprimary)
162		name = unix_normalize(cp, name_buffer, dosname);
163	else
164		name = longname;
165
166	perm = 0;
167	while (a == NAMEMATCH_NONE) {
168		fprintf(stderr, "%s file name \"%s\" %s.\n",
169			isprimary ? "Long" : "Short", name, reasons[reason]);
170		fprintf(stderr,
171			"a)utorename A)utorename-all r)ename R)ename-all ");
172		if(!no_overwrite)
173			fprintf(stderr,"o)verwrite O)verwrite-all");
174		fprintf(stderr,
175			"\ns)kip S)kip-all q)uit (aArR");
176		if(!no_overwrite)
177			fprintf(stderr,"oO");
178		fprintf(stderr,"sSq): ");
179		fflush(stderr);
180		fflush(opentty(1));
181		if (mtools_raw_tty) {
182			int rep;
183			rep = fgetc(opentty(1));
184			fputs("\n", stderr);
185			if(rep == EOF)
186				ans[0] = 'q';
187			else
188				ans[0] = rep;
189		} else {
190			fgets(ans, 9, opentty(0));
191		}
192		perm = isupper((unsigned char)ans[0]);
193		switch(tolower((unsigned char)ans[0])) {
194			case 'a':
195				a = NAMEMATCH_AUTORENAME;
196				break;
197			case 'r':
198				if(isprimary)
199					a = NAMEMATCH_PRENAME;
200				else
201					a = NAMEMATCH_RENAME;
202				break;
203			case 'o':
204				if(no_overwrite)
205					continue;
206				a = NAMEMATCH_OVERWRITE;
207				break;
208			case 's':
209				a = NAMEMATCH_SKIP;
210				break;
211			case 'q':
212				perm = 0;
213				a = NAMEMATCH_QUIT;
214				break;
215			default:
216				perm = 0;
217		}
218	}
219
220	/* Keep track of this action in case this file collides again */
221	ch->action[isprimary]  = a;
222	if (perm)
223		ch->namematch_default[isprimary] = a;
224
225	/* if we were asked to overwrite be careful. We can't set the action
226	 * to overwrite, else we get won't get a chance to specify another
227	 * action, should overwrite fail. Indeed, we'll be caught in an
228	 * infinite loop because overwrite will fail the same way for the
229	 * second time */
230	if(a == NAMEMATCH_OVERWRITE)
231		ch->action[isprimary] = NAMEMATCH_NONE;
232	return a;
233}
234
235/*
236 * Processes a name match
237 *  dosname short dosname (ignored if is_primary)
238 *
239 *
240 * Returns:
241 * 2 if file is to be overwritten
242 * 1 if file was renamed
243 * 0 if it was skipped
244 *
245 * If a short name is involved, handle conversion between the 11-character
246 * fixed-length record DOS name and a literal null-terminated name (e.g.
247 * "COMMAND  COM" (no null) <-> "COMMAND.COM" (null terminated)).
248 *
249 * Also, immediately copy the original name so that messages can use it.
250 */
251static __inline__ clash_action process_namematch(doscp_t *cp,
252						 dos_name_t *dosname,
253						 char *longname,
254						 int isprimary,
255						 ClashHandling_t *ch,
256						 int no_overwrite,
257						 int reason)
258{
259	clash_action action;
260
261#if 0
262	fprintf(stderr,
263		"process_namematch: name=%s, default_action=%d, ask=%d.\n",
264		name, default_action, ch->ask);
265#endif
266
267	action = ask_namematch(cp, dosname, longname,
268			       isprimary, ch, no_overwrite, reason);
269
270	switch(action){
271	case NAMEMATCH_QUIT:
272		got_signal = 1;
273		return NAMEMATCH_SKIP;
274	case NAMEMATCH_SKIP:
275		return NAMEMATCH_SKIP;
276	case NAMEMATCH_RENAME:
277	case NAMEMATCH_PRENAME:
278		/* We need to rename the file now.  This means we must pass
279		 * back through the loop, a) ensuring there isn't a potential
280		 * new name collision, and b) finding a big enough VSE.
281		 * Change the name, so that it won't collide again.
282		 */
283		ask_rename(cp, ch, dosname, longname, isprimary);
284		return action;
285	case NAMEMATCH_AUTORENAME:
286		/* Very similar to NAMEMATCH_RENAME, except that we need to
287		 * first generate the name.
288		 * TODO: Remember previous name so we don't
289		 * keep trying the same one.
290		 */
291		if (isprimary) {
292			autorename_long(longname, 1);
293			return NAMEMATCH_PRENAME;
294		} else {
295			autorename_short(dosname, 1);
296			return NAMEMATCH_RENAME;
297		}
298	case NAMEMATCH_OVERWRITE:
299		if(no_overwrite)
300			return NAMEMATCH_SKIP;
301		else
302			return NAMEMATCH_OVERWRITE;
303	default:
304		return NAMEMATCH_NONE;
305	}
306}
307
308static int contains_illegals(const char *string, const char *illegals,
309			     int len)
310{
311	for(; *string && len--; string++)
312		if((*string < ' ' && *string != '\005' && !(*string & 0x80)) ||
313		   strchr(illegals, *string))
314			return 1;
315	return 0;
316}
317
318static int is_reserved(char *ans, int islong)
319{
320	unsigned int i;
321	static const char *dev3[] = {"CON", "AUX", "PRN", "NUL", "   "};
322	static const char *dev4[] = {"COM", "LPT" };
323
324	for (i = 0; i < sizeof(dev3)/sizeof(*dev3); i++)
325		if (!strncasecmp(ans, dev3[i], 3) &&
326		    ((islong && !ans[3]) ||
327		     (!islong && !strncmp(ans+3,"     ",5))))
328			return 1;
329
330	for (i = 0; i < sizeof(dev4)/sizeof(*dev4); i++)
331		if (!strncasecmp(ans, dev4[i], 3) &&
332		    (ans[3] >= '1' && ans[3] <= '4') &&
333		    ((islong && !ans[4]) ||
334		     (!islong && !strncmp(ans+4,"    ",4))))
335			return 1;
336
337	return 0;
338}
339
340static __inline__ clash_action get_slots(Stream_t *Dir,
341					 dos_name_t *dosname,
342					 char *longname,
343					 struct scan_state *ssp,
344					 ClashHandling_t *ch)
345{
346	int error;
347	clash_action ret;
348	int match_pos=0;
349	direntry_t entry;
350	int isprimary;
351	int no_overwrite;
352	int reason;
353	int pessimisticShortRename;
354	doscp_t *cp = GET_DOSCONVERT(Dir);
355
356	pessimisticShortRename = (ch->action[0] == NAMEMATCH_AUTORENAME);
357
358	entry.Dir = Dir;
359	no_overwrite = 1;
360	if((is_reserved(longname,1)) ||
361	   longname[strspn(longname,". ")] == '\0'){
362		reason = RESERVED;
363		isprimary = 1;
364	} else if(contains_illegals(longname,long_illegals,1024)) {
365		reason = ILLEGALS;
366		isprimary = 1;
367	} else if(is_reserved(dosname->base,0)) {
368		reason = RESERVED;
369		ch->use_longname = 1;
370		isprimary = 0;
371	} else if(contains_illegals(dosname->base,short_illegals,11)) {
372		reason = ILLEGALS;
373		ch->use_longname = 1;
374		isprimary = 0;
375	} else {
376		reason = EXISTS;
377		switch (lookupForInsert(Dir,
378					&entry,
379					dosname, longname, ssp,
380					ch->ignore_entry,
381					ch->source_entry,
382					pessimisticShortRename &&
383					ch->use_longname,
384					ch->use_longname)) {
385			case -1:
386				return NAMEMATCH_ERROR;
387
388			case 0:
389				return NAMEMATCH_SKIP;
390				/* Single-file error error or skip request */
391
392			case 5:
393				return NAMEMATCH_GREW;
394				/* Grew directory, try again */
395
396			case 6:
397				return NAMEMATCH_SUCCESS; /* Success */
398		}
399		match_pos = -2;
400		if (ssp->longmatch > -1) {
401			/* Primary Long Name Match */
402#ifdef debug
403			fprintf(stderr,
404				"Got longmatch=%d for name %s.\n",
405				longmatch, longname);
406#endif
407			match_pos = ssp->longmatch;
408			isprimary = 1;
409		} else if ((ch->use_longname & 1) && (ssp->shortmatch != -1)) {
410			/* Secondary Short Name Match */
411#ifdef debug
412			fprintf(stderr,
413				"Got secondary short name match for name %s.\n",
414				longname);
415#endif
416
417			match_pos = ssp->shortmatch;
418			isprimary = 0;
419		} else if (ssp->shortmatch >= 0) {
420			/* Primary Short Name Match */
421#ifdef debug
422			fprintf(stderr,
423				"Got primary short name match for name %s.\n",
424				longname);
425#endif
426			match_pos = ssp->shortmatch;
427			isprimary = 1;
428		} else
429			return NAMEMATCH_RENAME;
430
431		if(match_pos > -1) {
432			entry.entry = match_pos;
433			dir_read(&entry, &error);
434			if (error)
435			    return NAMEMATCH_ERROR;
436			/* if we can't overwrite, don't propose it */
437			no_overwrite = (match_pos == ch->source || IS_DIR(&entry));
438		}
439	}
440	ret = process_namematch(cp, dosname, longname,
441				isprimary, ch, no_overwrite, reason);
442
443	if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){
444		if((entry.dir.attr & 0x5) &&
445		   (ask_confirmation("file is read only, overwrite anyway (y/n) ? ")))
446			return NAMEMATCH_RENAME;
447		/* Free up the file to be overwritten */
448		if(fatFreeWithDirentry(&entry))
449			return NAMEMATCH_ERROR;
450
451#if 0
452		if(isprimary &&
453		   match_pos - ssp->match_free + 1 >= ssp->size_needed){
454			/* reuse old entry and old short name for overwrite */
455			ssp->free_start = match_pos - ssp->size_needed + 1;
456			ssp->free_size = ssp->size_needed;
457			ssp->slot = match_pos;
458			ssp->got_slots = 1;
459			strncpy(dosname, dir.name, 3);
460			strncpy(dosname + 8, dir.ext, 3);
461			return ret;
462		} else
463#endif
464			{
465			wipeEntry(&entry);
466			return NAMEMATCH_RENAME;
467		}
468	}
469
470	return ret;
471}
472
473
474static __inline__ int write_slots(Stream_t *Dir,
475				  dos_name_t *dosname,
476				  char *longname,
477				  struct scan_state *ssp,
478				  write_data_callback *cb,
479				  void *arg,
480				  int Case)
481{
482	direntry_t entry;
483
484	/* write the file */
485	if (fat_error(Dir))
486		return 0;
487
488	entry.Dir = Dir;
489	entry.entry = ssp->slot;
490	native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0);
491	entry.name[MAX_VNAMELEN]='\0';
492	entry.dir.Case = Case & (EXTCASE | BASECASE);
493	if (cb(dosname, longname, arg, &entry) >= 0) {
494		if ((ssp->size_needed > 1) &&
495		    (ssp->free_end - ssp->free_start >= ssp->size_needed)) {
496			ssp->slot = write_vfat(Dir, dosname, longname,
497					       ssp->free_start, &entry);
498		} else {
499			ssp->size_needed = 1;
500			write_vfat(Dir, dosname, 0,
501				   ssp->free_start, &entry);
502		}
503		/* clear_vses(Dir, ssp->free_start + ssp->size_needed,
504		   ssp->free_end); */
505	} else
506		return 0;
507
508	return 1;	/* Successfully wrote the file */
509}
510
511static void stripspaces(char *name)
512{
513	char *p,*non_space;
514
515	non_space = name;
516	for(p=name; *p; p++)
517		if (*p != ' ')
518			non_space = p;
519	if(name[0])
520		non_space[1] = '\0';
521}
522
523
524static int _mwrite_one(Stream_t *Dir,
525		       char *argname,
526		       char *shortname,
527		       write_data_callback *cb,
528		       void *arg,
529		       ClashHandling_t *ch)
530{
531	char longname[VBUFSIZE];
532	const char *dstname;
533	dos_name_t dosname;
534	int expanded;
535	struct scan_state scan;
536	clash_action ret;
537	doscp_t *cp = GET_DOSCONVERT(Dir);
538
539	expanded = 0;
540
541	if(isSpecial(argname)) {
542		fprintf(stderr, "Cannot create entry named . or ..\n");
543		return -1;
544	}
545
546	if(ch->name_converter == dos_name) {
547		if(shortname)
548			stripspaces(shortname);
549		if(argname)
550			stripspaces(argname);
551	}
552
553	if(shortname){
554		convert_to_shortname(cp, ch, shortname, &dosname);
555		if(ch->use_longname & 1){
556			/* short name mangled, treat it as a long name */
557			argname = shortname;
558			shortname = 0;
559		}
560	}
561
562	if (argname[0] && (argname[1] == ':')) {
563		/* Skip drive letter */
564		dstname = argname + 2;
565	} else {
566		dstname = argname;
567	}
568
569	/* Copy original argument dstname to working value longname */
570	strncpy(longname, dstname, VBUFSIZE-1);
571
572	if(shortname) {
573		ch->use_longname =
574			convert_to_shortname(cp, ch, shortname, &dosname);
575		if(strcmp(shortname, longname))
576			ch->use_longname |= 1;
577	} else {
578		ch->use_longname =
579			convert_to_shortname(cp, ch, longname, &dosname);
580	}
581
582	ch->action[0] = ch->namematch_default[0];
583	ch->action[1] = ch->namematch_default[1];
584
585	while (1) {
586		switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){
587			case NAMEMATCH_ERROR:
588				return -1;	/* Non-file-specific error,
589						 * quit */
590
591			case NAMEMATCH_SKIP:
592				return -1;	/* Skip file (user request or
593						 * error) */
594
595			case NAMEMATCH_PRENAME:
596				ch->use_longname =
597					convert_to_shortname(cp, ch,
598							     longname,
599							     &dosname);
600				continue;
601			case NAMEMATCH_RENAME:
602				continue;	/* Renamed file, loop again */
603
604			case NAMEMATCH_GREW:
605				/* No collision, and not enough slots.
606				 * Try to grow the directory
607				 */
608				if (expanded) {	/* Already tried this
609						 * once, no good */
610					fprintf(stderr,
611						"%s: No directory slots\n",
612						progname);
613					return -1;
614				}
615				expanded = 1;
616
617				if (dir_grow(Dir, scan.max_entry))
618					return -1;
619				continue;
620			case NAMEMATCH_OVERWRITE:
621			case NAMEMATCH_SUCCESS:
622				return write_slots(Dir, &dosname, longname,
623						   &scan, cb, arg,
624						   ch->use_longname);
625			default:
626				fprintf(stderr,
627					"Internal error: clash_action=%d\n",
628					ret);
629				return -1;
630		}
631
632	}
633}
634
635int mwrite_one(Stream_t *Dir,
636	       const char *_argname,
637	       const char *_shortname,
638	       write_data_callback *cb,
639	       void *arg,
640	       ClashHandling_t *ch)
641{
642	char *argname;
643	char *shortname;
644	int ret;
645
646	if(_argname)
647		argname = strdup(_argname);
648	else
649		argname = 0;
650	if(_shortname)
651		shortname = strdup(_shortname);
652	else
653		shortname = 0;
654	ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch);
655	if(argname)
656		free(argname);
657	if(shortname)
658		free(shortname);
659	return ret;
660}
661
662void init_clash_handling(ClashHandling_t *ch)
663{
664	ch->ignore_entry = -1;
665	ch->source_entry = -2;
666	ch->nowarn = 0;	/*Don't ask, just do default action if name collision */
667	ch->namematch_default[0] = NAMEMATCH_AUTORENAME;
668	ch->namematch_default[1] = NAMEMATCH_NONE;
669	ch->name_converter = dos_name; /* changed by mlabel */
670	ch->source = -2;
671}
672
673int handle_clash_options(ClashHandling_t *ch, char c)
674{
675	int isprimary;
676	if(isupper(c))
677		isprimary = 0;
678	else
679		isprimary = 1;
680	c = tolower(c);
681	switch(c) {
682		case 'o':
683			/* Overwrite if primary name matches */
684			ch->namematch_default[isprimary] = NAMEMATCH_OVERWRITE;
685			return 0;
686		case 'r':
687				/* Rename primary name interactively */
688			ch->namematch_default[isprimary] = NAMEMATCH_RENAME;
689			return 0;
690		case 's':
691			/* Skip file if primary name collides */
692			ch->namematch_default[isprimary] = NAMEMATCH_SKIP;
693			return 0;
694		case 'm':
695			ch->namematch_default[isprimary] = NAMEMATCH_NONE;
696			return 0;
697		case 'a':
698			ch->namematch_default[isprimary] = NAMEMATCH_AUTORENAME;
699			return 0;
700		default:
701			return -1;
702	}
703}
704
705void dosnameToDirentry(const struct dos_name_t *dn, struct directory *dir) {
706	strncpy(dir->name, dn->base, 8);
707	strncpy(dir->ext, dn->ext, 3);
708}
709