#include #include #include #include #include #include "dpkg.h" #include "memory.h" // enlarge this if britney has issues parsing packages // (e.g. very slow installability checks) #define SIZEOFHASHMAP 16 /* #define DIAGNOSE 1 */ #define insert_packagenamelist(x,y) insert_l_packagenamelist(x,y,__LINE__) static void free_dependency(dependency *dep); static void free_collected_package(dpkg_collected_package *pkg); static collpackagelist **get_matching_low(collpackagelist **addto, dpkg_packages *pkgs, dependency *dep, int line); static collpackagelist *get_matching(dpkg_packages *pkgs, deplist *depopts, int line); static deplist *read_deplist(char **buf, char sep, char end); static dependency *read_dependency(char **buf, char *end); static void add_virtualpackage(virtualpkgtbl *vpkgs, char *package, char *version, dpkg_collected_package *cpkg); static void remove_virtualpackage(virtualpkgtbl *vpkgs, char *pkgname, dpkg_collected_package *cpkg); static char *read_packagename(char **buf, char *end); static char *read_until_char(char **buf, char *end); static int checkinstallable(dpkg_packages *pkgs, collpackagelist *instoneof); // implemented in dpkg-lib.c int cmpversions(char *left, int op, char *right); #define block_malloc(s) block_malloc2(s, __LINE__) static int dependency_counts[] = { 1, 1, 0, 0 }; #define SMB_SIZE (1<<22) struct stringmemblock { struct stringmemblock *next; size_t last; char mem[SMB_SIZE]; }; static struct stringmemblock *stringmemory = NULL; static int stringmemorycount = 0; static const unsigned long stringmemblocksizekib = (unsigned long) sizeof(struct stringmemblock) / 1024; static char *my_strdup(char *foo) { struct stringmemblock *which; size_t len; if (!foo) return NULL; len = strlen(foo) + 1; if (len > SMB_SIZE) return strdup(foo); for (which = stringmemory; which; which = which->next) { if (SMB_SIZE - which->last > len + 1) { break; } } if (!which) { which = malloc(sizeof(struct stringmemblock)); if (!which) return NULL; MDEBUG1_ONLY(fprintf(stderr, "ALLOC: string memblock %d (%lu KiB, %lu KiB total)\n", stringmemorycount, stringmemblocksizekib, (stringmemorycount+1) * stringmemblocksizekib)); memset(which->mem, 0, SMB_SIZE); which->last = 0; which->next = stringmemory; stringmemory = which; stringmemorycount++; } strcpy(&which->mem[which->last], foo); foo = &which->mem[which->last]; which->last += len; return foo; } /* DIE **/ static void die(char *orig_msg) { char *msg = my_strdup(orig_msg); if (*msg && msg[strlen(msg)-1] == ':') { msg[strlen(msg)-1] = '\0'; perror(msg); } else { printf("%s\n", msg); } abort(); } /************************************************************************* * Basic Package Operations */ static dpkg_collected_package *new_collected_package(dpkg_package *pkg) { dpkg_collected_package *result; result = block_malloc(sizeof(dpkg_collected_package)); if (result == NULL) die("new_collected_package alloc:"); result->pkg = pkg; result->installed = 0; result->conflicted = 0; result->installable = UNKNOWN; result->mayaffect = NULL; return result; } static void free_collected_package(dpkg_collected_package *cpkg) { if (cpkg == NULL) return; cpkg->pkg = NULL; free_packagenamelist(cpkg->mayaffect); cpkg->mayaffect = NULL; block_free(cpkg, sizeof(dpkg_collected_package)); } LIST_IMPL(deplist, dependency*, free_dependency, block_malloc, block_free); LIST_IMPL(deplistlist, deplist*, free_deplist, block_malloc, block_free); LIST_IMPLX(packagenamelist, char*, KEEP(char*)); LIST_IMPL(ownedpackagenamelist, char*, KEEP(char*), block_malloc, block_free); /* ownedpackagenamelist stores the packagename in the string store */ static int packagecmp(dpkg_package *l, dpkg_package *r) { if (l->priority < r->priority) return -1; if (l->priority > r->priority) return +1; return strcmp(l->package, r->package); } /* container for existing pkgs */ LIST_IMPL(packagelist, dpkg_package *, KEEP(dpkg_package *), block_malloc, block_free); LIST_IMPLX(collpackagelist, dpkg_collected_package *, KEEP(dpkg_collected_package *)) #define insert_collpackagelist(x,y) insert_l_collpackagelist(x,y,__LINE__) /************************************************************************* * Operations on distributions (collections of packages) */ dpkg_packages *new_packages(char *arch) { dpkg_packages *result; result = block_malloc(sizeof(dpkg_packages)); if (result == NULL) die("new_packages alloc:"); result->arch = my_strdup(arch); result->packages = new_packagetbl(); result->virtualpkgs = new_virtualpkgtbl(); return result; } void add_package(dpkg_packages *pkgs, dpkg_package *pkg) { ownedpackagenamelist *v; dpkg_collected_package *cpkg; if (lookup_packagetbl(pkgs->packages, pkg->package) != NULL) return; cpkg = new_collected_package(pkg); add_packagetbl(pkgs->packages, cpkg->pkg->package, cpkg); add_virtualpackage(pkgs->virtualpkgs, cpkg->pkg->package, cpkg->pkg->version, cpkg); for (v = cpkg->pkg->provides; v != NULL; v = v->next) { add_virtualpackage(pkgs->virtualpkgs, v->value, NULL, cpkg); } } void remove_package(dpkg_packages *pkgs, dpkg_collected_package *cpkg) { ownedpackagenamelist *v; packagenamelist *aff; dpkg_collected_package *p; for (aff = cpkg->mayaffect; aff != NULL; aff = aff->next) { p = lookup_packagetbl(pkgs->packages, aff->value); if (p == NULL) continue; p->installable = UNKNOWN; } p = remove_packagetbl(pkgs->packages, cpkg->pkg->package); if (p != cpkg) return; remove_virtualpackage(pkgs->virtualpkgs, cpkg->pkg->package, cpkg); for (v = cpkg->pkg->provides; v != NULL; v = v->next) { remove_virtualpackage(pkgs->virtualpkgs, v->value, cpkg); } free_collected_package(cpkg); } void free_packages(dpkg_packages *pkgs) { if (pkgs == NULL) return; /* block_free(pkgs->arch); */ free_packagetbl(pkgs->packages); free_virtualpkgtbl(pkgs->virtualpkgs); block_free(pkgs, sizeof(dpkg_packages)); } HASH_IMPL(packagetbl, char *, dpkg_collected_package *, SIZEOFHASHMAP, strhash, strcmp, KEEP(char*),free_collected_package); HASH_IMPL(virtualpkgtbl, char *, virtualpkg *, SIZEOFHASHMAP, strhash, strcmp, KEEP(char*), free_virtualpkg); /* dpkg_provision refers to memory allocated elsewhere */ LIST_IMPL(virtualpkg, dpkg_provision, KEEP(dpkg_provision), block_malloc, block_free); static void remove_virtualpackage(virtualpkgtbl *vpkgs, char *pkgname, dpkg_collected_package *cpkg) { virtualpkg *list; virtualpkg **where; list = lookup_virtualpkgtbl(vpkgs, pkgname); assert(list != NULL); where = &list; while((*where)->value.pkg != cpkg) { where = &(*where)->next; assert(*where != NULL); } delete_virtualpkg(where); if (list == NULL) { remove_virtualpkgtbl(vpkgs, pkgname); } else { replace_virtualpkgtbl(vpkgs, pkgname, list); } } static void add_virtualpackage(virtualpkgtbl *vpkgs, char *package, char *version, dpkg_collected_package *cpkg) { dpkg_provision value; virtualpkg *list, **addto; int shouldreplace; value.pkg = cpkg; value.version = version; list = lookup_virtualpkgtbl(vpkgs, package); shouldreplace = (list != NULL); addto = &list; while (*addto != NULL && packagecmp(cpkg->pkg, (*addto)->value.pkg->pkg) >= 0) { addto = &(*addto)->next; } insert_virtualpkg(addto, value); if (shouldreplace) { replace_virtualpkgtbl(vpkgs, package, list); /* old list is included in new list, so we don't need to free */ } else { add_virtualpkgtbl(vpkgs, package, list); } } /************************************************************************* * Parsing Helper Functions */ ownedpackagenamelist *read_packagenames(char *buf) { ownedpackagenamelist *result = NULL; ownedpackagenamelist **addto = &result; DEBUG_ONLY( char *strend = buf + strlen(buf); ) char *sub; while ((sub = my_strdup(read_packagename(&buf, ",")))) { insert_ownedpackagenamelist(addto, sub); addto = &(*addto)->next; while(isspace(*buf)) buf++; if (*buf == ',') { buf++; continue; } if (*buf == '\0') { break; } die("read_packagenames no/bad seperator"); } DEBUG_ONLY( assert(buf <= strend); ) return result; } static char *read_until_char(char **buf, char *end) { static char *result = NULL; char *start; DEBUG_ONLY( char *strend = *buf + strlen(*buf); ) int n; while(isspace(**buf)) (*buf)++; start = *buf; while (**buf && !isspace(**buf) && strchr(end, **buf) == NULL) { (*buf)++; } n = *buf - start; if (n == 0) return NULL; result = realloc(result, n + 1); if (result == NULL) die("read_until_char alloc:"); strncpy(result, start, n); result[n] = '\0'; while(isspace(**buf)) (*buf)++; DEBUG_ONLY( assert(*buf <= strend); ) return result; } static char *read_packagename(char **buf, char *end) { return read_until_char(buf, end); } deplist *read_dep_and(char *buf) { return read_deplist(&buf, ',', '\0'); } static deplist *read_deplist(char **buf, char sep, char end) { deplist *result = NULL; deplist **addto = &result; char separs[3] = { sep, end, '\0' }; DEBUG_ONLY( char *strend = *buf + strlen(*buf); ) dependency *sub; while ((sub = read_dependency(buf, separs))) { insert_deplist(addto, sub); addto = &(*addto)->next; while(isspace(**buf)) (*buf)++; if (**buf == sep) { (*buf)++; continue; } if (**buf == '\0' || **buf == end) { break; } die("read_deplist no/bad seperator"); } DEBUG_ONLY( assert(*buf <= strend); ) return result; } deplistlist *read_dep_andor(char *buf) { deplistlist *result = NULL; deplistlist **addto = &result; deplist *sub; DEBUG_ONLY( char *strend = buf + strlen(buf); ) while ((sub = read_deplist(&buf, '|', ','))) { insert_deplistlist(addto, sub); addto = &(*addto)->next; if (*buf == ',') buf++; } DEBUG_ONLY( assert(buf <= strend); ) return result; } static dependency *read_dependency(char **buf, char *end) { dependency *dep; char *name; char newend[10]; DEBUG_ONLY( char *strend = *buf + strlen(*buf); ) assert(strlen(end) <= 8); newend[0] = '('; strcpy(newend + 1, end); name = my_strdup(read_until_char(buf, newend)); if (name == NULL) return NULL; dep = block_malloc(sizeof(dependency)); if (dep == NULL) die("read_dependency alloc 1:"); dep->package = name; while(isspace(**buf)) (*buf)++; if (**buf != '(') { dep->op = dr_NOOP; dep->version = NULL; } else { (*buf)++; while(isspace(**buf)) (*buf)++; /* << , <= , = , >= , >> */ if (**buf == '<') { (*buf)++; if (**buf == '<') { dep->op = dr_LT; (*buf)++; } else if (**buf == '=') { dep->op = dr_LTEQ; (*buf)++; } else { /* The forms `<' and `>' were used to mean earlier/later or * equal, rather than strictly earlier/later, so they should * not appear in new packages (though `dpkg' still supports * them). */ dep->op = dr_LTEQ; } } else if (**buf == '>') { (*buf)++; if (**buf == '>') { dep->op = dr_GT; (*buf)++; } else if (**buf == '=') { dep->op = dr_GTEQ; (*buf)++; } else { dep->op = dr_GTEQ; } } else if (**buf == '=') { dep->op = dr_EQ; (*buf)++; if (**buf == '>') { dep->op = dr_GTEQ; (*buf)++; } else if (**buf == '<') { dep->op = dr_LTEQ; (*buf)++; } } else { /* treat it as an implicit = :( */ dep->op = dr_EQ; /* would prefer to: die("read_dependency unknown version op"); */ } while (isspace(**buf)) (*buf)++; newend[0] = ')'; dep->version = my_strdup(read_until_char(buf, newend)); while (isspace(**buf)) (*buf)++; if (dep->version == NULL) die("read_dependency: no version"); if (**buf != ')') die("read_dependency: unterminated version"); (*buf)++; } DEBUG_ONLY( assert(*buf <= strend); ) return dep; } static void free_dependency(dependency *dep) { if (dep == NULL) return; /* block_free(dep->package); */ /* if (dep->version) block_free(dep->version); */ block_free(dep, sizeof(dependency)); } /************************************************************************* * Installability Checking */ static collpackagelist **get_matching_low(collpackagelist **addto, dpkg_packages *pkgs, dependency *dep, int line) { virtualpkg *vpkg; for (vpkg = lookup_virtualpkgtbl(pkgs->virtualpkgs, dep->package); vpkg != NULL; vpkg = vpkg->next) { int add; add = 0; if (dep->op == dr_NOOP) { add = 1; } else if (vpkg->value.version != NULL) { if (cmpversions(vpkg->value.version, dep->op, dep->version)) { add = 1; } } if (add) { insert_l_collpackagelist(addto, vpkg->value.pkg, line); addto = &(*addto)->next; } } return addto; } static collpackagelist *get_matching(dpkg_packages *pkgs, deplist *depopts, int line) { collpackagelist *list = NULL; collpackagelist **addto = &list; for(; depopts != NULL; depopts = depopts->next) { addto = get_matching_low(addto, pkgs, depopts->value, line); } return list; } typedef struct instonelist instonelist; struct instonelist { collpackagelist *curX; collpackagelist *instoneX; int expandedX; struct instonelist *nextX, *prevX, *cutoffX; }; #define I1CUR(i1) ((i1)->curX) #define I1INSTONE(i1) ((i1)->instoneX) #define I1CUTOFF(i1) ((i1)->cutoffX) #define I1NEXT(i1) ((i1)->nextX) /* can be modified ! */ #define I1PREV(i1) ((i1)->prevX) #define I1EXPANDED(i1) ((i1)->expandedX) static instonelist *insert_instonelist(instonelist *where, collpackagelist *instone); static void trim_instonelist_after(instonelist *first); static void free_instonelist(instonelist *l); static instonelist *insert_instonelist(instonelist *old, collpackagelist *instone) { instonelist *n = block_malloc(sizeof(instonelist)); if (n == NULL) die("insert_instonelist alloc:"); n->curX = NULL; n->instoneX = instone; n->cutoffX = NULL; n->nextX = (old ? old->nextX : NULL); n->prevX = old; n->expandedX = 0; if (old) old->nextX = n; if (n->nextX) n->nextX->prevX = n; return n; } static void trim_instonelist_after(instonelist *first) { if (!first->nextX) return; first->nextX->prevX = NULL; free_instonelist(first->nextX); first->nextX = NULL; } static void free_instonelist(instonelist *l) { instonelist *p, *k; if (!l) return; for (p = l; p->nextX; p = p->nextX); do { k = p; p = k->prevX; free_collpackagelist(k->instoneX); block_free(k, sizeof(instonelist)); } while (k != l); } static int caninstall(dpkg_packages *pkgs, dpkg_collected_package *cpkg) { collpackagelist *conflicts; collpackagelist *conf; int okay; if (cpkg->installed > 0) return 1; if (cpkg->conflicted > 0) return 0; conflicts = get_matching(pkgs, cpkg->pkg->conflicts, __LINE__); okay = 1; for (conf = conflicts; conf != NULL; conf = conf->next) { if (conf->value->installed > 0) { okay = 0; break; } } free_collpackagelist(conflicts); return okay; } static void install(dpkg_packages *pkgs, dpkg_collected_package *cpkg) { if (cpkg->installed == 0) { collpackagelist *conflicts = get_matching(pkgs, cpkg->pkg->conflicts, __LINE__); collpackagelist *conf; for (conf = conflicts; conf != NULL; conf = conf->next) { if (conf->value == cpkg) continue; assert(conf->value->installed == 0); conf->value->conflicted++; } free_collpackagelist(conflicts); } assert(cpkg->conflicted == 0); cpkg->installed++; } static void uninstall(dpkg_packages *pkgs, dpkg_collected_package *cpkg) { assert(cpkg->installed > 0); assert(cpkg->conflicted == 0); cpkg->installed--; if (cpkg->installed == 0) { collpackagelist *conflicts = get_matching(pkgs, cpkg->pkg->conflicts, __LINE__); collpackagelist *conf; for (conf = conflicts; conf != NULL; conf = conf->next) { if (conf->value == cpkg) continue; assert(conf->value->installed == 0); assert(conf->value->conflicted > 0); conf->value->conflicted--; } free_collpackagelist(conflicts); } } int checkinstallable2(dpkg_packages *pkgs, char *pkgname) { dpkg_collected_package *cpkg = lookup_packagetbl(pkgs->packages, pkgname); collpackagelist *cpl = NULL; if (cpkg == NULL) return 0; insert_collpackagelist(&cpl, cpkg); /* cpl gets freed in checkinstallable :-/ */ return checkinstallable(pkgs, cpl); } static void debug_checkinstallable(FILE *out, instonelist *list, instonelist *last, instonelist *pointer) { instonelist *l; fprintf(out, "Status:"); /* codes: | = multiple options here * @ = no options can satisfy this dep * + = dependencies that can be expanded have been * * = nothing selected yet * > = where pointer points * ^ = the cut point for where we are */ for (l = list; ; l = I1NEXT(l)) { fprintf(out, " "); if (l == pointer) fprintf(out, ">"); if (l == I1CUTOFF(pointer)) fprintf(out, "^"); if (I1INSTONE(l) == NULL) { fprintf(out, "@"); } else { if (I1INSTONE(l)->next != NULL) { fprintf(out, "|"); } if (I1EXPANDED(l)) { fprintf(out, "+"); } if (I1CUR(l) == NULL) { fprintf(out, "*%s", I1INSTONE(l)->value->pkg->package); } else { fprintf(out, "%s", I1CUR(l)->value->pkg->package); } } if (l == last) break; } fprintf(out, " ###\n"); fflush(out); } static int checkinstallable(dpkg_packages *pkgs, collpackagelist *instoneof) { /* We use pkg->installed, pkg->conflicted to note how many * times we've used this pkg to satisfy a dependency or installed * a package that conflicts with it. * Thus: pkg->installed == 0, or pkg->conflicted == 0 * * We assume these are okay initially, aren't being played with * concurrently elsewhere, and make sure they're still okay when * we return. */ instonelist *list; instonelist *last; instonelist *pointer; unsigned long counter = 10000000; { collpackagelist *cpkg; for (cpkg = instoneof; cpkg; cpkg = cpkg->next) { if (cpkg->value->installable == YES) { free_collpackagelist(instoneof); return 1; } } } list = insert_instonelist(NULL, instoneof); last = list; pointer = list; while(--counter > 0 && pointer) { deplistlist *dep; dpkg_collected_package *instpkg; /* convenient alias */ int i; #ifndef NDEBUG { instonelist *p; for (p = list; p != pointer; p = I1NEXT(p)) { assert(p != NULL); assert(I1CUR(p) != NULL); assert(I1CUR(p)->value != NULL); assert(I1CUR(p)->value->installed > 0); assert(I1CUR(p)->value->conflicted == 0); } if (I1NEXT(pointer) == NULL) { assert(pointer == last); } else { for (p = I1NEXT(pointer); p; p = I1NEXT(p)) { if (I1NEXT(p) == NULL) { assert(p == last); } assert(I1CUR(p) == NULL); } } } #endif #ifdef DIAGNOSE debug_checkinstallable(stdout, list, last, pointer); #endif if (I1CUR(pointer) == NULL) { I1CUR(pointer) = I1INSTONE(pointer); /* try to choose an already installed package if there is one */ while (I1CUR(pointer) != NULL) { if (I1CUR(pointer)->value->installed != 0) { break; } I1CUR(pointer) = I1CUR(pointer)->next; } if (I1CUR(pointer) == NULL) { I1CUR(pointer) = I1INSTONE(pointer); } assert(I1CUR(pointer) || !I1INSTONE(pointer)); I1CUTOFF(pointer) = last; } else { uninstall(pkgs, I1CUR(pointer)->value); trim_instonelist_after(I1CUTOFF(pointer)); last = I1CUTOFF(pointer); if (I1CUR(pointer)->value->installed > 0) { /* this dependency isn't the issue -- even doing * nothing to satisfy it (ie, using an already * installed package) doesn't do any good. So give up. */ I1CUR(pointer) = NULL; } else { I1CUR(pointer) = I1CUR(pointer)->next; } } while(I1CUR(pointer) && !caninstall(pkgs, I1CUR(pointer)->value)) { I1CUR(pointer) = I1CUR(pointer)->next; } if (I1CUR(pointer) == NULL) { if (I1PREV(pointer) == NULL) break; pointer = I1PREV(pointer); continue; } instpkg = I1CUR(pointer)->value; install(pkgs, instpkg); assert(instpkg->installed > 0); if (instpkg->installed == 1) { /* if it's been installed exactly once, then this must've been * the first time it was touched, so we need to look at the * dependencies. If it's the second or later, then we don't care * about them. */ /* if any of the deps can't be satisfied, don't move on */ int bother = 1; int expanded = I1EXPANDED(pointer); for (i = 0; i < 4; i++) { if (!dependency_counts[i]) continue; for (dep = instpkg->pkg->depends[i]; dep != NULL; dep = dep->next) { collpackagelist *thisdep = get_matching(pkgs, dep->value, __LINE__); if (thisdep == NULL) { bother = 0; } else if (thisdep != NULL && thisdep->next == NULL) { collpackagelist *x; /* if there's only one way of fulfilling this dep, * do it "ASAP" */ /* optimisation: if thisdep == foo, but the parent * was foo|bar, then we already know "foo" is not going * to work in this combination, and we can skip it. * * This deals with cases like X deps: Y|bar, bar deps: Y * where bar is a virtual package; cf xlibs */ for (x = I1INSTONE(pointer); x != I1CUR(pointer); x = x->next) { if (x->value == thisdep->value) { bother = 0; break; } } if (I1INSTONE(pointer)->next == NULL) { /* the parent of this entry essentially depends * on this too, so we'll get it out of the way * ASAP, to reduce the degree of exponentiation * in bad cases. * * _However_ we only want to do this _once_ for * any particular node. */ if (expanded) { /* thisdep isn't used! */ free_collpackagelist(thisdep); } else { insert_instonelist(pointer, thisdep); I1EXPANDED(pointer) = 1; } } else { insert_instonelist(I1CUTOFF(pointer), thisdep); } if (I1NEXT(last)) last = I1NEXT(last); assert(!I1NEXT(last)); } else { /* otherwise it's a multi possibility dep, so do it * at the end */ last = insert_instonelist(last, thisdep); } } } if (!bother) { /* stay where we are, and try the next possibility */ continue; } } pointer = I1NEXT(pointer); } if (counter == 0) { unsigned int package_count = 0; fprintf(stderr, "AIEEE: counter overflow:"); assert(pointer != NULL); if (I1CUR(pointer) == NULL || I1CUR(pointer)->value == NULL) { /* we're not guaranteed that pointer will make sense here */ pointer = I1PREV(pointer); } for (; pointer != NULL; pointer = I1PREV(pointer)) { if (I1CUR(pointer) == NULL) { /* should only happen at pointer, so not here */ fprintf(stderr, " >> eep, no packages at pointer <<"); continue; } if (I1CUR(pointer)->value == NULL) { /* should never happen */ fprintf(stderr, " >> eep, no package selected <<"); continue; } /* the full list is no as interesting as the "guilty" package, * display the number of involved packages instead */ #if 0 fprintf(stderr, " %s%s", (I1INSTONE(pointer)->next == NULL ? "" : "|"), I1CUR(pointer)->value->pkg->package); #endif package_count++; uninstall(pkgs, I1CUR(pointer)->value); } fprintf(stderr, " %u involved packages.\n", package_count); free_instonelist(list); /* let the caller know we hit a bad failure */ return -1; } if (pointer == NULL) { dpkg_collected_package *cpkg = I1CUR(list)->value; assert(cpkg->installable != YES); cpkg->installable = YES; for (pointer = last; pointer != NULL; pointer = I1PREV(pointer)) { if (I1CUR(pointer)->value->installed == 1) { packagenamelist **p = &I1CUR(pointer)->value->mayaffect; #if 0 while ( *p && (*p)->value < cpkg->pkg->package ) { p = &(*p)->next; } if (*p == NULL || (*p)->value > cpkg->pkg->package) #endif { insert_packagenamelist(p, cpkg->pkg->package); } } uninstall(pkgs, I1CUR(pointer)->value); } free_instonelist(list); return 1; } else { assert(I1CUR(list) == NULL); free_instonelist(list); return 0; } }