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