xref: /minix/external/bsd/pkg_install/dist/lib/plist.c (revision 0a6a1f1d)
1 /*	$NetBSD: plist.c,v 1.1.1.5 2009/08/06 16:55:28 joerg Exp $	*/
2 
3 #if HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 #include <nbcompat.h>
7 #if HAVE_SYS_CDEFS_H
8 #include <sys/cdefs.h>
9 #endif
10 __RCSID("$NetBSD: plist.c,v 1.1.1.5 2009/08/06 16:55:28 joerg Exp $");
11 
12 /*
13  * FreeBSD install - a package for the installation and maintainance
14  * of non-core utilities.
15  *
16  * Redistribution and use in source and binary forms, with or without
17  * modification, are permitted provided that the following conditions
18  * are met:
19  * 1. Redistributions of source code must retain the above copyright
20  *    notice, this list of conditions and the following disclaimer.
21  * 2. Redistributions in binary form must reproduce the above copyright
22  *    notice, this list of conditions and the following disclaimer in the
23  *    documentation and/or other materials provided with the distribution.
24  *
25  * Jordan K. Hubbard
26  * 18 July 1993
27  *
28  * General packing list routines.
29  *
30  */
31 
32 /*-
33  * Copyright (c) 2008, 2009 Joerg Sonnenberger <joerg@NetBSD.org>.
34  * All rights reserved.
35  *
36  * Redistribution and use in source and binary forms, with or without
37  * modification, are permitted provided that the following conditions
38  * are met:
39  *
40  * 1. Redistributions of source code must retain the above copyright
41  *    notice, this list of conditions and the following disclaimer.
42  * 2. Redistributions in binary form must reproduce the above copyright
43  *    notice, this list of conditions and the following disclaimer in
44  *    the documentation and/or other materials provided with the
45  *    distribution.
46  *
47  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
48  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
49  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
50  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
51  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
52  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
53  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
54  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
55  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
56  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
57  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
58  * SUCH DAMAGE.
59  */
60 
61 #include "lib.h"
62 #if HAVE_ERRNO_H
63 #include <errno.h>
64 #endif
65 #if HAVE_ERR_H
66 #include <err.h>
67 #endif
68 #ifndef NETBSD
69 #include <nbcompat/md5.h>
70 #else
71 #include <md5.h>
72 #endif
73 
74 static int     delete_with_parents(const char *, Boolean, Boolean);
75 
76 /* This struct defines a plist command type */
77 typedef struct cmd_t {
78 	const char   *c_s;		/* string to recognise */
79 	pl_ent_t c_type;	/* type of command */
80 	int     c_argc;		/* # of arguments */
81 	int     c_subst;	/* can substitute real prefix */
82 }       cmd_t;
83 
84 /* Commands to recognise */
85 static const cmd_t cmdv[] = {
86 	{"cwd", PLIST_CWD, 1, 1},
87 	{"src", PLIST_SRC, 1, 1},
88 	{"exec", PLIST_CMD, 1, 0},
89 	{"unexec", PLIST_UNEXEC, 1, 0},
90 	{"mode", PLIST_CHMOD, 1, 0},
91 	{"owner", PLIST_CHOWN, 1, 0},
92 	{"group", PLIST_CHGRP, 1, 0},
93 	{"comment", PLIST_COMMENT, 1, 0},
94 	{"ignore", PLIST_IGNORE, 0, 0},
95 	{"name", PLIST_NAME, 1, 0},
96 	{"display", PLIST_DISPLAY, 1, 0},
97 	{"pkgdep", PLIST_PKGDEP, 1, 0},
98 	{"pkgcfl", PLIST_PKGCFL, 1, 0},
99 	{"pkgdir", PLIST_PKGDIR, 1, 0},
100 	{"dirrm", PLIST_DIR_RM, 1, 0},
101 	{"option", PLIST_OPTION, 1, 0},
102 	{"blddep", PLIST_BLDDEP, 1, 0},
103 	{NULL, FAIL, 0, 0}
104 };
105 
106 /*
107  * Add an item to the end of a packing list
108  */
109 void
110 add_plist(package_t *p, pl_ent_t type, const char *arg)
111 {
112 	plist_t *tmp;
113 
114 	tmp = new_plist_entry();
115 	tmp->name = (arg == NULL) ? NULL : xstrdup(arg);
116 	tmp->type = type;
117 	if (!p->head) {
118 		p->head = p->tail = tmp;
119 	} else {
120 		tmp->prev = p->tail;
121 		p->tail->next = tmp;
122 		p->tail = tmp;
123 	}
124 }
125 
126 /*
127  * Add an item to the start of a packing list
128  */
129 void
130 add_plist_top(package_t *p, pl_ent_t type, const char *arg)
131 {
132 	plist_t *tmp;
133 
134 	tmp = new_plist_entry();
135 	tmp->name = (arg == NULL) ? NULL : xstrdup(arg);
136 	tmp->type = type;
137 	if (!p->head) {
138 		p->head = p->tail = tmp;
139 	} else {
140 		tmp->next = p->head;
141 		p->head->prev = tmp;
142 		p->head = tmp;
143 	}
144 }
145 
146 /*
147  * Return the last (most recent) entry in a packing list
148  */
149 plist_t *
150 last_plist(package_t *p)
151 {
152 	return p->tail;
153 }
154 
155 /*
156  * Mark all items in a packing list to prevent iteration over them
157  */
158 void
159 mark_plist(package_t *pkg)
160 {
161 	plist_t *pp;
162 
163 	for (pp = pkg->head; pp; pp = pp->next) {
164 		pp->marked = TRUE;
165 	}
166 }
167 
168 /*
169  * Find a given item in a packing list and, if so, return it (else NULL)
170  */
171 plist_t *
172 find_plist(package_t *pkg, pl_ent_t type)
173 {
174 	plist_t *pp;
175 
176 	for (pp = pkg->head; pp && pp->type != type; pp = pp->next) {
177 	}
178 	return pp;
179 }
180 
181 /*
182  * Look for a specific boolean option argument in the list
183  */
184 char   *
185 find_plist_option(package_t *pkg, const char *name)
186 {
187 	plist_t *p;
188 
189 	for (p = pkg->head; p; p = p->next) {
190 		if (p->type == PLIST_OPTION
191 		    && strcmp(p->name, name) == 0) {
192 			return p->name;
193 		}
194 	}
195 
196 	return (char *) NULL;
197 }
198 
199 /*
200  * Delete plist item 'type' in the list (if 'name' is non-null, match it
201  * too.)  If 'all' is set, delete all items, not just the first occurance.
202  */
203 void
204 delete_plist(package_t *pkg, Boolean all, pl_ent_t type, char *name)
205 {
206 	plist_t *p = pkg->head;
207 
208 	while (p) {
209 		plist_t *pnext = p->next;
210 
211 		if (p->type == type && (!name || !strcmp(name, p->name))) {
212 			free(p->name);
213 			if (p->prev)
214 				p->prev->next = pnext;
215 			else
216 				pkg->head = pnext;
217 			if (pnext)
218 				pnext->prev = p->prev;
219 			else
220 				pkg->tail = p->prev;
221 			free(p);
222 			if (!all)
223 				return;
224 			p = pnext;
225 		} else
226 			p = p->next;
227 	}
228 }
229 
230 /*
231  * Allocate a new packing list entry, and return a pointer to it.
232  */
233 plist_t *
234 new_plist_entry(void)
235 {
236 	return xcalloc(1, sizeof(plist_t));
237 }
238 
239 /*
240  * Free an entire packing list
241  */
242 void
243 free_plist(package_t *pkg)
244 {
245 	plist_t *p = pkg->head;
246 
247 	while (p) {
248 		plist_t *p1 = p->next;
249 
250 		free(p->name);
251 		free(p);
252 		p = p1;
253 	}
254 	pkg->head = pkg->tail = NULL;
255 }
256 
257 /*
258  * For an ASCII string denoting a plist command, return its code and
259  * optionally its argument(s)
260  */
261 static int
262 plist_cmd(const char *s, char **arg)
263 {
264 	const cmd_t *cmdp;
265 	const char *cp, *sp;
266 	char *sp2;
267 
268 	sp = NULL; /* Older GCC can't detect that the loop is executed */
269 
270 	for (cmdp = cmdv; cmdp->c_s; ++cmdp) {
271 		for (sp = s, cp = cmdp->c_s; *sp && *cp; ++cp, ++sp)
272 			if (*sp != *cp)
273 				break;
274 		if (*cp == '\0')
275 			break;
276 	}
277 
278 	if (cmdp->c_s == NULL || arg == NULL)
279 		return cmdp->c_type;
280 
281 	while (isspace((unsigned char)*sp))
282 		++sp;
283 	*arg = xstrdup(sp);
284 	if (*sp) {
285 		sp2 = *arg + strlen(*arg) - 1;
286 		/*
287 		 * The earlier loop ensured that at least one non-whitespace
288 		 * is in the string.
289 		 */
290 		while (isspace((unsigned char)*sp2))
291 			--sp2;
292 		sp2[1] = '\0';
293 	}
294 	return cmdp->c_type;
295 }
296 
297 /*
298  * Parse a packaging list from a memory buffer.
299  */
300 void
301 parse_plist(package_t *pkg, const char *buf)
302 {
303 	int cmd;
304 	char *line, *cp;
305 	const char *eol, *next;
306 	size_t len;
307 
308 	pkg->head = NULL;
309 	pkg->tail = NULL;
310 
311 	for (; *buf; buf = next) {
312 		/* Until add_plist can deal with trailing whitespace. */
313 		if ((eol = strchr(buf, '\n')) != NULL) {
314 			next = eol + 1;
315 			len = eol - buf;
316 		} else {
317 			len = strlen(buf);
318 			next = buf + len;
319 		}
320 
321 		while (len && isspace((unsigned char)buf[len - 1]))
322 			--len;
323 
324 		if (len == 0)
325 			continue;
326 
327 		line = xmalloc(len + 1);
328 		memcpy(line, buf, len);
329 		line[len] = '\0';
330 
331 		if (*(cp = line) == CMD_CHAR) {
332 			if ((cmd = plist_cmd(line + 1, &cp)) == FAIL) {
333 				warnx("Unrecognised PLIST command `%s'", line);
334 				continue;
335 			}
336 			if (*cp == '\0') {
337 				free(cp);
338 				cp = NULL;
339 			}
340 		} else {
341 			cmd = PLIST_FILE;
342 		}
343 		add_plist(pkg, cmd, cp);
344 		free(cp);
345 	}
346 }
347 
348 /*
349  * Read a packing list from a file
350  */
351 void
352 append_plist(package_t *pkg, FILE * fp)
353 {
354 	char    pline[MaxPathSize];
355 	char   *cp;
356 	int     cmd;
357 	int     len;
358 	int	free_cp;
359 
360 	while (fgets(pline, MaxPathSize, fp) != (char *) NULL) {
361 		for (len = strlen(pline); len &&
362 		    isspace((unsigned char) pline[len - 1]);) {
363 			pline[--len] = '\0';
364 		}
365 		if (len == 0) {
366 			continue;
367 		}
368 		free_cp = 0;
369 		if (*(cp = pline) == CMD_CHAR) {
370 			if ((cmd = plist_cmd(pline + 1, &cp)) == FAIL) {
371 				warnx("Unrecognised PLIST command `%s'", pline);
372 				continue;
373 			}
374 			if (*cp == '\0') {
375 				free(cp);
376 				cp = NULL;
377 			}
378 			free_cp = 1;
379 		} else {
380 			cmd = PLIST_FILE;
381 		}
382 		add_plist(pkg, cmd, cp);
383 		if (free_cp)
384 			free(cp);
385 	}
386 }
387 
388 void
389 read_plist(package_t *pkg, FILE * fp)
390 {
391 	pkg->head = NULL;
392 	pkg->tail = NULL;
393 
394 	append_plist(pkg, fp);
395 }
396 
397 /*
398  * Write a packing list to a file, converting commands to ASCII equivs
399  */
400 void
401 write_plist(package_t *pkg, FILE * fp, char *realprefix)
402 {
403 	plist_t *p;
404 	const cmd_t *cmdp;
405 
406 	for (p = pkg->head; p; p = p->next) {
407 		if (p->type == PLIST_FILE) {
408 			/* Fast-track files - these are the most common */
409 			(void) fprintf(fp, "%s\n", p->name);
410 			continue;
411 		}
412 		for (cmdp = cmdv; cmdp->c_type != FAIL && cmdp->c_type != p->type; cmdp++) {
413 		}
414 		if (cmdp->c_type == FAIL) {
415 			warnx("Unknown PLIST command type %d (%s)", p->type, p->name);
416 		} else if (cmdp->c_argc == 0) {
417 			(void) fprintf(fp, "%c%s\n", CMD_CHAR, cmdp->c_s);
418 		} else if (cmdp->c_subst && realprefix) {
419 			(void) fprintf(fp, "%c%s %s\n", CMD_CHAR, cmdp->c_s, realprefix);
420 		} else {
421 			(void) fprintf(fp, "%c%s %s\n", CMD_CHAR, cmdp->c_s,
422 			    (p->name) ? p->name : "");
423 		}
424 	}
425 }
426 
427 /*
428  * Like write_plist, but compute memory string.
429  */
430 void
431 stringify_plist(package_t *pkg, char **real_buf, size_t *real_len,
432     const char *realprefix)
433 {
434 	plist_t *p;
435 	const cmd_t *cmdp;
436 	char *buf;
437 	size_t len;
438 	int item_len;
439 
440 	/* Pass One: compute output size only. */
441 	len = 0;
442 
443 	for (p = pkg->head; p; p = p->next) {
444 		if (p->type == PLIST_FILE) {
445 			len += strlen(p->name) + 1;
446 			continue;
447 		}
448 		for (cmdp = cmdv; cmdp->c_type != FAIL && cmdp->c_type != p->type; cmdp++) {
449 		}
450 		if (cmdp->c_type == FAIL)
451 			continue;
452 		if (cmdp->c_argc == 0)
453 			len += 1 + strlen(cmdp->c_s) + 1;
454 		else if (cmdp->c_subst && realprefix)
455 			len += 1 + strlen(cmdp->c_s) + 1 + strlen(realprefix) + 1;
456 		else
457 			len += 1 + strlen(cmdp->c_s) + 1 + strlen(p->name ? p->name : "") + 1;
458 	}
459 
460 	/* Pass Two: build actual string. */
461 	buf = xmalloc(len + 1);
462 	*real_buf = buf;
463 	*real_len = len;
464 	++len;
465 
466 #define	UPDATE_LEN							\
467 do {									\
468 	if (item_len < 0 || (size_t)item_len > len)			\
469 		errx(2, "Size computation failed, aborted.");		\
470 	buf += item_len;						\
471 	len -= item_len;						\
472 } while (/* CONSTCOND */0)
473 
474 	for (p = pkg->head; p; p = p->next) {
475 		if (p->type == PLIST_FILE) {
476 			/* Fast-track files - these are the most common */
477 			item_len = snprintf(buf, len, "%s\n", p->name);
478 			UPDATE_LEN;
479 			continue;
480 		}
481 		for (cmdp = cmdv; cmdp->c_type != FAIL && cmdp->c_type != p->type; cmdp++) {
482 		}
483 		if (cmdp->c_type == FAIL) {
484 			warnx("Unknown PLIST command type %d (%s)", p->type, p->name);
485 		} else if (cmdp->c_argc == 0) {
486 			item_len = snprintf(buf, len, "%c%s\n", CMD_CHAR, cmdp->c_s);
487 			UPDATE_LEN;
488 		} else if (cmdp->c_subst && realprefix) {
489 			item_len = snprintf(buf, len, "%c%s %s\n", CMD_CHAR, cmdp->c_s, realprefix);
490 			UPDATE_LEN;
491 		} else {
492 			item_len = snprintf(buf, len, "%c%s %s\n", CMD_CHAR, cmdp->c_s,
493 			    (p->name) ? p->name : "");
494 			UPDATE_LEN;
495 		}
496 	}
497 
498 	if (len != 1)
499 		errx(2, "Size computation failed, aborted.");
500 }
501 
502 /*
503  * Delete the results of a package installation.
504  *
505  * This is here rather than in the pkg_delete code because pkg_add needs to
506  * run it too in cases of failure.
507  */
508 int
509 delete_package(Boolean ign_err, package_t *pkg, Boolean NoDeleteFiles,
510     const char *destdir)
511 {
512 	plist_t *p;
513 	const char *last_file = "";
514 	int     fail = SUCCESS;
515 	Boolean preserve;
516 	char    tmp[MaxPathSize];
517 	const char *prefix = NULL, *name = NULL;
518 
519 	if (!pkgdb_open(ReadWrite)) {
520 		err(EXIT_FAILURE, "cannot open pkgdb");
521 	}
522 
523 	preserve = find_plist_option(pkg, "preserve") ? TRUE : FALSE;
524 
525 	for (p = pkg->head; p; p = p->next) {
526 		switch (p->type) {
527 		case PLIST_NAME:
528 			name = p->name;
529 			break;
530 		case PLIST_CWD:
531 			if (prefix == NULL)
532 				prefix = p->name;
533 			break;
534 		default:
535 			break;
536 		}
537 	}
538 
539 	if (name == NULL || prefix == NULL)
540 		errx(EXIT_FAILURE, "broken PLIST");
541 
542 	/*
543 	 * Remove database entries first, directory removal is done
544 	 * in the main loop below.
545 	 */
546 	for (p = pkg->head; p; p = p->next) {
547 		if (p->type == PLIST_PKGDIR)
548 			delete_pkgdir(name, prefix, p->name);
549 	}
550 
551 	for (p = pkg->head; p; p = p->next) {
552 		switch (p->type) {
553 		case PLIST_NAME:
554 			/* Handled already */
555 			break;
556 
557 		case PLIST_PKGDIR:
558 		case PLIST_DIR_RM:
559 			(void) snprintf(tmp, sizeof(tmp), "%s/%s",
560 			    prefix, p->name);
561 			if (has_pkgdir(tmp))
562 				continue;
563 			(void) snprintf(tmp, sizeof(tmp), "%s%s%s/%s",
564 			    destdir ? destdir : "", destdir ? "/" : "",
565 			    prefix, p->name);
566 			if (!fexists(tmp)) {
567 				if (p->type == PLIST_PKGDIR)
568 					warnx("Directory `%s' disappeared, skipping", tmp);
569 			} else if (!isdir(tmp)) {
570 				warnx("attempting to delete a file `%s' as a directory\n"
571 				    "this packing list is incorrect - ignoring delete request", tmp);
572 			} else if (delete_with_parents(tmp, ign_err, TRUE))
573 				fail = FAIL;
574 			break;
575 
576 		case PLIST_IGNORE:
577 			p = p->next;
578 			break;
579 
580 		case PLIST_UNEXEC:
581 			if (NoDeleteFiles)
582 				break;
583 			format_cmd(tmp, sizeof(tmp), p->name, prefix, last_file);
584 			printf("Executing `%s'\n", tmp);
585 			if (!Fake && system(tmp)) {
586 				warnx("unexec command for `%s' failed", tmp);
587 				fail = FAIL;
588 			}
589 			break;
590 
591 		case PLIST_FILE:
592 			last_file = p->name;
593 			(void) snprintf(tmp, sizeof(tmp), "%s%s%s/%s",
594 			    destdir ? destdir : "", destdir ? "/" : "",
595 			    prefix, p->name);
596 			if (isdir(tmp)) {
597 				warnx("attempting to delete directory `%s' as a file\n"
598 				    "this packing list is incorrect - ignoring delete request", tmp);
599 			} else {
600 				int     restored = 0;	/* restored from preserve? */
601 
602 				if (p->next && p->next->type == PLIST_COMMENT) {
603 					if (strncmp(p->next->name, CHECKSUM_HEADER, ChecksumHeaderLen) == 0) {
604 						char   *cp, buf[LegibleChecksumLen];
605 
606 						if ((cp = MD5File(tmp, buf)) != NULL) {
607 							/* Mismatch? */
608 							if (strcmp(cp, p->next->name + ChecksumHeaderLen) != 0) {
609 								printf("original MD5 checksum failed, %s: %s\n",
610 								    Force ? "deleting anyway" : "not deleting", tmp);
611 								if (!Force) {
612 									fail = FAIL;
613 									goto pkgdb_cleanup;
614 								}
615 							}
616 						}
617 					} else if (strncmp(p->next->name, SYMLINK_HEADER, SymlinkHeaderLen) == 0) {
618 						char	buf[MaxPathSize + SymlinkHeaderLen];
619 						int	cc;
620 
621 						(void) strlcpy(buf, SYMLINK_HEADER,
622 						    sizeof(buf));
623 						if ((cc = readlink(tmp, &buf[SymlinkHeaderLen],
624 							  sizeof(buf) - SymlinkHeaderLen - 1)) < 0) {
625 							warn("can't readlink `%s'", tmp);
626 							goto pkgdb_cleanup;
627 						}
628 						buf[SymlinkHeaderLen + cc] = 0x0;
629 						if (strcmp(buf, p->next->name) != 0) {
630 							if ((cc = readlink(&buf[SymlinkHeaderLen], &buf[SymlinkHeaderLen],
631 								  sizeof(buf) - SymlinkHeaderLen)) < 0) {
632 								printf("symlink %s is not same as recorded value, %s: %s\n",
633 								    buf, Force ? "deleting anyway" : "not deleting", tmp);
634 								if (!Force) {
635 									fail = FAIL;
636 									goto pkgdb_cleanup;
637 								}
638 							}
639 							buf[SymlinkHeaderLen + cc] = 0x0;
640 							if (strcmp(buf, p->next->name) != 0) {
641 								printf("symlink %s is not same as recorded value, %s: %s\n",
642 								    buf, Force ? "deleting anyway" : "not deleting", tmp);
643 								if (!Force) {
644 									fail = FAIL;
645 									goto pkgdb_cleanup;
646 								}
647 							}
648 						}
649 					}
650 				}
651 				if (Verbose && !NoDeleteFiles)
652 					printf("Delete file %s\n", tmp);
653 				if (!Fake && !NoDeleteFiles) {
654 					if (delete_with_parents(tmp, ign_err, FALSE))
655 						fail = FAIL;
656 					if (preserve && name) {
657 						char    tmp2[MaxPathSize];
658 
659 						if (make_preserve_name(tmp2, MaxPathSize, name, tmp)) {
660 							if (fexists(tmp2)) {
661 								if (rename(tmp2, tmp))
662 									warn("preserve: unable to restore %s as %s",
663 									    tmp2, tmp);
664 								else
665 									restored = 1;
666 							}
667 						}
668 					}
669 				}
670 
671 pkgdb_cleanup:
672 				if (!Fake) {
673 					if (!restored) {
674 						errno = 0;
675 						if (pkgdb_remove(tmp) && errno)
676 							perror("pkgdb_remove");
677 					}
678 				}
679 			}
680 			break;
681 		default:
682 			break;
683 		}
684 	}
685 	pkgdb_close();
686 	return fail;
687 }
688 
689 /*
690  * Selectively delete a hierarchy
691  * Returns 1 on error, 0 else.
692  */
693 static int
694 delete_with_parents(const char *fname, Boolean ign_err, Boolean ign_nonempty)
695 {
696 	char   *cp, *cp2;
697 
698 	if (remove(fname)) {
699 		if (!ign_err && (!ign_nonempty || errno != ENOTEMPTY))
700 			warnx("Couldn't remove %s", fname);
701 		return 0;
702 	}
703 	cp = xstrdup(fname);
704 	while (*cp) {
705 		if ((cp2 = strrchr(cp, '/')) != NULL)
706 			*cp2 = '\0';
707 		if (!isemptydir(cp))
708 			break;
709 		if (has_pkgdir(cp))
710 			break;
711 		if (rmdir(cp))
712 			break;
713 	}
714 	free(cp);
715 
716 	return 0;
717 }
718 
719 void
720 add_pkgdir(const char *pkg, const char *prefix, const char *path)
721 {
722 	char *fullpath, *oldvalue, *newvalue;
723 
724 	fullpath = xasprintf("%s/%s", prefix, path);
725 	oldvalue = pkgdb_retrieve(fullpath);
726 	if (oldvalue) {
727 		if (strncmp(oldvalue, "@pkgdir ", 8) != 0)
728 			errx(EXIT_FAILURE, "Internal error while processing pkgdb, run pkg_admin rebuild");
729 		newvalue = xasprintf("%s %s", oldvalue, pkg);
730 		pkgdb_remove(fullpath);
731 	} else {
732 		newvalue = xasprintf("@pkgdir %s", pkg);
733 	}
734 	pkgdb_store(fullpath, newvalue);
735 
736 	free(fullpath);
737 	free(newvalue);
738 }
739 
740 void
741 delete_pkgdir(const char *pkg, const char *prefix, const char *path)
742 {
743 	size_t pkg_len, len;
744 	char *fullpath, *oldvalue, *newvalue, *iter;
745 
746 	fullpath = xasprintf("%s/%s", prefix, path);
747 	oldvalue = pkgdb_retrieve(fullpath);
748 	if (oldvalue && strncmp(oldvalue, "@pkgdir ", 8) == 0) {
749 		newvalue = xstrdup(oldvalue);
750 		iter = newvalue + 8;
751 		pkg_len = strlen(pkg);
752 		while (*iter) {
753 			if (strncmp(iter, pkg, pkg_len) == 0 &&
754 			    (iter[pkg_len] == ' ' || iter[pkg_len] == '\0')) {
755 				len = strlen(iter + pkg_len);
756 				memmove(iter, iter + pkg_len + 1, len);
757 				if (len == 0)
758 					*iter = '\0';
759 			} else {
760 				iter += strcspn(iter, " ");
761 				iter += strspn(iter, " ");
762 			}
763 		}
764 		pkgdb_remove(fullpath);
765 		if (iter != newvalue + 8)
766 			pkgdb_store(fullpath, newvalue);
767 		free(newvalue);
768 	}
769 	free(fullpath);
770 }
771 
772 int
773 has_pkgdir(const char *path)
774 {
775 	const char *value;
776 
777 	value = pkgdb_retrieve(path);
778 
779 	if (value && strncmp(value, "@pkgdir ", 8) == 0)
780 		return 1;
781 	else
782 		return 0;
783 }
784