1/*  Copyright 1997-2002,2005-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 * mainloop.c
18 * Iterating over all the command line parameters, and matching patterns
19 * where needed
20 */
21
22#include "sysincludes.h"
23#include "msdos.h"
24#include "mtools.h"
25#include "vfat.h"
26#include "fs.h"
27#include "mainloop.h"
28#include "plain_io.h"
29#include "file.h"
30#include "file_name.h"
31
32
33/* Fix the info in the MCWD file to be a proper directory name.
34 * Always has a leading separator.  Never has a trailing separator
35 * (unless it is the path itself).  */
36
37static const char *fix_mcwd(char *ans)
38{
39	FILE *fp;
40	char *s;
41	char buf[MAX_PATH];
42
43	fp = open_mcwd("r");
44	if(!fp || !fgets(buf, MAX_PATH, fp)) {
45		if(fp)
46			fclose(fp);
47		ans[0] = get_default_drive();
48		strcpy(ans+1, ":/");
49		return ans;
50	}
51
52	buf[strlen(buf) -1] = '\0';
53	fclose(fp);
54					/* drive letter present? */
55	s = buf;
56	if (buf[0] && buf[1] == ':') {
57		strncpy(ans, buf, 2);
58		ans[2] = '\0';
59		s = &buf[2];
60	} else {
61		ans[0] = get_default_drive();
62		strcpy(ans+1, ":");
63	}
64			/* add a leading separator */
65	if (*s != '/' && *s != '\\') {
66		strcat(ans, "/");
67		strcat(ans, s);
68	} else
69		strcat(ans, s);
70
71#if 0
72					/* translate to upper case */
73	for (s = ans; *s; ++s) {
74		*s = toupper(*s);
75		if (*s == '\\')
76			*s = '/';
77	}
78#endif
79					/* if only drive, colon, & separator */
80	if (strlen(ans) == 3)
81		return(ans);
82					/* zap the trailing separator */
83	if (*--s == '/')
84		*s = '\0';
85	return ans;
86}
87
88int unix_dir_loop(Stream_t *Stream, MainParam_t *mp);
89int unix_loop(Stream_t *Stream, MainParam_t *mp, char *arg,
90	      int follow_dir_link);
91
92static int _unix_loop(Stream_t *Dir, MainParam_t *mp, const char *filename)
93{
94	return unix_dir_loop(Dir, mp);
95}
96
97int unix_loop(Stream_t *Stream, MainParam_t *mp, char *arg, int follow_dir_link)
98{
99	int ret;
100	int isdir;
101	int unixNameLength;
102
103	mp->File = NULL;
104	mp->direntry = NULL;
105	unixNameLength = strlen(arg);
106	if(unixNameLength > 1 && arg[unixNameLength-1] == '/') {
107	    /* names ending in slash, and having at least two characters */
108	    char *name = strdup(arg);
109	    name[unixNameLength-1]='\0';
110	    mp->unixSourceName = name;
111	} else {
112	    mp->unixSourceName = arg;
113	}
114	/*	mp->dir.attr = ATTR_ARCHIVE;*/
115	mp->loop = _unix_loop;
116	if((mp->lookupflags & DO_OPEN)){
117		mp->File = SimpleFileOpen(0, 0, arg, O_RDONLY, 0, 0, 0, 0);
118		if(!mp->File){
119			perror(arg);
120#if 0
121			tmp = _basename(arg);
122			strncpy(mp->filename, tmp, VBUFSIZE);
123			mp->filename[VBUFSIZE-1] = '\0';
124#endif
125			return ERROR_ONE;
126		}
127		GET_DATA(mp->File, 0, 0, &isdir, 0);
128		if(isdir) {
129#if !defined(__EMX__) && !defined(OS_mingw32msvc)
130			struct MT_STAT buf;
131#endif
132
133			FREE(&mp->File);
134#if !defined(__EMX__) && !defined(OS_mingw32msvc)
135			if(!follow_dir_link &&
136			   MT_LSTAT(arg, &buf) == 0 &&
137			   S_ISLNK(buf.st_mode)) {
138				/* skip links to directories in order to avoid
139				 * infinite loops */
140				fprintf(stderr,
141					"skipping directory symlink %s\n",
142					arg);
143				return 0;
144			}
145#endif
146			if(! (mp->lookupflags & ACCEPT_DIR))
147				return 0;
148			mp->File = OpenDir(Stream, arg);
149		}
150	}
151
152	if(isdir)
153		ret = mp->dirCallback(0, mp);
154	else
155		ret = mp->unixcallback(mp);
156	FREE(&mp->File);
157	return ret;
158}
159
160
161int isSpecial(const char *name)
162{
163	if(name[0] == '\0')
164		return 1;
165	if(!strcmp(name,"."))
166		return 1;
167	if(!strcmp(name,".."))
168		return 1;
169	return 0;
170}
171
172#ifdef HAVE_WCHAR_H
173int isSpecialW(const wchar_t *name)
174{
175	if(name[0] == '\0')
176		return 1;
177	if(!wcscmp(name,L"."))
178		return 1;
179	if(!wcscmp(name,L".."))
180		return 1;
181	return 0;
182}
183#endif
184
185static int checkForDot(int lookupflags, const wchar_t *name)
186{
187	return (lookupflags & NO_DOTS) && isSpecialW(name);
188}
189
190
191typedef struct lookupState_t {
192	Stream_t *container;
193	int nbContainers;
194	Stream_t *Dir;
195	int nbDirs;
196	const char *filename;
197} lookupState_t;
198
199static int isUniqueTarget(const char *name)
200{
201	return name && strcmp(name, "-");
202}
203
204static int handle_leaf(direntry_t *direntry, MainParam_t *mp,
205		       lookupState_t *lookupState)
206{
207	Stream_t *MyFile=0;
208	int ret;
209
210	if(got_signal)
211		return ERROR_ONE;
212	if(lookupState) {
213		/* we are looking for a "target" file */
214		switch(lookupState->nbDirs) {
215			case 0: /* no directory yet, open it */
216				lookupState->Dir = OpenFileByDirentry(direntry);
217				lookupState->nbDirs++;
218				/* dump the container, we have
219				 * better now */
220				FREE(&lookupState->container);
221				return 0;
222			case 1: /* we have already a directory */
223				FREE(&lookupState->Dir);
224				fprintf(stderr,"Ambigous\n");
225				return STOP_NOW | ERROR_ONE;
226			default:
227				return STOP_NOW | ERROR_ONE;
228		}
229	}
230
231	mp->direntry = direntry;
232	if(IS_DIR(direntry)) {
233		if(mp->lookupflags & (DO_OPEN | DO_OPEN_DIRS))
234			MyFile = mp->File = OpenFileByDirentry(direntry);
235		ret = mp->dirCallback(direntry, mp);
236	} else {
237		if(mp->lookupflags & DO_OPEN)
238			MyFile = mp->File = OpenFileByDirentry(direntry);
239		ret = mp->callback(direntry, mp);
240	}
241	FREE(&MyFile);
242	if(isUniqueTarget(mp->targetName))
243		ret |= STOP_NOW;
244	return ret;
245}
246
247static int _dos_loop(Stream_t *Dir, MainParam_t *mp, const char *filename)
248{
249	Stream_t *MyFile=0;
250	direntry_t entry;
251	int ret;
252	int r;
253
254	ret = 0;
255	r=0;
256	initializeDirentry(&entry, Dir);
257	while(!got_signal &&
258	      (r=vfat_lookup(&entry, filename, -1,
259			     mp->lookupflags, mp->shortname,
260			     mp->longname)) == 0 ){
261		mp->File = NULL;
262		if(!checkForDot(mp->lookupflags,entry.name)) {
263			MyFile = 0;
264			if((mp->lookupflags & DO_OPEN) ||
265			   (IS_DIR(&entry) &&
266			    (mp->lookupflags & DO_OPEN_DIRS))) {
267				MyFile = mp->File = OpenFileByDirentry(&entry);
268			}
269			if(got_signal)
270				break;
271			mp->direntry = &entry;
272			if(IS_DIR(&entry))
273				ret |= mp->dirCallback(&entry,mp);
274			else
275				ret |= mp->callback(&entry, mp);
276			FREE(&MyFile);
277		}
278		if (fat_error(Dir))
279			ret |= ERROR_ONE;
280		if(mp->fast_quit && (ret & ERROR_ONE))
281			break;
282	}
283	if (r == -2)
284	    return ERROR_ONE;
285	if(got_signal)
286		ret |= ERROR_ONE;
287	return ret;
288}
289
290static int recurs_dos_loop(MainParam_t *mp, const char *filename0,
291			   const char *filename1,
292			   lookupState_t *lookupState)
293{
294	/* Dir is de-allocated by the same entity which allocated it */
295	const char *ptr;
296	direntry_t entry;
297	int length;
298	int lookupflags;
299	int ret;
300	int have_one;
301	int doing_mcwd;
302	int r;
303
304	while(1) {
305		/* strip dots and / */
306		if(!strncmp(filename0,"./", 2)) {
307			filename0 += 2;
308			continue;
309		}
310		if(!strcmp(filename0,".") && filename1) {
311			filename0 ++;
312			continue;
313		}
314		if(filename0[0] == '/') {
315			filename0++;
316			continue;
317		}
318		if(!filename0[0]) {
319			if(!filename1)
320				break;
321			filename0 = filename1;
322			filename1 = 0;
323			continue;
324		}
325		break;
326	}
327
328	if(!strncmp(filename0,"../", 3) ||
329	   (!strcmp(filename0, "..") && filename1)) {
330		/* up one level */
331		mp->File = getDirentry(mp->File)->Dir;
332		return recurs_dos_loop(mp, filename0+2, filename1, lookupState);
333	}
334
335	doing_mcwd = !!filename1;
336
337	ptr = strchr(filename0, '/');
338	if(!ptr) {
339		length = strlen(filename0);
340		ptr = filename1;
341		filename1 = 0;
342	} else {
343		length = ptr - filename0;
344		ptr++;
345	}
346	if(!ptr) {
347		if(mp->lookupflags & OPEN_PARENT) {
348			mp->targetName = filename0;
349			ret = handle_leaf(getDirentry(mp->File), mp,
350					  lookupState);
351			mp->targetName = 0;
352			return ret;
353		}
354
355		if(!strcmp(filename0, ".") || !filename0[0]) {
356			return handle_leaf(getDirentry(mp->File),
357					   mp, lookupState);
358		}
359
360		if(!strcmp(filename0, "..")) {
361			return handle_leaf(getParent(getDirentry(mp->File)), mp,
362					   lookupState);
363		}
364
365		lookupflags = mp->lookupflags;
366
367		if(lookupState) {
368			lookupState->filename = filename0;
369			if(lookupState->nbContainers + lookupState->nbDirs > 0){
370				/* we have already one target, don't bother
371				 * with this one. */
372				FREE(&lookupState->container);
373			} else {
374				/* no match yet.  Remember this container for
375				 * later use */
376				lookupState->container = COPY(mp->File);
377			}
378			lookupState->nbContainers++;
379		}
380	} else
381		lookupflags = ACCEPT_DIR | DO_OPEN | NO_DOTS;
382
383	ret = 0;
384	r = 0;
385	have_one = 0;
386	initializeDirentry(&entry, mp->File);
387	while(!(ret & STOP_NOW) &&
388	      !got_signal &&
389	      (r=vfat_lookup(&entry, filename0, length,
390			     lookupflags | NO_MSG,
391			     mp->shortname, mp->longname)) == 0 ){
392		if(checkForDot(lookupflags, entry.name))
393			/* while following the path, ignore the
394			 * special entries if they were not
395			 * explicitly given */
396			continue;
397		have_one = 1;
398		if(ptr) {
399			Stream_t *SubDir;
400			SubDir = mp->File = OpenFileByDirentry(&entry);
401			ret |= recurs_dos_loop(mp, ptr, filename1, lookupState);
402			FREE(&SubDir);
403		} else {
404			ret |= handle_leaf(&entry, mp, lookupState);
405			if(isUniqueTarget(mp->targetName))
406				return ret | STOP_NOW;
407		}
408		if(doing_mcwd)
409			break;
410	}
411	if (r == -2)
412		return ERROR_ONE;
413	if(got_signal)
414		return ret | ERROR_ONE;
415	if(doing_mcwd && !have_one)
416		return NO_CWD;
417	return ret;
418}
419
420static int common_dos_loop(MainParam_t *mp, const char *pathname,
421			   lookupState_t *lookupState, int open_mode)
422
423{
424	Stream_t *RootDir;
425	const char *cwd;
426	char drive;
427
428	int ret;
429	mp->loop = _dos_loop;
430
431	drive='\0';
432	cwd = "";
433	if(*pathname && pathname[1] == ':') {
434		drive = toupper(*pathname);
435		pathname += 2;
436		if(mp->mcwd[0] == drive)
437			cwd = mp->mcwd+2;
438	} else if(mp->mcwd[0]) {
439		drive = mp->mcwd[0];
440		cwd = mp->mcwd+2;
441	} else {
442		drive = get_default_drive();
443	}
444
445	if(*pathname=='/') /* absolute path name */
446		cwd = "";
447
448	RootDir = mp->File = open_root_dir(drive, open_mode, NULL);
449	if(!mp->File)
450		return ERROR_ONE;
451
452	ret = recurs_dos_loop(mp, cwd, pathname, lookupState);
453	if(ret & NO_CWD) {
454		/* no CWD */
455		*mp->mcwd = '\0';
456		unlink_mcwd();
457		ret = recurs_dos_loop(mp, "", pathname, lookupState);
458	}
459	FREE(&RootDir);
460	return ret;
461}
462
463static int dos_loop(MainParam_t *mp, const char *arg)
464{
465	return common_dos_loop(mp, arg, 0, mp->openflags);
466}
467
468
469static int dos_target_lookup(MainParam_t *mp, const char *arg)
470{
471	lookupState_t lookupState;
472	int ret;
473	int lookupflags;
474
475	lookupState.nbDirs = 0;
476	lookupState.Dir = 0;
477	lookupState.nbContainers = 0;
478	lookupState.container = 0;
479
480	lookupflags = mp->lookupflags;
481	mp->lookupflags = DO_OPEN | ACCEPT_DIR;
482	ret = common_dos_loop(mp, arg, &lookupState, O_RDWR);
483	mp->lookupflags = lookupflags;
484	if(ret & ERROR_ONE)
485		return ret;
486
487	if(lookupState.nbDirs) {
488		mp->targetName = 0;
489		mp->targetDir = lookupState.Dir;
490		FREE(&lookupState.container); /* container no longer needed */
491		return ret;
492	}
493
494	switch(lookupState.nbContainers) {
495		case 0:
496			/* no match */
497			fprintf(stderr,"%s: no match for target\n", arg);
498			return MISSED_ONE;
499		case 1:
500			mp->targetName = strdup(lookupState.filename);
501			mp->targetDir = lookupState.container;
502			return ret;
503		default:
504			/* too much */
505			fprintf(stderr, "Ambigous %s\n", arg);
506			return ERROR_ONE;
507	}
508}
509
510static int unix_target_lookup(MainParam_t *mp, const char *arg)
511{
512	char *ptr;
513	mp->unixTarget = strdup(arg);
514	/* try complete filename */
515	if(access(mp->unixTarget, F_OK) == 0)
516		return GOT_ONE;
517	ptr = strrchr(mp->unixTarget, '/');
518	if(!ptr) {
519		mp->targetName = mp->unixTarget;
520		mp->unixTarget = strdup(".");
521		return GOT_ONE;
522	} else {
523		*ptr = '\0';
524		mp->targetName = ptr+1;
525		return GOT_ONE;
526	}
527}
528
529int target_lookup(MainParam_t *mp, const char *arg)
530{
531	if((mp->lookupflags & NO_UNIX) || (arg[0]
532#ifdef OS_mingw32msvc
533/* On Windows, support only the command-line image drive. */
534                                           && arg[0] == ':'
535#endif
536                                           && arg[1] == ':' ))
537		return dos_target_lookup(mp, arg);
538	else
539		return unix_target_lookup(mp, arg);
540}
541
542int main_loop(MainParam_t *mp, char **argv, int argc)
543{
544	int i;
545	int ret, Bret;
546
547	Bret = 0;
548
549	if(argc != 1 && mp->targetName) {
550		fprintf(stderr,
551			"Several file names given, but last argument (%s) not a directory\n", mp->targetName);
552	}
553
554	for (i = 0; i < argc; i++) {
555		if ( got_signal )
556			break;
557		mp->originalArg = argv[i];
558		mp->basenameHasWildcard = strpbrk(_basename(mp->originalArg),
559						  "*[?") != 0;
560		if (mp->unixcallback && (!argv[i][0]
561#ifdef OS_mingw32msvc
562/* On Windows, support only the command-line image drive. */
563                                         || argv[i][0] != ':'
564#endif
565                                         || argv[i][1] != ':' ))
566			ret = unix_loop(0, mp, argv[i], 1);
567		else
568			ret = dos_loop(mp, argv[i]);
569
570		if (! (ret & (GOT_ONE | ERROR_ONE)) ) {
571			/* one argument was unmatched */
572			fprintf(stderr, "%s: File \"%s\" not found\n",
573				progname, argv[i]);
574			ret |= ERROR_ONE;
575		}
576		Bret |= ret;
577		if(mp->fast_quit && (Bret & (MISSED_ONE | ERROR_ONE)))
578			break;
579	}
580	FREE(&mp->targetDir);
581	if(Bret & ERROR_ONE)
582		return 1;
583	if ((Bret & GOT_ONE) && ( Bret & MISSED_ONE))
584		return 2;
585	if (Bret & MISSED_ONE)
586		return 1;
587	return 0;
588}
589
590static int dispatchToFile(direntry_t *entry, MainParam_t *mp)
591{
592	if(entry)
593		return mp->callback(entry, mp);
594	else
595		return mp->unixcallback(mp);
596}
597
598
599void init_mp(MainParam_t *mp)
600{
601	fix_mcwd(mp->mcwd);
602	mp->openflags = O_RDONLY;
603	mp->targetName = 0;
604	mp->targetDir = 0;
605	mp->unixTarget = 0;
606	mp->dirCallback = dispatchToFile;
607	mp->unixcallback = NULL;
608	mp->shortname = mp->longname = 0;
609	mp->File = 0;
610	mp->fast_quit = 0;
611}
612
613const char *mpGetBasename(MainParam_t *mp)
614{
615	if(mp->direntry) {
616		wchar_to_native(mp->direntry->name, mp->targetBuffer,
617				MAX_VNAMELEN+1);
618		return mp->targetBuffer;
619	} else
620		return _basename(mp->unixSourceName);
621}
622
623void mpPrintFilename(FILE *fp, MainParam_t *mp)
624{
625	if(mp->direntry)
626		fprintPwd(fp, mp->direntry, 0);
627	else
628		fprintf(fp,"%s",mp->originalArg);
629}
630
631const char *mpPickTargetName(MainParam_t *mp)
632{
633	/* picks the target name: either the one explicitly given by the
634	 * user, or the same as the source */
635	if(mp->targetName)
636		return mp->targetName;
637	else
638		return mpGetBasename(mp);
639}
640
641char *mpBuildUnixFilename(MainParam_t *mp)
642{
643	const char *target;
644	char *ret;
645	char *tmp;
646
647	target = mpPickTargetName(mp);
648	ret = malloc(strlen(mp->unixTarget) + 2 + strlen(target));
649	if(!ret)
650		return 0;
651	strcpy(ret, mp->unixTarget);
652	if(*target) {
653#if 1 /* fix for 'mcopy -n x:file existingfile' -- H. Lermen 980816 */
654		if(!mp->targetName && !mp->targetDir) {
655			struct MT_STAT buf;
656			if (!MT_STAT(ret, &buf) && !S_ISDIR(buf.st_mode))
657				return ret;
658		}
659#endif
660		strcat(ret, "/");
661		if(!strcmp(target, ".")) {
662		  target="DOT";
663		} else if(!strcmp(target, "..")) {
664		  target="DOTDOT";
665		}
666		while( (tmp=strchr(target, '/')) ) {
667		  strncat(ret, target, tmp-target);
668		  strcat(ret, "\\");
669		  target=tmp+1;
670		}
671		strcat(ret, target);
672	}
673	return ret;
674}
675