#include "udpkg.h" #include #include #include #include #include #include #include /* Status file handling routines * * This is a fairly minimalistic implementation. there are two main functions * that are supported: * * 1) reading the entire status file: * the status file is read into memory as a binary-tree, with just the * package and status info preserved * * 2) merging the status file * control info from (new) packages is merged into the status file, * replacing any pre-existing entries. when a merge happens, status info * read using the status_read function is written back to the status file * * There is also a pair of functions to lock and unlock access to the * status, to protect against multiple invocations of udpkg racing * against each other. Callers are responsible for using this locking * around any code that works with the status file. */ static int lock_fd = -1; static const char *statuswords[][10] = { { (char *)STATUS_WANTSTART, "unknown", "install", "hold", "deinstall", "purge", 0 }, { (char *)STATUS_FLAGSTART, "ok", "reinstreq", "hold", "hold-reinstreq", 0 }, { (char *)STATUS_STATUSSTART, "not-installed", "unpacked", "half-configured", "installed", "half-installed", "config-files", "post-inst-failed", "removal-failed", 0 } }; int package_compare(const void *p1, const void *p2) { return strcmp(((struct package_t *)p1)->package, ((struct package_t *)p2)->package); } static unsigned long status_parse(const char *line) { char *p; int i, j; unsigned long l = 0; for (i = 0; i < 3; i++) { p = strchr(line, ' '); if (p) *p = 0; j = 1; while (statuswords[i][j] != 0) { if (strcmp(line, statuswords[i][j]) == 0) { l |= (1 << ((long)statuswords[i][0] + j - 1)); break; } j++; } if (statuswords[i][j] == 0) return 0; /* parse error */ line = p+1; } return l; } static const char *status_print(unsigned long flags) { /* this function returns a static buffer... */ static char buf[256]; int i, j; buf[0] = 0; for (i = 0; i < 3; i++) { j = 1; while (statuswords[i][j] != 0) { if ((flags & (1 << ((long)statuswords[i][0] + j - 1))) != 0) { strcat(buf, statuswords[i][j]); if (i < 2) strcat(buf, " "); break; } j++; } if (statuswords[i][j] == 0) { fprintf(stderr, "corrupted status flag!!: %lx\n",flags); return NULL; } } return buf; } static char *read_block(FILE *f) { char ch; char *multiple_lines = strdup(""); size_t len = 0; char buf[BUFSIZE]; while (((ch = fgetc(f)) == ' ') && !feof(f)) { size_t buflen; if (fgets(buf, BUFSIZE, f) == NULL) { if (ferror(f)) perror("fgets"); break; } buflen = strlen(buf); multiple_lines = (char *) di_realloc(multiple_lines, len + buflen + 2); memset(multiple_lines + len, '\0', buflen + 2); strcat(multiple_lines, " "); strcat(multiple_lines, buf); len += buflen + 1; } ungetc(ch, f); return multiple_lines; } /* * Read a control file (or a stanza of a status file) and parse it, * filling parsed fields into the package structure */ void control_read(FILE *f, struct package_t *p) { char buf[BUFSIZE]; while (fgets(buf, BUFSIZE, f) && !feof(f)) { buf[strlen(buf)-1] = 0; if (*buf == 0) return; /* these are common to both installed and uninstalled packages */ else if (strstr(buf, "Package: ") == buf) { p->package = strdup(buf+9); } else if (strstr(buf, "Status: ") == buf) { p->status = status_parse(buf+8); } else if (strstr(buf, "Depends: ") == buf) { p->depends = strdup(buf+9); } else if (strstr(buf, "Provides: ") == buf) { p->provides = strdup(buf+10); } else if (strstr(buf, "Description: ") == buf) { p->description = strdup(buf+13); p->long_description = read_block(f); } #ifdef SUPPORTL10N else if (strstr(buf, "description-") == buf) { /* Localized description */ struct language_description_t *l; l = di_malloc(sizeof(struct language_description_t)); memset(l,'\0',sizeof (struct language_description_t)); l->next = p->localized_descriptions; p->localized_descriptions = l; buf[14] = '\0'; l->language = strdup(buf+12); l->description = strdup(buf+16); l->long_description = read_block(f); } #endif /* This is specific to the Debian Installer. Ifdef? */ else if (strcasestr(buf, "Installer-Menu-Item: ") == buf) { p->installer_menu_item = atoi(buf+21); } else if (strcasestr(buf, "Priority: ") == buf) { p->priority = strdup(buf + 10); } else if (strcasestr(buf, "Section: ") == buf) { p->section = strdup(buf + 9); } else if (strcasestr(buf, "Installed-Size: ") == buf) { p->installed_size = strdup(buf + 16); } else if (strcasestr(buf, "Maintainer: ") == buf) { p->maintainer = strdup(buf + 12); } else if (strcasestr(buf, "Version: ") == buf) { p->version = strdup(buf + 9); } else if (strcasestr(buf, "Suggests: ") == buf) { p->suggests = strdup(buf + 10); } else if (strcasestr(buf, "Recommends: ") == buf) { p->recommends = strdup(buf + 12); } else if (strcasestr(buf, "Conffiles: ") == buf) { p->conffiles = read_block(f); } } } void status_lock(void) { int error; lock_fd = open(STATUSFILELOCK, O_WRONLY | O_CREAT, 0200); if (lock_fd < 0) { fprintf(stderr, "Unable to open lockfile %s (%d), abort!", STATUSFILELOCK, errno); exit(1); } error = flock(lock_fd, LOCK_EX); if (error < 0) { fprintf(stderr, "Unable to lock lockfile %s (%d), abort!", STATUSFILELOCK, errno); exit(1); } } void status_unlock(void) { int error; if (lock_fd < 0) { fprintf(stderr, "Trying to unlock when we don't have the lockfile %s open!", STATUSFILELOCK); exit(1); } error = flock(lock_fd, LOCK_UN); if (error < 0) { fprintf(stderr, "Unable to unlock lockfile %s (%d), abort!", STATUSFILELOCK, errno); exit(1); } close(lock_fd); lock_fd = -1; } void *status_read(void) { FILE *f; void *status = 0; struct package_t *m = 0, *p = 0, *t = 0; if ((f = fopen(STATUSFILE, "r")) == NULL) { perror(STATUSFILE); return 0; } if (getenv(UDPKG_QUIET) == NULL) printf("(Reading database...)\n"); while (!feof(f)) { m = (struct package_t *)di_malloc(sizeof(struct package_t)); memset(m, 0, sizeof(struct package_t)); control_read(f, m); if (m->package) { /* * If there is an item in the tree by this name, * it must be a virtual package; insert real * package in preference. */ tdelete(m, &status, package_compare); tsearch(m, &status, package_compare); if (m->provides) { /* * A "Provides" triggers the insertion * of a pseudo package into the status * binary-tree. */ p = (struct package_t *)di_malloc(sizeof(struct package_t)); memset(p, 0, sizeof(struct package_t)); p->package = strdup(m->provides); t = *(struct package_t **)tsearch(p, &status, package_compare); if (!(t == p)) { di_free(p->package); di_free(p); } else { /* * Pseudo package status is the * same as the status of the * package providing it * FIXME: (not quite right, if 2 * packages of different statuses * provide it). */ t->status = m->status; } } } else { di_free(m); } } fclose(f); return status; } int status_merge(void *status, struct package_t *pkgs) { FILE *fin, *fout; char buf[BUFSIZE]; struct package_t *pkg = 0, *statpkg = 0; struct package_t locpkg; int r = 0; if ((fin = fopen(STATUSFILE, "r")) == NULL) { perror(STATUSFILE); return 0; } if ((fout = fopen(STATUSFILE ".new", "w")) == NULL) { perror(STATUSFILE ".new"); return 0; } if (getenv(UDPKG_QUIET) == NULL) printf("(Updating database...)\n"); while (fgets(buf, BUFSIZE, fin) && !feof(fin)) { buf[strlen(buf)-1] = 0; /* trim newline */ /* If we see a package header, find out if it's a package * that we have processed. if so, we skip that block for * now (write it at the end). * * we also look at packages in the status cache and update * their status fields */ if (strstr(buf, "Package: ") == buf) { for (pkg = pkgs; pkg != 0 && strcmp(buf+9, pkg->package)!=0; pkg = pkg->next) ; locpkg.package = buf+9; statpkg = tfind(&locpkg, &status, package_compare); /* note: statpkg should be non-zero, unless the status * file was changed while we are processing (no locking * is currently done... */ if (statpkg != 0) statpkg = *(struct package_t **)statpkg; } if (pkg != 0) continue; if (strstr(buf, "Status: ") == buf && statpkg != 0) { snprintf(buf, sizeof(buf), "Status: %s", status_print(statpkg->status)); } fputs(buf, fout); fputc('\n', fout); } // Print out packages we processed. for (pkg = pkgs; pkg != 0; pkg = pkg->next) { fprintf(fout, "Package: %s\nStatus: %s\n", pkg->package, status_print(pkg->status)); if (pkg->priority) fprintf(fout, "Priority: %s\n", pkg->priority); if (pkg->section) fprintf(fout, "Section: %s\n", pkg->section); if (pkg->priority) fprintf(fout, "Installed-Size: %s\n", pkg->installed_size); if (pkg->maintainer) fprintf(fout, "Maintainer: %s\n", pkg->maintainer); if (pkg->source) fprintf(fout, "Source: %s\n", pkg->source); if (pkg->version) fprintf(fout, "Version: %s\n", pkg->version); if (pkg->pre_depends) fprintf(fout, "Pre-Depends: %s\n", pkg->pre_depends); if (pkg->depends) fprintf(fout, "Depends: %s\n", pkg->depends); if (pkg->replaces) fprintf(fout, "Replaces: %s\n", pkg->replaces); if (pkg->recommends) fprintf(fout, "Recommends: %s\n", pkg->recommends); if (pkg->suggests) fprintf(fout, "Suggests: %s\n", pkg->suggests); if (pkg->provides) fprintf(fout, "Provides: %s\n", pkg->provides); if (pkg->conflicts) fprintf(fout, "Conflicts: %s\n", pkg->conflicts); if (pkg->conffiles) fprintf(fout, "Conffiles:\n %s\n", pkg->conffiles); if (pkg->description) fprintf(fout, "Description: %s\n%s", pkg->description, pkg->long_description); #ifdef SUPPORTL10N if (pkg->localized_descriptions) { struct language_description_t *ld; ld = pkg->localized_descriptions; while (ld) { if (ld->language && ld->description && ld->long_description) { fprintf(fout, "description-%s: %s\n%s", ld->language, ld->description, ld->long_description); } ld = ld->next; } } #endif if (pkg->installer_menu_item) fprintf(fout, "Installer-Menu-Item: %i\n", pkg->installer_menu_item); fputc('\n', fout); } fclose(fin); fclose(fout); r = rename(STATUSFILE, STATUSFILE ".bak"); if (r == 0) r = rename(STATUSFILE ".new", STATUSFILE); return 0; }