xref: /netbsd-src/external/bsd/pkg_install/dist/lib/file.c (revision 96230fab84e26a6435963032070e916a951a8b2e)
1 /*	$NetBSD: file.c,v 1.1.1.1 2008/09/30 19:00:27 joerg Exp $	*/
2 
3 #if HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 #include <nbcompat.h>
7 #if HAVE_SYS_CDEFS_H
8 #include <sys/cdefs.h>
9 #endif
10 #if HAVE_SYS_PARAM_H
11 #include <sys/param.h>
12 #endif
13 #if HAVE_SYS_QUEUE_H
14 #include <sys/queue.h>
15 #endif
16 #ifndef lint
17 #if 0
18 static const char *rcsid = "from FreeBSD Id: file.c,v 1.29 1997/10/08 07:47:54 charnier Exp";
19 #else
20 __RCSID("$NetBSD: file.c,v 1.1.1.1 2008/09/30 19:00:27 joerg Exp $");
21 #endif
22 #endif
23 
24 /*
25  * FreeBSD install - a package for the installation and maintainance
26  * of non-core utilities.
27  *
28  * Redistribution and use in source and binary forms, with or without
29  * modification, are permitted provided that the following conditions
30  * are met:
31  * 1. Redistributions of source code must retain the above copyright
32  *    notice, this list of conditions and the following disclaimer.
33  * 2. Redistributions in binary form must reproduce the above copyright
34  *    notice, this list of conditions and the following disclaimer in the
35  *    documentation and/or other materials provided with the distribution.
36  *
37  * Jordan K. Hubbard
38  * 18 July 1993
39  *
40  * Miscellaneous file access utilities.
41  *
42  */
43 
44 #include "lib.h"
45 
46 #if HAVE_SYS_WAIT_H
47 #include <sys/wait.h>
48 #endif
49 
50 #if HAVE_ASSERT_H
51 #include <assert.h>
52 #endif
53 #if HAVE_ERR_H
54 #include <err.h>
55 #endif
56 #if HAVE_GLOB_H
57 #include <glob.h>
58 #endif
59 #if HAVE_NETDB_H
60 #include <netdb.h>
61 #endif
62 #if HAVE_PWD_H
63 #include <pwd.h>
64 #endif
65 #if HAVE_TIME_H
66 #include <time.h>
67 #endif
68 #if HAVE_FCNTL_H
69 #include <fcntl.h>
70 #endif
71 
72 
73 /*
74  * Quick check to see if a file (or dir ...) exists
75  */
76 Boolean
77 fexists(const char *fname)
78 {
79 	struct stat dummy;
80 	if (!lstat(fname, &dummy))
81 		return TRUE;
82 	return FALSE;
83 }
84 
85 /*
86  * Quick check to see if something is a directory
87  */
88 Boolean
89 isdir(const char *fname)
90 {
91 	struct stat sb;
92 
93 	if (lstat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode))
94 		return TRUE;
95 	else
96 		return FALSE;
97 }
98 
99 /*
100  * Check if something is a link to a directory
101  */
102 Boolean
103 islinktodir(const char *fname)
104 {
105 	struct stat sb;
106 
107 	if (lstat(fname, &sb) != FAIL && S_ISLNK(sb.st_mode)) {
108 		if (stat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode))
109 			return TRUE;	/* link to dir! */
110 		else
111 			return FALSE;	/* link to non-dir */
112 	} else
113 		return FALSE;	/* non-link */
114 }
115 
116 /*
117  * Check if something is a link that points to nonexistant target.
118  */
119 Boolean
120 isbrokenlink(const char *fname)
121 {
122 	struct stat sb;
123 
124 	if (lstat(fname, &sb) != FAIL && S_ISLNK(sb.st_mode)) {
125 		if (stat(fname, &sb) != FAIL)
126 			return FALSE;	/* link target exists! */
127 		else
128 			return TRUE;	/* link target missing*/
129 	} else
130 		return FALSE;	/* non-link */
131 }
132 
133 /*
134  * Check to see if file is a dir, and is empty
135  */
136 Boolean
137 isemptydir(const char *fname)
138 {
139 	if (isdir(fname) || islinktodir(fname)) {
140 		DIR    *dirp;
141 		struct dirent *dp;
142 
143 		dirp = opendir(fname);
144 		if (!dirp)
145 			return FALSE;	/* no perms, leave it alone */
146 		for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
147 			if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
148 				closedir(dirp);
149 				return FALSE;
150 			}
151 		}
152 		(void) closedir(dirp);
153 		return TRUE;
154 	}
155 	return FALSE;
156 }
157 
158 /*
159  * Check if something is a regular file
160  */
161 Boolean
162 isfile(const char *fname)
163 {
164 	struct stat sb;
165 	if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode))
166 		return TRUE;
167 	return FALSE;
168 }
169 
170 /*
171  * Check to see if file is a file and is empty. If nonexistent or not
172  * a file, say "it's empty", otherwise return TRUE if zero sized.
173  */
174 Boolean
175 isemptyfile(const char *fname)
176 {
177 	struct stat sb;
178 	if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode)) {
179 		if (sb.st_size != 0)
180 			return FALSE;
181 	}
182 	return TRUE;
183 }
184 
185 /* This struct defines the leading part of a valid URL name */
186 typedef struct url_t {
187 	char   *u_s;		/* the leading part of the URL */
188 	int     u_len;		/* its length */
189 }       url_t;
190 
191 /* A table of valid leading strings for URLs */
192 static const url_t urls[] = {
193 	{"ftp://", 6},
194 	{"http://", 7},
195 	{NULL}
196 };
197 
198 /*
199  * Returns length of leading part of any URL from urls table, or -1
200  */
201 int
202 URLlength(const char *fname)
203 {
204 	const url_t *up;
205 	int     i;
206 
207 	if (fname != (char *) NULL) {
208 		for (i = 0; isspace((unsigned char) *fname); i++) {
209 			fname++;
210 		}
211 		for (up = urls; up->u_s; up++) {
212 			if (strncmp(fname, up->u_s, up->u_len) == 0) {
213 				return i + up->u_len;    /* ... + sizeof(up->u_s);  - HF */
214 			}
215 		}
216 	}
217 	return -1;
218 }
219 
220 /*
221  * Returns the host part of a URL
222  */
223 const char *
224 fileURLHost(const char *fname, char *where, int max)
225 {
226 	const char   *ret;
227 	int     i;
228 
229 	assert(where != NULL);
230 	assert(max > 0);
231 
232 	if ((i = URLlength(fname)) < 0) {	/* invalid URL? */
233 		errx(EXIT_FAILURE, "fileURLhost called with a bad URL: `%s'", fname);
234 	}
235 	fname += i;
236 	/* Do we have a place to stick our work? */
237 	ret = where;
238 	while (*fname && *fname != '/' && --max)
239 		*where++ = *fname++;
240 	*where = '\0';
241 
242 	return ret;
243 }
244 
245 /*
246  * Returns the filename part of a URL
247  */
248 const char *
249 fileURLFilename(const char *fname, char *where, int max)
250 {
251 	const char *ret;
252 	int     i;
253 
254 	assert(where != NULL);
255 	assert(max > 0);
256 
257 	if ((i = URLlength(fname)) < 0) {	/* invalid URL? */
258 		errx(EXIT_FAILURE, "fileURLFilename called with a bad URL: `%s'", fname);
259 	}
260 	fname += i;
261 	/* Do we have a place to stick our work? */
262 	ret = where;
263 	while (*fname && *fname != '/')
264 		++fname;
265 	if (*fname == '/') {
266 		while (*fname && --max)
267 			*where++ = *fname++;
268 	}
269 	*where = '\0';
270 
271 	return ret;
272 }
273 
274 /*
275  * Try and fetch a file by URL, returning the directory name for where
276  * it's unpacked, if successful. To be handed to leave_playpen() later.
277  */
278 char   *
279 fileGetURL(const char *spec)
280 {
281 	char    host[MAXHOSTNAMELEN], file[MaxPathSize];
282 	const char *cp;
283 	char   *rp;
284 	char    pen[MaxPathSize];
285 	int     rc;
286 
287 	rp = NULL;
288 	if (!IS_URL(spec)) {
289 		errx(EXIT_FAILURE, "fileGetURL was called with non-URL arg '%s'", spec);
290 	}
291 
292  	/* Some sanity checks on the URL */
293 	cp = fileURLHost(spec, host, MAXHOSTNAMELEN);
294 	if (!*cp) {
295 		warnx("URL `%s' has bad host part!", spec);
296 		return NULL;
297 	}
298 	cp = fileURLFilename(spec, file, MaxPathSize);
299 	if (!*cp) {
300 		warnx("URL `%s' has bad filename part!", spec);
301 		return NULL;
302 	}
303 
304 	if (Verbose)
305 		printf("Trying to fetch %s.\n", spec);
306 
307 	pen[0] = '\0';
308 	rp = make_playpen(pen, sizeof(pen), 0);
309 	if (rp == NULL) {
310 		printf("Error: Unable to construct a new playpen for FTP!\n");
311 		return NULL;
312 	}
313 
314 	rp = strdup(pen);
315 	rc = unpackURL(spec, pen);
316 	if (rc < 0) {
317 		leave_playpen(rp); /* Don't leave dir hang around! */
318 
319 		printf("Error on unpackURL('%s', '%s')\n", spec, pen);
320 		return NULL;
321 	}
322 	return rp;
323 }
324 
325 static char *
326 resolvepattern1(const char *name)
327 {
328 	static char tmp[MaxPathSize];
329 	char *cp;
330 
331 	if (IS_URL(name)) {
332 		/* some package depends on a wildcard pkg */
333 		int rc;
334 
335 		rc = expandURL(tmp, name);
336 		if (rc < 0) {
337 			return NULL;
338 		}
339 		if (Verbose)
340 			printf("'%s' expanded to '%s'\n", name, tmp);
341 		return tmp;    /* return expanded URL w/ corrent pkg */
342 	}
343 	else if (ispkgpattern(name)) {
344 		cp = find_best_matching_file(dirname_of(name), basename_of(name), 1, 0);
345 		if (cp) {
346 			snprintf(tmp, sizeof(tmp), "%s/%s", dirname_of(name), cp);
347 			free(cp);
348 			return tmp;
349 		}
350 	} else {
351 		if (isfile(name)) {
352 			strlcpy(tmp, name, sizeof(tmp));
353 			return tmp;
354 		}
355 	}
356 
357 	return NULL;
358 }
359 
360 static char *
361 resolvepattern(const char *name)
362 {
363 	char tmp[MaxPathSize];
364 	char *cp;
365 	const char *suf;
366 
367 	cp = resolvepattern1(name);
368 	if (cp != NULL)
369 		return cp;
370 
371 	if (ispkgpattern(name))
372 		return NULL;
373 
374 	suf = suffix_of(name);
375 	if (!strcmp(suf, "tbz") || !strcmp(suf, "tgz"))
376 		return NULL;
377 
378 	/* add suffix and try */
379 	snprintf(tmp, sizeof(tmp), "%s.tbz", name);
380 	cp = resolvepattern1(tmp);
381 	if (cp != NULL)
382 		return cp;
383 	snprintf(tmp, sizeof(tmp), "%s.tgz", name);
384 	cp = resolvepattern1(tmp);
385 	if (cp != NULL)
386 		return cp;
387 
388 	/* add version number wildcard and try */
389 	snprintf(tmp, sizeof(tmp), "%s-[0-9]*", name);
390 	return resolvepattern1(tmp);
391 }
392 
393 /*
394  *  Look for filename/pattern "fname" in
395  * Returns a full path/URL where the pkg can be found
396  */
397 char   *
398 fileFindByPath(const char *fname)
399 {
400 	char    tmp[MaxPathSize];
401 	struct path *path;
402 
403 	/*
404 	 * 1. if fname is an absolute pathname or a URL,
405 	 *    just use it.
406 	 */
407 	if (IS_FULLPATH(fname) || IS_URL(fname))
408 		return resolvepattern(fname);
409 
410 	/*
411 	 * 2. otherwise, use PKG_PATH.
412 	 */
413 	TAILQ_FOREACH(path, &PkgPath, pl_entry) {
414 		char *cp;
415 		const char *cp2 = path->pl_path;
416 
417 		if (Verbose)
418 			printf("trying PKG_PATH %s\n", cp2);
419 
420 		if (IS_FULLPATH(cp2) || IS_URL(cp2)) {
421 			snprintf(tmp, sizeof(tmp), "%s/%s", cp2, fname);
422 		}
423 		else {
424 			char cwdtmp[MaxPathSize];
425 			if (getcwd(cwdtmp, sizeof(cwdtmp)) == NULL)
426 				errx(EXIT_FAILURE, "getcwd");
427 			snprintf(tmp, sizeof(tmp), "%s/%s/%s", cwdtmp, cp2, fname);
428 		}
429 		cp = resolvepattern(tmp);
430 		if (cp)
431 			return cp;
432 	}
433 
434 #if 0
435 	/*
436 	 * 3. finally, search current directory.
437 	 */
438 	snprintf(tmp, sizeof(tmp), "./%s", fname);
439 	return resolvepattern(tmp);
440 #else
441 	return NULL;
442 #endif
443 }
444 
445 /*
446  *  Expect "fname" to point at a file, and read it into
447  *  the buffer returned.
448  */
449 char   *
450 fileGetContents(char *fname)
451 {
452 	char   *contents;
453 	struct stat sb;
454 	int     fd;
455 
456 	if (stat(fname, &sb) == FAIL) {
457 		cleanup(0);
458 		errx(2, "can't stat '%s'", fname);
459 	}
460 
461 	contents = (char *) malloc((size_t) (sb.st_size) + 1);
462 	fd = open(fname, O_RDONLY, 0);
463 	if (fd == FAIL) {
464 		cleanup(0);
465 		errx(2, "unable to open '%s' for reading", fname);
466 	}
467 	if (read(fd, contents, (size_t) sb.st_size) != (size_t) sb.st_size) {
468 		cleanup(0);
469 		errx(2, "short read on '%s' - did not get %lld bytes",
470 		    fname, (long long) sb.st_size);
471 	}
472 	close(fd);
473 	contents[(size_t) sb.st_size] = '\0';
474 	return contents;
475 }
476 
477 /*
478  * Takes a filename and package name, returning (in "try") the canonical
479  * "preserve" name for it.
480  */
481 Boolean
482 make_preserve_name(char *try, size_t max, char *name, char *file)
483 {
484 	int     len, i;
485 
486 	if ((len = strlen(file)) == 0)
487 		return FALSE;
488 	else
489 		i = len - 1;
490 	strncpy(try, file, max);
491 	if (try[i] == '/')	/* Catch trailing slash early and save checking in the loop */
492 		--i;
493 	for (; i; i--) {
494 		if (try[i] == '/') {
495 			try[i + 1] = '.';
496 			strncpy(&try[i + 2], &file[i + 1], max - i - 2);
497 			break;
498 		}
499 	}
500 	if (!i) {
501 		try[0] = '.';
502 		strncpy(try + 1, file, max - 1);
503 	}
504 	/* I should probably be called rude names for these inline assignments */
505 	strncat(try, ".", max -= strlen(try));
506 	strncat(try, name, max -= strlen(name));
507 	strncat(try, ".", max--);
508 	strncat(try, "backup", max -= 6);
509 	return TRUE;
510 }
511 
512 /*
513  * Write the contents of "str" to a file
514  */
515 void
516 write_file(char *name, char *str)
517 {
518 	size_t  len;
519 	FILE   *fp;
520 
521 	if ((fp = fopen(name, "w")) == (FILE *) NULL) {
522 		cleanup(0);
523 		errx(2, "cannot fopen '%s' for writing", name);
524 	}
525 	len = strlen(str);
526 	if (fwrite(str, 1, len, fp) != len) {
527 		cleanup(0);
528 		errx(2, "short fwrite on '%s', tried to write %ld bytes",
529 		    name, (long) len);
530 	}
531 	if (fclose(fp)) {
532 		cleanup(0);
533 		errx(2, "failure to fclose '%s'", name);
534 	}
535 }
536 
537 void
538 copy_file(char *dir, char *fname, char *to)
539 {
540 	char    fpath[MaxPathSize];
541 
542 	(void) snprintf(fpath, sizeof(fpath), "%s%s%s",
543 			(fname[0] != '/') ? dir : "",
544 			(fname[0] != '/') ? "/" : "",
545 			fname);
546 	if (fexec("cp", "-r", fpath, to, NULL)) {
547 		cleanup(0);
548 		errx(2, "could not perform 'cp -r %s %s'", fpath, to);
549 	}
550 }
551 
552 void
553 move_file(char *dir, char *fname, char *to)
554 {
555 	char    fpath[MaxPathSize];
556 
557 	(void) snprintf(fpath, sizeof(fpath), "%s%s%s",
558 			(fname[0] != '/') ? dir : "",
559 			(fname[0] != '/') ? "/" : "",
560 			fname);
561 	if (fexec("mv", fpath, to, NULL)) {
562 		cleanup(0);
563 		errx(2, "could not perform 'mv %s %s'", fpath, to);
564 	}
565 }
566 
567 void
568 move_files(const char *dir, const char *pattern, const char *to)
569 {
570 	char	fpath[MaxPathSize];
571 	glob_t	globbed;
572 	size_t	i;
573 
574 	(void) snprintf(fpath, sizeof(fpath), "%s/%s", dir, pattern);
575 	if ((i=glob(fpath, GLOB_NOSORT, NULL, &globbed)) != 0) {
576 		switch(i) {
577 		case GLOB_NOMATCH:
578 			warn("no files matching ``%s'' found", fpath);
579 			break;
580 		case GLOB_ABORTED:
581 			warn("globbing aborted");
582 			break;
583 		case GLOB_NOSPACE:
584 			warn("out-of-memory during globbing");
585 			break;
586 		default:
587 			warn("unknown error during globbing");
588 			break;
589 		}
590 		return;
591 	}
592 
593 	/* Moving globbed files -- we just use mv(1) to do the job */
594 	for (i=0; i<globbed.gl_pathc; i++)
595 		if (fexec("mv", globbed.gl_pathv[i], to, NULL)) {
596 			cleanup(0);
597 			errx(2, "could not perform 'mv %s %s'", globbed.gl_pathv[i], to);
598 		}
599 
600 	return;
601 }
602 
603 void
604 remove_files(const char *path, const char *pattern)
605 {
606 	char	fpath[MaxPathSize];
607 	glob_t	globbed;
608 	int	i;
609 
610 	(void) snprintf(fpath, sizeof(fpath), "%s/%s", path, pattern);
611 	if ((i=glob(fpath, GLOB_NOSORT, NULL, &globbed)) != 0) {
612 		switch(i) {
613 		case GLOB_NOMATCH:
614 			warn("no files matching ``%s'' found", fpath);
615 			break;
616 		case GLOB_ABORTED:
617 			warn("globbing aborted");
618 			break;
619 		case GLOB_NOSPACE:
620 			warn("out-of-memory during globbing");
621 			break;
622 		default:
623 			warn("unknown error during globbing");
624 			break;
625 		}
626 		return;
627 	}
628 
629 	/* deleting globbed files */
630 	for (i=0; i<globbed.gl_pathc; i++)
631 		if (unlink(globbed.gl_pathv[i]) < 0)
632 			warn("can't delete ``%s''", globbed.gl_pathv[i]);
633 
634 	return;
635 }
636 
637 /*
638  * Unpack a tar file
639  */
640 int
641 unpack(const char *pkg, const lfile_head_t *filesp)
642 {
643 	const char *decompress_cmd = NULL;
644 	const char *suf;
645 	int count = 0;
646 	lfile_t	*lfp;
647 	char **up_argv;
648 	int up_argc = 7;
649 	int i = 0;
650 	int result;
651 
652 	if (filesp != NULL)
653 		TAILQ_FOREACH(lfp, filesp, lf_link)
654 			count++;
655 	up_argc += count;
656 	up_argv = malloc((count + up_argc + 1) * sizeof(char *));
657 	if (!IS_STDIN(pkg)) {
658 		suf = suffix_of(pkg);
659 		if (!strcmp(suf, "tbz") || !strcmp(suf, "bz2"))
660 			decompress_cmd = BZIP2_CMD;
661 		else if (!strcmp(suf, "tgz") || !strcmp(suf, "gz"))
662 			decompress_cmd = GZIP_CMD;
663 		else if (!strcmp(suf, "tar"))
664 			; /* do nothing */
665 		else
666 			errx(EXIT_FAILURE, "don't know how to decompress %s, sorry", pkg);
667 	} else
668 		decompress_cmd = GZIP_CMD;
669 
670 	up_argv[i] = (char *)strrchr(TAR_CMD, '/');
671 	if (up_argv[i] == NULL)
672 		up_argv[i] = TAR_CMD;
673 	else
674 		up_argv[i]++;  /* skip / character */
675 	if (count > 0)
676 		up_argv[++i] = "--fast-read";
677 	if (decompress_cmd != NULL) {
678 		up_argv[++i] = "--use-compress-program";
679 		up_argv[++i] = (char *)decompress_cmd;
680 	}
681 	up_argv[++i] = "-xpf";
682 	up_argv[++i] = (char *)pkg;
683 	if (count > 0)
684 		TAILQ_FOREACH(lfp, filesp, lf_link)
685 			up_argv[++i] = lfp->lf_name;
686 	up_argv[++i] = NULL;
687 
688 	if (Verbose) {
689 		printf("running: %s", TAR_CMD);
690 		for (i = 1; up_argv[i] != NULL; i++)
691 			printf(" %s", up_argv[i]);
692 		printf("\n");
693 	}
694 
695 	result = pfcexec(NULL, TAR_CMD, (const char **)up_argv);
696 	free(up_argv);
697 	if (result != 0) {
698 		warnx("extract of %s failed", pkg);
699 		return 1;
700 	}
701 
702 	return 0;
703 }
704 
705 /*
706  * Using fmt, replace all instances of:
707  *
708  * %F	With the parameter "name"
709  * %D	With the parameter "dir"
710  * %B	Return the directory part ("base") of %D/%F
711  * %f	Return the filename part of %D/%F
712  *
713  * Check that no overflows can occur.
714  */
715 void
716 format_cmd(char *buf, size_t size, char *fmt, char *dir, char *name)
717 {
718 	char    scratch[MaxPathSize * 2];
719 	char   *bufp;
720 	char   *cp;
721 
722 	for (bufp = buf; (int) (bufp - buf) < size && *fmt;) {
723 		if (*fmt == '%') {
724 			if (*++fmt != 'D' && name == NULL) {
725 				cleanup(0);
726 				errx(2, "no last file available for '%s' command", buf);
727 			}
728 			switch (*fmt) {
729 			case 'F':
730 				strlcpy(bufp, name, size - (int) (bufp - buf));
731 				bufp += strlen(bufp);
732 				break;
733 
734 			case 'D':
735 				strlcpy(bufp, dir, size - (int) (bufp - buf));
736 				bufp += strlen(bufp);
737 				break;
738 
739 			case 'B':
740 				(void) snprintf(scratch, sizeof(scratch), "%s/%s", dir, name);
741 				if ((cp = strrchr(scratch, '/')) == (char *) NULL) {
742 					cp = scratch;
743 				}
744 				*cp = '\0';
745 				strlcpy(bufp, scratch, size - (int) (bufp - buf));
746 				bufp += strlen(bufp);
747 				break;
748 
749 			case 'f':
750 				(void) snprintf(scratch, sizeof(scratch), "%s/%s", dir, name);
751 				if ((cp = strrchr(scratch, '/')) == (char *) NULL) {
752 					cp = scratch;
753 				} else {
754 					cp++;
755 				}
756 				strlcpy(bufp, cp, size - (int) (bufp - buf));
757 				bufp += strlen(bufp);
758 				break;
759 
760 			default:
761 				*bufp++ = '%';
762 				*bufp++ = *fmt;
763 				break;
764 			}
765 			++fmt;
766 		} else {
767 			*bufp++ = *fmt++;
768 		}
769 	}
770 	*bufp = '\0';
771 }
772