Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.sbin / pkg_install / lib / file.c
1 /*
2  * FreeBSD install - a package for the installation and maintainance
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: src/usr.sbin/pkg_install/lib/file.c,v 1.40.2.14 2002/09/25 23:22:14 bmah Exp $");
23
24 #include "lib.h"
25 #include <err.h>
26 #include <fetch.h>
27 #include <pwd.h>
28 #include <time.h>
29 #include <sys/wait.h>
30
31 /* Quick check to see if a file exists */
32 Boolean
33 fexists(const char *fname)
34 {
35     struct stat dummy;
36     if (!lstat(fname, &dummy))
37         return TRUE;
38     return FALSE;
39 }
40
41 /* Quick check to see if something is a directory or symlink to a directory */
42 Boolean
43 isdir(const char *fname)
44 {
45     struct stat sb;
46
47     if (lstat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode))
48         return TRUE;
49     else if (lstat(strconcat(fname, "/."), &sb) != FAIL && S_ISDIR(sb.st_mode))
50         return TRUE;
51     else
52         return FALSE;
53 }
54
55 /* Check to see if file is a dir or symlink to a dir, and is empty */
56 Boolean
57 isemptydir(const char *fname)
58 {
59     if (isdir(fname)) {
60         DIR *dirp;
61         struct dirent *dp;
62
63         dirp = opendir(fname);
64         if (!dirp)
65             return FALSE;       /* no perms, leave it alone */
66         for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
67             if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
68                 closedir(dirp);
69                 return FALSE;
70             }
71         }
72         (void)closedir(dirp);
73         return TRUE;
74     }
75     return FALSE;
76 }
77
78 /*
79  * Returns TRUE if file is a regular file or symlink pointing to a regular
80  * file
81  */
82 Boolean
83 isfile(const char *fname)
84 {
85     struct stat sb;
86     if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode))
87         return TRUE;
88     return FALSE;
89 }
90
91 /* 
92  * Check to see if file is a file or symlink pointing to a file and is empty.
93  * If nonexistent or not a file, say "it's empty", otherwise return TRUE if
94  * zero sized.
95  */
96 Boolean
97 isemptyfile(const char *fname)
98 {
99     struct stat sb;
100     if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode)) {
101         if (sb.st_size != 0)
102             return FALSE;
103     }
104     return TRUE;
105 }
106
107 /* Returns TRUE if file is a symbolic link. */
108 Boolean
109 issymlink(const char *fname)
110 {
111     struct stat sb;
112     if (lstat(fname, &sb) != FAIL && S_ISLNK(sb.st_mode))
113         return TRUE;
114     return FALSE;
115 }
116
117 /* Returns TRUE if file is a URL specification */
118 Boolean
119 isURL(const char *fname)
120 {
121     /*
122      * I'm sure there are other types of URL specifications that I could
123      * also be looking for here, but for now I'll just be happy to get ftp
124      * and http working.
125      */
126     if (!fname)
127         return FALSE;
128     while (isspace(*fname))
129         ++fname;
130     if (!strncmp(fname, "ftp://", 6) || !strncmp(fname, "http://", 7))
131         return TRUE;
132     return FALSE;
133 }
134
135 #define HOSTNAME_MAX    64
136 /*
137  * Try and fetch a file by URL, returning the directory name for where
138  * it's unpacked, if successful.
139  */
140 char *
141 fileGetURL(const char *base, const char *spec)
142 {
143     char *cp, *rp;
144     char fname[FILENAME_MAX];
145     char pen[FILENAME_MAX];
146     char buf[8192];
147     FILE *ftp;
148     pid_t tpid;
149     int pfd[2], pstat, r, w;
150     char *hint;
151     int fd;
152
153     rp = NULL;
154     /* Special tip that sysinstall left for us */
155     hint = getenv("PKG_ADD_BASE");
156     if (!isURL(spec)) {
157         if (!base && !hint)
158             return NULL;
159         /*
160          * We've been given an existing URL (that's known-good) and now we need
161          * to construct a composite one out of that and the basename we were
162          * handed as a dependency.
163          */
164         if (base) {
165             strcpy(fname, base);
166             /*
167              * Advance back two slashes to get to the root of the package
168              * hierarchy
169              */
170             cp = strrchr(fname, '/');
171             if (cp) {
172                 *cp = '\0';     /* chop name */
173                 cp = strrchr(fname, '/');
174             }
175             if (cp) {
176                 *(cp + 1) = '\0';
177                 strcat(cp, "All/");
178                 strcat(cp, spec);
179                 strcat(cp, ".tgz");
180             }
181             else
182                 return NULL;
183         }
184         else {
185             /*
186              * Otherwise, we've been given an environment variable hinting
187              * at the right location from sysinstall
188              */
189             strcpy(fname, hint);
190             strcat(fname, spec);
191             strcat(fname, ".tgz");
192         }
193     }
194     else
195         strcpy(fname, spec);
196
197     if ((ftp = fetchGetURL(fname, Verbose ? "v" : NULL)) == NULL) {
198         printf("Error: FTP Unable to get %s: %s\n",
199                fname, fetchLastErrString);
200         return NULL;
201     }
202     
203     if (isatty(0) || Verbose)
204         printf("Fetching %s...", fname), fflush(stdout);
205     pen[0] = '\0';
206     if ((rp = make_playpen(pen, 0)) == NULL) {
207         printf("Error: Unable to construct a new playpen for FTP!\n");
208         fclose(ftp);
209         return NULL;
210     }
211     if (pipe(pfd) == -1) {
212         warn("pipe()");
213         cleanup(0);
214         exit(2);
215     }
216     if ((tpid = fork()) == -1) {
217         warn("pipe()");
218         cleanup(0);
219         exit(2);
220     }
221     if (!tpid) {
222         dup2(pfd[0], 0);
223         for (fd = getdtablesize() - 1; fd >= 3; --fd)
224             close(fd);
225         execl("/usr/bin/tar", "tar", Verbose ? "-xzvf" : "-xzf", "-",
226             (char *)0);
227         _exit(2);
228     }
229     close(pfd[0]);
230     for (;;) {
231         if ((r = fread(buf, 1, sizeof buf, ftp)) < 1)
232             break;
233         if ((w = write(pfd[1], buf, r)) != r)
234             break;
235     }
236     if (ferror(ftp))
237         warn("warning: error reading from server");
238     fclose(ftp);
239     close(pfd[1]);
240     if (w == -1)
241         warn("warning: error writing to tar");
242     tpid = waitpid(tpid, &pstat, 0);
243     if (Verbose)
244         printf("tar command returns %d status\n", WEXITSTATUS(pstat));
245     if (rp && (isatty(0) || Verbose))
246         printf(" Done.\n");
247     return rp;
248 }
249
250 char *
251 fileFindByPath(const char *base, const char *fname)
252 {
253     static char tmp[FILENAME_MAX];
254     char *cp;
255     const char *suffixes[] = {".tbz", ".tgz", ".tar", NULL};
256     int i;
257
258     if (fexists(fname) && isfile(fname)) {
259         strcpy(tmp, fname);
260         return tmp;
261     }
262     if (base) {
263         strcpy(tmp, base);
264
265         cp = strrchr(tmp, '/');
266         if (cp) {
267             *cp = '\0'; /* chop name */
268             cp = strrchr(tmp, '/');
269         }
270         if (cp)
271             for (i = 0; suffixes[i] != NULL; i++) {
272                 *(cp + 1) = '\0';
273                 strcat(cp, "All/");
274                 strcat(cp, fname);
275                 strcat(cp, suffixes[i]);
276                 if (fexists(tmp))
277                     return tmp;
278             }
279     }
280
281     cp = getenv("PKG_PATH");
282     while (cp) {
283         char *cp2 = strsep(&cp, ":");
284
285         for (i = 0; suffixes[i] != NULL; i++) {
286             snprintf(tmp, FILENAME_MAX, "%s/%s%s", cp2 ? cp2 : cp, fname, suffixes[i]);
287             if (fexists(tmp) && isfile(tmp))
288                 return tmp;
289         }
290     }
291     return NULL;
292 }
293
294 char *
295 fileGetContents(const char *fname)
296 {
297     char *contents;
298     struct stat sb;
299     int fd;
300
301     if (stat(fname, &sb) == FAIL) {
302         cleanup(0);
303         errx(2, "%s: can't stat '%s'", __func__, fname);
304     }
305
306     contents = (char *)malloc(sb.st_size + 1);
307     fd = open(fname, O_RDONLY, 0);
308     if (fd == FAIL) {
309         cleanup(0);
310         errx(2, "%s: unable to open '%s' for reading", __func__, fname);
311     }
312     if (read(fd, contents, sb.st_size) != sb.st_size) {
313         cleanup(0);
314         errx(2, "%s: short read on '%s' - did not get %qd bytes", __func__,
315              fname, (long long)sb.st_size);
316     }
317     close(fd);
318     contents[sb.st_size] = '\0';
319     return contents;
320 }
321
322 /*
323  * Takes a filename and package name, returning (in "try") the
324  * canonical "preserve" name for it.
325  */
326 Boolean
327 make_preserve_name(char *try, int max, const char *name, const char *file)
328 {
329     int len, i;
330
331     if ((len = strlen(file)) == 0)
332         return FALSE;
333     else
334         i = len - 1;
335     strncpy(try, file, max);
336     if (try[i] == '/') /* Catch trailing slash early and save checking in the loop */
337         --i;
338     for (; i; i--) {
339         if (try[i] == '/') {
340             try[i + 1]= '.';
341             strncpy(&try[i + 2], &file[i + 1], max - i - 2);
342             break;
343         }
344     }
345     if (!i) {
346         try[0] = '.';
347         strncpy(try + 1, file, max - 1);
348     }
349     /* I should probably be called rude names for these inline assignments */
350     strncat(try, ".",  max -= strlen(try));
351     strncat(try, name, max -= strlen(name));
352     strncat(try, ".",  max--);
353     strncat(try, "backup", max -= 6);
354     return TRUE;
355 }
356
357 /* Write the contents of "str" to a file */
358 void
359 write_file(const char *name, const char *str)
360 {
361     FILE *fp;
362     size_t len;
363
364     fp = fopen(name, "w");
365     if (!fp) {
366         cleanup(0);
367         errx(2, "%s: cannot fopen '%s' for writing", __func__, name);
368     }
369     len = strlen(str);
370     if (fwrite(str, 1, len, fp) != len) {
371         cleanup(0);
372         errx(2, "%s: short fwrite on '%s', tried to write %ld bytes",
373             __func__, name, (long)len);
374     }
375     if (fclose(fp)) {
376         cleanup(0);
377         errx(2, "%s: failure to fclose '%s'", __func__, name);
378     }
379 }
380
381 void
382 copy_file(const char *dir, const char *fname, const char *to)
383 {
384     char cmd[FILENAME_MAX];
385
386     if (fname[0] == '/')
387         snprintf(cmd, FILENAME_MAX, "cp -r %s %s", fname, to);
388     else
389         snprintf(cmd, FILENAME_MAX, "cp -r %s/%s %s", dir, fname, to);
390     if (vsystem(cmd)) {
391         cleanup(0);
392         errx(2, "%s: could not perform '%s'", __func__, cmd);
393     }
394 }
395
396 void
397 move_file(const char *dir, const char *fname, const char *to)
398 {
399     char cmd[FILENAME_MAX];
400
401     if (fname[0] == '/')
402         snprintf(cmd, FILENAME_MAX, "mv %s %s", fname, to);
403     else
404         snprintf(cmd, FILENAME_MAX, "mv %s/%s %s", dir, fname, to);
405     if (vsystem(cmd)) {
406         cleanup(0);
407         errx(2, "%s: could not perform '%s'", __func__, cmd);
408     }
409 }
410
411 /*
412  * Copy a hierarchy (possibly from dir) to the current directory, or
413  * if "to" is TRUE, from the current directory to a location someplace
414  * else.
415  *
416  * Though slower, using tar to copy preserves symlinks and everything
417  * without me having to write some big hairy routine to do it.
418  */
419 void
420 copy_hierarchy(const char *dir, const char *fname, Boolean to)
421 {
422     char cmd[FILENAME_MAX * 3];
423
424     if (!to) {
425         /* If absolute path, use it */
426         if (*fname == '/')
427             dir = "/";
428         snprintf(cmd, FILENAME_MAX * 3, "tar cf - -C %s %s | tar xpf -",
429                  dir, fname);
430     }
431     else
432         snprintf(cmd, FILENAME_MAX * 3, "tar cf - %s | tar xpf - -C %s",
433                  fname, dir);
434 #ifdef DEBUG
435     printf("Using '%s' to copy trees.\n", cmd);
436 #endif
437     if (system(cmd)) {
438         cleanup(0);
439         errx(2, "%s: could not perform '%s'", __func__, cmd);
440     }
441 }
442
443 /* Unpack a tar file */
444 int
445 unpack(const char *pkg, const char *flist)
446 {
447     char args[10], suff[80], *cp;
448
449     args[0] = '\0';
450     /*
451      * Figure out by a crude heuristic whether this or not this is probably
452      * compressed and whichever compression utility was used (gzip or bzip2).
453      */
454     if (strcmp(pkg, "-")) {
455         cp = strrchr(pkg, '.');
456         if (cp) {
457             strcpy(suff, cp + 1);
458             if (strchr(suff, 'z') || strchr(suff, 'Z')) {
459                 if (strchr(suff, 'b'))
460                     strcpy(args, "-j");
461                 else
462                     strcpy(args, "-z");
463             }
464         }
465     }
466     else
467         strcpy(args, "-z");
468     strcat(args, " -xpf");
469     if (vsystem("tar %s '%s' %s", args, pkg, flist ? flist : "")) {
470         warnx("tar extract of %s failed!", pkg);
471         return 1;
472     }
473     return 0;
474 }
475
476 /*
477  * Using fmt, replace all instances of:
478  *
479  * %F   With the parameter "name"
480  * %D   With the parameter "dir"
481  * %B   Return the directory part ("base") of %D/%F
482  * %f   Return the filename part of %D/%F
483  *
484  * Does not check for overflow - caution!
485  *
486  */
487 void
488 format_cmd(char *buf, const char *fmt, const char *dir, const char *name)
489 {
490     char *cp, scratch[FILENAME_MAX * 2];
491
492     while (*fmt) {
493         if (*fmt == '%') {
494             switch (*++fmt) {
495             case 'F':
496                 strcpy(buf, name);
497                 buf += strlen(name);
498                 break;
499
500             case 'D':
501                 strcpy(buf, dir);
502                 buf += strlen(dir);
503                 break;
504
505             case 'B':
506                 sprintf(scratch, "%s/%s", dir, name);
507                 cp = &scratch[strlen(scratch) - 1];
508                 while (cp != scratch && *cp != '/')
509                     --cp;
510                 *cp = '\0';
511                 strcpy(buf, scratch);
512                 buf += strlen(scratch);
513                 break;
514
515             case 'f':
516                 sprintf(scratch, "%s/%s", dir, name);
517                 cp = &scratch[strlen(scratch) - 1];
518                 while (cp != scratch && *(cp - 1) != '/')
519                     --cp;
520                 strcpy(buf, cp);
521                 buf += strlen(cp);
522                 break;
523
524             default:
525                 *buf++ = *fmt;
526                 break;
527             }
528             ++fmt;
529         }
530         else
531             *buf++ = *fmt++;
532     }
533     *buf = '\0';
534 }