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