1/*
2 * FreeBSD install - a package for the installation and maintenance
3 * of non-core utilities.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * Jordan K. Hubbard
15 * 18 July 1993
16 *
17 * Miscellaneous file access utilities.
18 *
19 */
20
21#include <sys/cdefs.h>
22__FBSDID("$FreeBSD$");
23
24#include "lib.h"
25#include <err.h>
26#include <pwd.h>
27#include <time.h>
28#include <sys/wait.h>
29
30/* Quick check to see if a file exists */
31Boolean
32fexists(const char *fname)
33{
34    int fd;
35
36    if ((fd = open(fname, O_RDONLY)) == -1)
37	return FALSE;
38
39    close(fd);
40    return TRUE;
41}
42
43/* Quick check to see if something is a directory or symlink to a directory */
44Boolean
45isdir(const char *fname)
46{
47    struct stat sb;
48
49    if (lstat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode))
50	return TRUE;
51    else if (lstat(strconcat(fname, "/."), &sb) != FAIL && S_ISDIR(sb.st_mode))
52	return TRUE;
53    else
54	return FALSE;
55}
56
57/* Check to see if file is a dir or symlink to a dir, and is empty */
58Boolean
59isemptydir(const char *fname)
60{
61    if (isdir(fname)) {
62	DIR *dirp;
63	struct dirent *dp;
64
65	dirp = opendir(fname);
66	if (!dirp)
67	    return FALSE;	/* no perms, leave it alone */
68	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
69	    if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
70		closedir(dirp);
71		return FALSE;
72	    }
73	}
74	(void)closedir(dirp);
75	return TRUE;
76    }
77    return FALSE;
78}
79
80/*
81 * Returns TRUE if file is a regular file or symlink pointing to a regular
82 * file
83 */
84Boolean
85isfile(const char *fname)
86{
87    struct stat sb;
88    if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode))
89	return TRUE;
90    return FALSE;
91}
92
93/*
94 * Check to see if file is a file or symlink pointing to a file and is empty.
95 * If nonexistent or not a file, say "it's empty", otherwise return TRUE if
96 * zero sized.
97 */
98Boolean
99isemptyfile(const char *fname)
100{
101    struct stat sb;
102    if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode)) {
103	if (sb.st_size != 0)
104	    return FALSE;
105    }
106    return TRUE;
107}
108
109/* Returns TRUE if file is a symbolic link. */
110Boolean
111issymlink(const char *fname)
112{
113    struct stat sb;
114    if (lstat(fname, &sb) != FAIL && S_ISLNK(sb.st_mode))
115	return TRUE;
116    return FALSE;
117}
118
119/* Returns TRUE if file is a URL specification */
120Boolean
121isURL(const char *fname)
122{
123    /*
124     * I'm sure there are other types of URL specifications that I could
125     * also be looking for here, but for now I'll just be happy to get ftp
126     * and http working.
127     */
128    if (!fname)
129	return FALSE;
130    while (isspace(*fname))
131	++fname;
132    if (!strncmp(fname, "ftp://", 6) || !strncmp(fname, "http://", 7) ||
133	!strncmp(fname, "https://", 8) || !strncmp(fname, "file://", 7))
134	return TRUE;
135    return FALSE;
136}
137
138char *
139fileFindByPath(const char *base, const char *fname)
140{
141    static char tmp[FILENAME_MAX];
142    char *cp;
143    const char *suffixes[] = {".tbz", ".tgz", ".tar", ".txz", NULL};
144    int i;
145
146    if (fexists(fname) && isfile(fname)) {
147	strcpy(tmp, fname);
148	return tmp;
149    }
150    if (base) {
151	strcpy(tmp, base);
152
153	cp = strrchr(tmp, '/');
154	if (cp) {
155	    *cp = '\0';	/* chop name */
156	    cp = strrchr(tmp, '/');
157	}
158	if (cp)
159	    for (i = 0; suffixes[i] != NULL; i++) {
160		*(cp + 1) = '\0';
161		strcat(cp, "All/");
162		strcat(cp, fname);
163		strcat(cp, suffixes[i]);
164		if (fexists(tmp))
165		    return tmp;
166	    }
167    }
168
169    cp = getenv("PKG_PATH");
170    while (cp) {
171	char *cp2 = strsep(&cp, ":");
172
173	for (i = 0; suffixes[i] != NULL; i++) {
174	    snprintf(tmp, FILENAME_MAX, "%s/%s%s", cp2 ? cp2 : cp, fname, suffixes[i]);
175	    if (fexists(tmp) && isfile(tmp))
176		return tmp;
177	}
178    }
179    return NULL;
180}
181
182char *
183fileGetContents(const char *fname)
184{
185    char *contents;
186    struct stat sb;
187    int fd;
188
189    if (stat(fname, &sb) == FAIL) {
190	cleanup(0);
191	errx(2, "%s: can't stat '%s'", __func__, fname);
192    }
193
194    contents = (char *)malloc(sb.st_size + 1);
195    fd = open(fname, O_RDONLY, 0);
196    if (fd == FAIL) {
197	cleanup(0);
198	errx(2, "%s: unable to open '%s' for reading", __func__, fname);
199    }
200    if (read(fd, contents, sb.st_size) != sb.st_size) {
201	cleanup(0);
202	errx(2, "%s: short read on '%s' - did not get %lld bytes", __func__,
203	     fname, (long long)sb.st_size);
204    }
205    close(fd);
206    contents[sb.st_size] = '\0';
207    return contents;
208}
209
210/*
211 * Takes a filename and package name, returning (in "try") the
212 * canonical "preserve" name for it.
213 */
214Boolean
215make_preserve_name(char *try, int max, const char *name, const char *file)
216{
217    int len, i;
218
219    if ((len = strlen(file)) == 0)
220	return FALSE;
221    else
222	i = len - 1;
223    strncpy(try, file, max);
224    if (try[i] == '/') /* Catch trailing slash early and save checking in the loop */
225	--i;
226    for (; i; i--) {
227	if (try[i] == '/') {
228	    try[i + 1]= '.';
229	    strncpy(&try[i + 2], &file[i + 1], max - i - 2);
230	    break;
231	}
232    }
233    if (!i) {
234	try[0] = '.';
235	strncpy(try + 1, file, max - 1);
236    }
237    /* I should probably be called rude names for these inline assignments */
238    strncat(try, ".",  max -= strlen(try));
239    strncat(try, name, max -= strlen(name));
240    strncat(try, ".",  max--);
241    strncat(try, "backup", max -= 6);
242    return TRUE;
243}
244
245/* Write the contents of "str" to a file */
246void
247write_file(const char *name, const char *str)
248{
249    FILE *fp;
250    size_t len;
251
252    fp = fopen(name, "w");
253    if (!fp) {
254	cleanup(0);
255	errx(2, "%s: cannot fopen '%s' for writing", __func__, name);
256    }
257    len = strlen(str);
258    if (fwrite(str, 1, len, fp) != len) {
259	cleanup(0);
260	errx(2, "%s: short fwrite on '%s', tried to write %ld bytes",
261	    __func__, name, (long)len);
262    }
263    if (fclose(fp)) {
264	cleanup(0);
265	errx(2, "%s: failure to fclose '%s'", __func__, name);
266    }
267}
268
269void
270copy_file(const char *dir, const char *fname, const char *to)
271{
272    char cmd[FILENAME_MAX];
273
274    if (fname[0] == '/')
275	snprintf(cmd, FILENAME_MAX, "/bin/cp -r %s %s", fname, to);
276    else
277	snprintf(cmd, FILENAME_MAX, "/bin/cp -r %s/%s %s", dir, fname, to);
278    if (vsystem(cmd)) {
279	cleanup(0);
280	errx(2, "%s: could not perform '%s'", __func__, cmd);
281    }
282}
283
284void
285move_file(const char *dir, const char *fname, const char *tdir)
286{
287    char from[FILENAME_MAX];
288    char to[FILENAME_MAX];
289
290    if (fname[0] == '/')
291	strncpy(from, fname, FILENAME_MAX);
292    else
293	snprintf(from, FILENAME_MAX, "%s/%s", dir, fname);
294
295    snprintf(to, FILENAME_MAX, "%s/%s", tdir, fname);
296
297    if (rename(from, to) == -1) {
298        if (vsystem("/bin/mv %s %s", from, to)) {
299	    cleanup(0);
300	    errx(2, "%s: could not move '%s' to '%s'", __func__, from, to);
301	}
302    }
303}
304
305/*
306 * Copy a hierarchy (possibly from dir) to the current directory, or
307 * if "to" is TRUE, from the current directory to a location someplace
308 * else.
309 *
310 * Though slower, using tar to copy preserves symlinks and everything
311 * without me having to write some big hairy routine to do it.
312 */
313void
314copy_hierarchy(const char *dir, const char *fname, Boolean to)
315{
316    char cmd[FILENAME_MAX * 3];
317
318    if (!to) {
319	/* If absolute path, use it */
320	if (*fname == '/')
321	    dir = "/";
322	snprintf(cmd, FILENAME_MAX * 3, "/usr/bin/tar cf - -C %s %s | /usr/bin/tar xpf -",
323		 dir, fname);
324    }
325    else
326	snprintf(cmd, FILENAME_MAX * 3, "/usr/bin/tar cf - %s | /usr/bin/tar xpf - -C %s",
327		 fname, dir);
328#ifdef DEBUG
329    printf("Using '%s' to copy trees.\n", cmd);
330#endif
331    if (system(cmd)) {
332	cleanup(0);
333	errx(2, "%s: could not perform '%s'", __func__, cmd);
334    }
335}
336
337/* Unpack a tar file */
338int
339unpack(const char *pkg, const char *flist)
340{
341    const char *comp, *cp;
342    char suff[80];
343
344    comp = "";
345    /*
346     * Figure out by a crude heuristic whether this or not this is probably
347     * compressed and whichever compression utility was used (gzip or bzip2).
348     */
349    if (strcmp(pkg, "-")) {
350	cp = strrchr(pkg, '.');
351	if (cp) {
352	    strcpy(suff, cp + 1);
353	    if (strchr(suff, 'z') || strchr(suff, 'Z')) {
354		if (strchr(suff, 'b'))
355		    comp = "-j";
356		else
357		    comp = "-z";
358	    }
359	}
360    }
361    else
362	comp = "-j";
363    if (vsystem("/usr/bin/tar -xp %s -f '%s' %s", comp, pkg, flist ? flist : "")) {
364	warnx("tar extract of %s failed!", pkg);
365	return 1;
366    }
367    return 0;
368}
369
370/*
371 * Using fmt, replace all instances of:
372 *
373 * %F	With the parameter "name"
374 * %D	With the parameter "dir"
375 * %B	Return the directory part ("base") of %D/%F
376 * %f	Return the filename part of %D/%F
377 *
378 * Does not check for overflow - caution!
379 *
380 */
381void
382format_cmd(char *buf, int max, const char *fmt, const char *dir, const char *name)
383{
384    char *cp, scratch[FILENAME_MAX * 2];
385    int l;
386
387    while (*fmt && max > 0) {
388	if (*fmt == '%') {
389	    switch (*++fmt) {
390	    case 'F':
391		strncpy(buf, name, max);
392		l = strlen(name);
393		buf += l, max -= l;
394		break;
395
396	    case 'D':
397		strncpy(buf, dir, max);
398		l = strlen(dir);
399		buf += l, max -= l;
400		break;
401
402	    case 'B':
403		snprintf(scratch, FILENAME_MAX * 2, "%s/%s", dir, name);
404		cp = &scratch[strlen(scratch) - 1];
405		while (cp != scratch && *cp != '/')
406		    --cp;
407		*cp = '\0';
408		strncpy(buf, scratch, max);
409		l = strlen(scratch);
410		buf += l, max -= l;
411		break;
412
413	    case 'f':
414		snprintf(scratch, FILENAME_MAX * 2, "%s/%s", dir, name);
415		cp = &scratch[strlen(scratch) - 1];
416		while (cp != scratch && *(cp - 1) != '/')
417		    --cp;
418		strncpy(buf, cp, max);
419		l = strlen(cp);
420		buf += l, max -= l;
421		break;
422
423	    default:
424		*buf++ = *fmt;
425		--max;
426		break;
427	    }
428	    ++fmt;
429	}
430	else {
431	    *buf++ = *fmt++;
432	    --max;
433	}
434    }
435    *buf = '\0';
436}
437