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