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