8e23a7ec0601305c39a56a0dbecc2548e5665405
[dragonfly.git] / usr.sbin / pkg_install / lib / plist.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  * General packing list routines.
18  *
19  * $FreeBSD: src/usr.sbin/pkg_install/lib/plist.c,v 1.48 2004/07/28 07:19:15 kan Exp $
20  * $DragonFly: src/usr.sbin/pkg_install/lib/Attic/plist.c,v 1.5 2005/08/28 16:56:12 corecode Exp $
21  */
22
23 #include "lib.h"
24 #include <err.h>
25 #include <md5.h>
26
27 /* Add an item to a packing list */
28 void
29 add_plist(Package *p, plist_t type, const char *arg)
30 {
31     PackingList tmp;
32
33     tmp = new_plist_entry();
34     tmp->name = copy_string(arg);
35     tmp->type = type;
36
37     if (!p->head)
38         p->head = p->tail = tmp;
39     else {
40         tmp->prev = p->tail;
41         p->tail->next = tmp;
42         p->tail = tmp;
43     }
44     switch (type) {
45     case PLIST_NAME:
46         p->name = tmp->name;
47         break;
48
49     case PLIST_ORIGIN:
50         p->origin = tmp->name;
51         break;
52
53     default:
54         break;
55     }
56 }
57
58 void
59 add_plist_top(Package *p, plist_t type, const char *arg)
60 {
61     PackingList tmp;
62
63     tmp = new_plist_entry();
64     tmp->name = copy_string(arg);
65     tmp->type = type;
66
67     if (!p->head)
68         p->head = p->tail = tmp;
69     else {
70         tmp->next = p->head;
71         p->head->prev = tmp;
72         p->head = tmp;
73     }
74 }
75
76 /* Return the last (most recent) entry in a packing list */
77 PackingList
78 last_plist(Package *p)
79 {
80     return p->tail;
81 }
82
83 /* Mark all items in a packing list to prevent iteration over them */
84 void
85 mark_plist(Package *pkg)
86 {
87     PackingList p = pkg->head;
88
89     while (p) {
90         p->marked = TRUE;
91         p = p->next;
92     }
93 }
94
95 /* Find a given item in a packing list and, if so, return it (else NULL) */
96 PackingList
97 find_plist(Package *pkg, plist_t type)
98 {
99     PackingList p = pkg->head;
100
101     while (p) {
102         if (p->type == type)
103             return p;
104         p = p->next;
105     }
106     return NULL;
107 }
108
109 /* Look for a specific boolean option argument in the list */
110 char *
111 find_plist_option(Package *pkg, const char *name)
112 {
113     PackingList p = pkg->head;
114
115     while (p) {
116         if (p->type == PLIST_OPTION && !strcmp(p->name, name))
117             return p->name;
118         p = p->next;
119     }
120     return NULL;
121 }
122
123 /*
124  * Delete plist item 'type' in the list (if 'name' is non-null, match it
125  * too.)  If 'all' is set, delete all items, not just the first occurance.
126  */
127 void
128 delete_plist(Package *pkg, Boolean all, plist_t type, const char *name)
129 {
130     PackingList p = pkg->head;
131
132     while (p) {
133         PackingList pnext = p->next;
134
135         if (p->type == type && (!name || !strcmp(name, p->name))) {
136             free(p->name);
137             if (p->prev)
138                 p->prev->next = pnext;
139             else
140                 pkg->head = pnext;
141             if (pnext)
142                 pnext->prev = p->prev;
143             else
144                 pkg->tail = p->prev;
145             free(p);
146             if (!all)
147                 return;
148             p = pnext;
149         }
150         else
151             p = p->next;
152     }
153 }
154
155 /* Allocate a new packing list entry */
156 PackingList
157 new_plist_entry(void)
158 {
159     PackingList ret;
160
161     ret = (PackingList)malloc(sizeof(struct _plist));
162     bzero(ret, sizeof(struct _plist));
163     return ret;
164 }
165
166 /* Free an entire packing list */
167 void
168 free_plist(Package *pkg)
169 {
170     PackingList p = pkg->head;
171
172     while (p) {
173         PackingList p1 = p->next;
174
175         free(p->name);
176         free(p);
177         p = p1;
178     }
179     pkg->head = pkg->tail = NULL;
180 }
181
182 /*
183  * For an ascii string denoting a plist command, return its code and
184  * optionally its argument(s)
185  */
186 int
187 plist_cmd(const char *s, char **arg)
188 {
189     char cmd[FILENAME_MAX + 20];        /* 20 == fudge for max cmd len */
190     char *cp;
191     const char *sp;
192
193     strcpy(cmd, s);
194     str_lowercase(cmd);
195     cp = cmd;
196     sp = s;
197     while (*cp) {
198         if (isspace(*cp)) {
199             *cp = '\0';
200             while (isspace(*sp)) /* Never sure if macro, increment later */
201                 ++sp;
202             break;
203         }
204         ++cp, ++sp;
205     }
206     if (arg)
207         *arg = (char *)sp;
208     if (!strcmp(cmd, "cwd"))
209         return PLIST_CWD;
210     else if (!strcmp(cmd, "srcdir"))
211         return PLIST_SRC;
212     else if (!strcmp(cmd, "cd"))
213         return PLIST_CWD;
214     else if (!strcmp(cmd, "exec"))
215         return PLIST_CMD;
216     else if (!strcmp(cmd, "unexec"))
217         return PLIST_UNEXEC;
218     else if (!strcmp(cmd, "mode"))
219         return PLIST_CHMOD;
220     else if (!strcmp(cmd, "owner"))
221         return PLIST_CHOWN;
222     else if (!strcmp(cmd, "group"))
223         return PLIST_CHGRP;
224     else if (!strcmp(cmd, "comment")) {
225         if (!strncmp(*arg, "ORIGIN:", 7)) {
226             *arg += 7;
227             return PLIST_ORIGIN;
228         } else if (!strncmp(*arg, "DEPORIGIN:", 10)) {
229             *arg += 10;
230             return PLIST_DEPORIGIN;
231         }
232         return PLIST_COMMENT;
233     } else if (!strcmp(cmd, "ignore"))
234         return PLIST_IGNORE;
235     else if (!strcmp(cmd, "ignore_inst"))
236         return PLIST_IGNORE_INST;
237     else if (!strcmp(cmd, "name"))
238         return PLIST_NAME;
239     else if (!strcmp(cmd, "display"))
240         return PLIST_DISPLAY;
241     else if (!strcmp(cmd, "pkgdep"))
242         return PLIST_PKGDEP;
243     else if (!strcmp(cmd, "conflicts"))
244         return PLIST_CONFLICTS;
245     else if (!strcmp(cmd, "mtree"))
246         return PLIST_MTREE;
247     else if (!strcmp(cmd, "dirrm"))
248         return PLIST_DIR_RM;
249     else if (!strcmp(cmd, "option"))
250         return PLIST_OPTION;
251     else
252         return FAIL;
253 }
254
255 /* Read a packing list from a file */
256 void
257 read_plist(Package *pkg, FILE *fp)
258 {
259     char *cp, pline[FILENAME_MAX];
260     int cmd, major, minor;
261
262     pkg->fmtver_maj = 1;
263     pkg->fmtver_mnr = 0;
264     pkg->origin = NULL;
265     while (fgets(pline, FILENAME_MAX, fp)) {
266         int len = strlen(pline);
267
268         while (len && isspace(pline[len - 1]))
269             pline[--len] = '\0';
270         if (!len)
271             continue;
272         cp = pline;
273         if (pline[0] != CMD_CHAR) {
274             cmd = PLIST_FILE;
275             goto bottom;
276         }
277         cmd = plist_cmd(pline + 1, &cp);
278         if (cmd == FAIL) {
279             warnx("%s: unknown command '%s' (package tools out of date?)",
280                 __func__, pline);
281             goto bottom;
282         }
283         if (*cp == '\0') {
284             cp = NULL;
285             goto bottom;
286         }
287         if (cmd == PLIST_COMMENT && sscanf(cp, "PKG_FORMAT_REVISION:%d.%d\n",
288                                            &major, &minor) == 2) {
289             pkg->fmtver_maj = major;
290             pkg->fmtver_mnr = minor;
291             if (verscmp(pkg, PLIST_FMT_VER_MAJOR, PLIST_FMT_VER_MINOR) <= 0)
292                 goto bottom;
293
294             warnx("plist format revision (%d.%d) is higher than supported"
295                   "(%d.%d)", pkg->fmtver_maj, pkg->fmtver_mnr,
296                   PLIST_FMT_VER_MAJOR, PLIST_FMT_VER_MINOR);
297             if (pkg->fmtver_maj > PLIST_FMT_VER_MAJOR) {
298                 cleanup(0);
299                 exit(2);
300             }
301         }
302 bottom:
303         add_plist(pkg, cmd, cp);
304     }
305 }
306
307 /* Write a packing list to a file, converting commands to ascii equivs */
308 void
309 write_plist(Package *pkg, FILE *fp)
310 {
311     PackingList plist = pkg->head;
312
313     while (plist) {
314         switch(plist->type) {
315         case PLIST_FILE:
316             fprintf(fp, "%s\n", plist->name);
317             break;
318
319         case PLIST_CWD:
320             fprintf(fp, "%ccwd %s\n", CMD_CHAR, plist->name);
321             break;
322
323         case PLIST_SRC:
324             fprintf(fp, "%csrcdir %s\n", CMD_CHAR, plist->name);
325             break;
326
327         case PLIST_CMD:
328             fprintf(fp, "%cexec %s\n", CMD_CHAR, plist->name);
329             break;
330
331         case PLIST_UNEXEC:
332             fprintf(fp, "%cunexec %s\n", CMD_CHAR, plist->name);
333             break;
334
335         case PLIST_CHMOD:
336             fprintf(fp, "%cmode %s\n", CMD_CHAR, plist->name ? plist->name : "");
337             break;
338
339         case PLIST_CHOWN:
340             fprintf(fp, "%cowner %s\n", CMD_CHAR, plist->name ? plist->name : "");
341             break;
342
343         case PLIST_CHGRP:
344             fprintf(fp, "%cgroup %s\n", CMD_CHAR, plist->name ? plist->name : "");
345             break;
346
347         case PLIST_COMMENT:
348             fprintf(fp, "%ccomment %s\n", CMD_CHAR, plist->name);
349             break;
350
351         case PLIST_IGNORE:
352         case PLIST_IGNORE_INST:         /* a one-time non-ignored file */
353             fprintf(fp, "%cignore\n", CMD_CHAR);
354             break;
355
356         case PLIST_NAME:
357             fprintf(fp, "%cname %s\n", CMD_CHAR, plist->name);
358             break;
359
360         case PLIST_DISPLAY:
361             fprintf(fp, "%cdisplay %s\n", CMD_CHAR, plist->name);
362             break;
363
364         case PLIST_PKGDEP:
365             fprintf(fp, "%cpkgdep %s\n", CMD_CHAR, plist->name);
366             break;
367
368         case PLIST_CONFLICTS:
369             fprintf(fp, "%cconflicts %s\n", CMD_CHAR, plist->name);
370             break;
371
372         case PLIST_MTREE:
373             fprintf(fp, "%cmtree %s\n", CMD_CHAR, plist->name);
374             break;
375
376         case PLIST_DIR_RM:
377             fprintf(fp, "%cdirrm %s\n", CMD_CHAR, plist->name);
378             break;
379
380         case PLIST_OPTION:
381             fprintf(fp, "%coption %s\n", CMD_CHAR, plist->name);
382             break;
383
384         case PLIST_ORIGIN:
385             fprintf(fp, "%ccomment ORIGIN:%s\n", CMD_CHAR, plist->name);
386             break;
387
388         case PLIST_DEPORIGIN:
389             fprintf(fp, "%ccomment DEPORIGIN:%s\n", CMD_CHAR, plist->name);
390             break;
391
392         default:
393             cleanup(0);
394             errx(2, "%s: unknown command type %d (%s)", __func__,
395                 plist->type, plist->name);
396             break;
397         }
398         plist = plist->next;
399     }
400 }
401
402 /*
403  * Delete the results of a package installation.
404  *
405  * This is here rather than in the pkg_delete code because pkg_add needs to
406  * run it too in cases of failure.
407  */
408 int
409 delete_package(Boolean ign_err, Boolean nukedirs, Package *pkg)
410 {
411     PackingList p;
412     char *dn = NULL;
413     const char *Where = ".", *last_file = "";
414     Boolean fail = SUCCESS;
415     Boolean preserve;
416     char tmp[FILENAME_MAX], *name = NULL;
417
418     preserve = find_plist_option(pkg, "preserve") ? TRUE : FALSE;
419     for (p = pkg->head; p; p = p->next) {
420         switch (p->type)  {
421         case PLIST_NAME:
422             name = p->name;
423             break;
424
425         case PLIST_IGNORE:
426             p = p->next;
427             break;
428
429         case PLIST_CWD:
430             if (dn != NULL)
431                     free(dn);
432             Where = dn = fake_chroot(p->name);
433             if (Verbose)
434                 printf("Change working directory to %s\n", Where);
435             break;
436
437         case PLIST_UNEXEC:
438             format_cmd(tmp, FILENAME_MAX, p->name, Where, last_file);
439             if (Verbose)
440                 printf("Execute '%s'\n", tmp);
441             if (!Fake && system(tmp)) {
442                 warnx("unexec command for '%s' failed", tmp);
443                 fail = FAIL;
444             }
445             break;
446
447         case PLIST_FILE:
448             last_file = p->name;
449             sprintf(tmp, "%s/%s", Where, p->name);
450             if (isdir(tmp) && fexists(tmp) && !issymlink(tmp)) {
451                 warnx("cannot delete specified file '%s' - it is a directory!\n"
452            "this packing list is incorrect - ignoring delete request", tmp);
453             }
454             else {
455                 if (p->next && p->next->type == PLIST_COMMENT && !strncmp(p->next->name, "MD5:", 4)) {
456                     char *cp = NULL, buf[33];
457
458                     /*
459                      * For packing lists whose version is 1.1 or greater, the md5
460                      * hash for a symlink is calculated on the string returned
461                      * by readlink().
462                      */
463                     if (issymlink(tmp) && verscmp(pkg, 1, 0) > 0) {
464                         int len;
465                         char linkbuf[FILENAME_MAX];
466
467                         if ((len = readlink(tmp, linkbuf, FILENAME_MAX)) > 0)
468                              cp = MD5Data((unsigned char *)linkbuf, len, buf);
469                     } else if (isfile(tmp) || verscmp(pkg, 1, 1) < 0)
470                         cp = MD5File(tmp, buf);
471
472                     if (cp != NULL) {
473                         /* Mismatch? */
474                         if (strcmp(cp, p->next->name + 4)) {
475                             warnx("'%s' fails original MD5 checksum - %s",
476                                        tmp, Force ? "deleted anyway." : "not deleted.");
477                             if (!Force) {
478                                 fail = FAIL;
479                                 continue;
480                             }
481                         }
482                     }
483                 }
484                 if (Verbose)
485                     printf("Delete file %s\n", tmp);
486                 if (!Fake) {
487                     if (delete_hierarchy(tmp, ign_err, nukedirs))
488                         fail = FAIL;
489                     if (preserve && name) {
490                         char tmp2[FILENAME_MAX];
491                             
492                         if (make_preserve_name(tmp2, FILENAME_MAX, name, tmp)) {
493                             if (fexists(tmp2)) {
494                                 if (rename(tmp2, tmp))
495                                    warn("preserve: unable to restore %s as %s",
496                                         tmp2, tmp);
497                             }
498                         }
499                     }
500                 }
501             }
502             break;
503
504         case PLIST_DIR_RM:
505             sprintf(tmp, "%s/%s", Where, p->name);
506             if (!isdir(tmp) && fexists(tmp)) {
507                 warnx("cannot delete specified directory '%s' - it is a file!\n"
508         "this packing list is incorrect - ignoring delete request", tmp);
509             }
510             else {
511                 if (Verbose)
512                     printf("Delete directory %s\n", tmp);
513                 if (!Fake && delete_hierarchy(tmp, ign_err, FALSE)) {
514                     warnx("unable to completely remove directory '%s'", tmp);
515                     fail = FAIL;
516                 }
517             }
518             last_file = p->name;
519             break;
520
521         default:
522             break;
523         }
524     }
525     free(dn);
526     return fail;
527 }
528
529 #ifdef DEBUG
530 #define RMDIR(dir) vsystem("%s %s", RMDIR_CMD, dir)
531 #define REMOVE(dir,ie) vsystem("%s %s%s", REMOVE_CMD, (ie ? "-f " : ""), dir)
532 #else
533 #define RMDIR rmdir
534 #define REMOVE(file,ie) (remove(file) && !(ie))
535 #endif
536
537 /* Selectively delete a hierarchy */
538 int
539 delete_hierarchy(const char *dir, Boolean ign_err, Boolean nukedirs)
540 {
541     char *cp1, *cp2;
542
543     cp1 = cp2 = strdup(dir);
544     if (!fexists(dir)) {
545         if (!ign_err)
546             warnx("%s '%s' doesn't really exist",
547                 isdir(dir) ? "directory" : "file", dir);
548         return !ign_err;
549     }
550     else if (nukedirs) {
551         if (vsystem("%s -r%s %s", REMOVE_CMD, (ign_err ? "f" : ""), dir))
552             return 1;
553     }
554     else if (isdir(dir) && !issymlink(dir)) {
555         if (RMDIR(dir) && !ign_err)
556             return 1;
557     }
558     else {
559         if (REMOVE(dir, ign_err))
560             return 1;
561     }
562
563     if (!nukedirs)
564         return 0;
565     while (cp2) {
566         if ((cp2 = strrchr(cp1, '/')) != NULL)
567             *cp2 = '\0';
568         if (!isemptydir(dir))
569             return 0;
570         if (RMDIR(dir) && !ign_err) {
571             if (!fexists(dir))
572                 warnx("directory '%s' doesn't really exist", dir);
573             else
574                 return 1;
575         }
576         /* back up the pathname one component */
577         if (cp2) {
578             cp1 = strdup(dir);
579         }
580     }
581     return 0;
582 }