/* * deps.c * * Copyright (c) 2002-2005 by Judd Vinet <jvinet@zeroflux.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #include <stdlib.h> #include <stdio.h> #include <string.h> /* pacman */ #include "util.h" #include "log.h" #include "error.h" #include "list.h" #include "package.h" #include "db.h" #include "cache.h" #include "provide.h" #include "deps.h" #include "rpmvercmp.h" /* Re-order a list of target packages with respect to their dependencies. * * Example (PM_TRANS_TYPE_ADD): * A depends on C * B depends on A * Target order is A,B,C,D * * Should be re-ordered to C,A,B,D * * mode should be either PM_TRANS_TYPE_ADD or PM_TRANS_TYPE_REMOVE. This * affects the dependency order sortbydeps() will use. * * This function returns the new PMList* target list. * */ PMList *sortbydeps(PMList *targets, int mode) { PMList *newtargs = NULL; PMList *i, *j, *k; int change = 1; int numscans = 0; int numtargs = 0; int clean = 0; if(targets == NULL) { return(NULL); } /* count the number of targets */ numtargs = pm_list_count(targets); while(change) { change = 0; if(numscans > numtargs) { _alpm_log(PM_LOG_WARNING, "possible dependency cycle detected"); change = 0; continue; } newtargs = NULL; numscans++; /* run thru targets, moving up packages as necessary */ for(i = targets; i; i = i->next) { pmpkg_t *p = (pmpkg_t*)i->data; for(j = p->depends; j; j = j->next) { pmdepend_t dep; int found = 0; pmpkg_t *q = NULL; splitdep(j->data, &dep); /* look for dep.name -- if it's farther down in the list, then * move it up above p */ for(k = i->next; k && !found; k = k->next) { q = (pmpkg_t*)k->data; if(!strcmp(dep.name, q->name)) { found = 1; } } if(found) { if(!pkg_isin(q, newtargs)) { change = 1; newtargs = pm_list_add(newtargs, q); } } } if(!pkg_isin(p, newtargs)) { newtargs = pm_list_add(newtargs, p); } } if(clean && change) { /* free up targets -- it's local now */ FREELISTPTR(targets); } targets = newtargs; clean = 1; } if(mode == PM_TRANS_TYPE_REMOVE) { /* we're removing packages, so reverse the order */ newtargs = _alpm_list_reverse(targets); /* free the old one */ FREELISTPTR(targets); targets = newtargs; } return(targets); } /* Returns a PMList* of missing_t pointers. * * conflicts are always name only, but dependencies can include versions * with depmod operators. * */ PMList *checkdeps(pmdb_t *db, unsigned char op, PMList *packages) { pmpkg_t *info = NULL; pmdepend_t depend; PMList *i, *j, *k; int cmp; int found = 0; PMList *baddeps = NULL; pmdepmissing_t *miss = NULL; if(db == NULL) { return(NULL); } if(op == PM_TRANS_TYPE_UPGRADE) { /* PM_TRANS_TYPE_UPGRADE handles the backwards dependencies, ie, the packages * listed in the requiredby field. */ for(i = packages; i; i = i->next) { pmpkg_t *tp, *oldpkg; if(i->data == NULL) { continue; } tp = (pmpkg_t *)i->data; if((oldpkg = db_scan(db, tp->name, INFRQ_DESC | INFRQ_DEPENDS)) == NULL) { continue; } for(j = oldpkg->requiredby; j; j = j->next) { char *ver; pmpkg_t *p; found = 0; if((p = db_scan(db, j->data, INFRQ_DESC | INFRQ_DEPENDS)) == NULL) { /* hmmm... package isn't installed.. */ continue; } if(pkg_isin(p, packages)) { /* this package is also in the upgrade list, so don't worry about it */ FREEPKG(p); continue; } for(k = p->depends; k && !found; k = k->next) { /* find the dependency info in p->depends */ splitdep(k->data, &depend); if(!strcmp(depend.name, oldpkg->name)) { found = 1; } } if(found == 0) { /* look for packages that list depend.name as a "provide" */ PMList *provides = _alpm_db_whatprovides(db, depend.name); if(provides == NULL) { /* not found */ FREEPKG(p); continue; } /* we found an installed package that provides depend.name */ FREELISTPTR(provides); } found = 0; if(depend.mod == PM_DEP_MOD_ANY) { found = 1; } else { /* note that we use the version from the NEW package in the check */ ver = strdup(tp->version); if(!index(depend.version,'-')) { char *ptr; for(ptr = ver; *ptr != '-'; ptr++); *ptr = '\0'; } cmp = rpmvercmp(ver, depend.version); switch(depend.mod) { case PM_DEP_MOD_EQ: found = (cmp == 0); break; case PM_DEP_MOD_GE: found = (cmp >= 0); break; case PM_DEP_MOD_LE: found = (cmp <= 0); break; } FREE(ver); } if(!found) { MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_REQUIRED; miss->depend.mod = depend.mod; STRNCPY(miss->target, p->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, depend.name, PKG_NAME_LEN); STRNCPY(miss->depend.version, depend.version, PKG_VERSION_LEN); if(!pm_list_is_ptrin(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } } FREEPKG(p); } FREEPKG(oldpkg); } } if(op == PM_TRANS_TYPE_ADD || op == PM_TRANS_TYPE_UPGRADE) { for(i = packages; i; i = i->next) { pmpkg_t *tp = i->data; if(tp == NULL) { continue; } /* CONFLICTS */ for(j = tp->conflicts; j; j = j->next) { /* check targets against database */ for(k = db_get_pkgcache(db); k; k = k->next) { pmpkg_t *dp = (pmpkg_t *)k->data; if(!strcmp(j->data, dp->name)) { MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_CONFLICT; miss->depend.mod = PM_DEP_MOD_ANY; miss->depend.version[0] = '\0'; STRNCPY(miss->target, tp->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, dp->name, PKG_NAME_LEN); if(!pm_list_is_ptrin(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } } } /* check targets against targets */ for(k = packages; k; k = k->next) { pmpkg_t *a = (pmpkg_t *)k->data; if(!strcmp(a->name, (char *)j->data)) { MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_CONFLICT; miss->depend.mod = PM_DEP_MOD_ANY; miss->depend.version[0] = '\0'; STRNCPY(miss->target, tp->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, a->name, PKG_NAME_LEN); if(!pm_list_is_ptrin(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } } } } /* check database against targets */ for(k = db_get_pkgcache(db); k; k = k->next) { info = k->data; for(j = info->conflicts; j; j = j->next) { if(!strcmp((char *)j->data, tp->name)) { MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_CONFLICT; miss->depend.mod = PM_DEP_MOD_ANY; miss->depend.version[0] = '\0'; STRNCPY(miss->target, tp->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, info->name, PKG_NAME_LEN); if(!pm_list_is_ptrin(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } } } } /* PROVIDES -- check to see if another package already provides what * we offer */ /* XXX: disabled -- we allow multiple packages to provide the same thing. * list packages in conflicts if they really do conflict. for(j = tp->provides; j; j = j->next) { PMList *provs = whatprovides(db, j->data); for(k = provs; k; k = k->next) { if(!strcmp(tp->name, k->data->name)) { // this is the same package -- skip it continue; } // we treat this just like a conflict MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_CONFLICT; miss->depend.mod = PM_DEP_MOD_ANY; miss->depend.version[0] = '\0'; STRNCPY(miss->target, tp->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, k->data, PKG_NAME_LEN); if(!pm_list_is_in(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } k->data = NULL; } FREELIST(provs); }*/ /* DEPENDENCIES -- look for unsatisfied dependencies */ for(j = tp->depends; j; j = j->next) { /* split into name/version pairs */ splitdep((char *)j->data, &depend); found = 0; /* check database for literal packages */ for(k = db_get_pkgcache(db); k && !found; k = k->next) { pmpkg_t *p = (pmpkg_t *)k->data; if(!strcmp(p->name, depend.name)) { if(depend.mod == PM_DEP_MOD_ANY) { /* accept any version */ found = 1; } else { char *ver = strdup(p->version); /* check for a release in depend.version. if it's * missing remove it from p->version as well. */ if(!index(depend.version,'-')) { char *ptr; for(ptr = ver; *ptr != '-'; ptr++); *ptr = '\0'; } cmp = rpmvercmp(ver, depend.version); switch(depend.mod) { case PM_DEP_MOD_EQ: found = (cmp == 0); break; case PM_DEP_MOD_GE: found = (cmp >= 0); break; case PM_DEP_MOD_LE: found = (cmp <= 0); break; } FREE(ver); } } } /* check other targets */ for(k = packages; k && !found; k = k->next) { pmpkg_t *p = (pmpkg_t *)k->data; /* see if the package names match OR if p provides depend.name */ if(!strcmp(p->name, depend.name) || pm_list_is_strin(depend.name, p->provides)) { if(depend.mod == PM_DEP_MOD_ANY) { /* accept any version */ found = 1; } else { char *ver = strdup(p->version); /* check for a release in depend.version. if it's * missing remove it from p->version as well. */ if(!index(depend.version,'-')) { char *ptr; for(ptr = ver; *ptr != '-'; ptr++); *ptr = '\0'; } cmp = rpmvercmp(ver, depend.version); switch(depend.mod) { case PM_DEP_MOD_EQ: found = (cmp == 0); break; case PM_DEP_MOD_GE: found = (cmp >= 0); break; case PM_DEP_MOD_LE: found = (cmp <= 0); break; } FREE(ver); } } } /* check database for provides matches */ if(!found){ k = _alpm_db_whatprovides(db, depend.name); if(k) { /* grab the first one (there should only really be one, anyway) */ pmpkg_t *p = db_scan(db, ((pmpkg_t *)k->data)->name, INFRQ_DESC); if(p == NULL) { /* wtf */ _alpm_log(PM_LOG_ERROR, "%s supposedly provides %s, but it was not found in db", ((pmpkg_t *)k->data)->name, depend.name); FREELISTPTR(k); continue; } if(depend.mod == PM_DEP_MOD_ANY) { /* accept any version */ found = 1; } else { char *ver = strdup(p->version); /* check for a release in depend.version. if it's * missing remove it from p->version as well. */ if(!index(depend.version,'-')) { char *ptr; for(ptr = ver; *ptr != '-'; ptr++); *ptr = '\0'; } cmp = rpmvercmp(ver, depend.version); switch(depend.mod) { case PM_DEP_MOD_EQ: found = (cmp == 0); break; case PM_DEP_MOD_GE: found = (cmp >= 0); break; case PM_DEP_MOD_LE: found = (cmp <= 0); break; } FREE(ver); } } FREELISTPTR(k); } /* else if still not found... */ if(!found) { MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_DEPEND; miss->depend.mod = depend.mod; STRNCPY(miss->target, tp->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, depend.name, PKG_NAME_LEN); STRNCPY(miss->depend.version, depend.version, PKG_VERSION_LEN); if(!pm_list_is_ptrin(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } } } } } else if(op == PM_TRANS_TYPE_REMOVE) { /* check requiredby fields */ for(i = packages; i; i = i->next) { pmpkg_t *tp; if(i->data == NULL) { continue; } tp = (pmpkg_t*)i->data; for(j = tp->requiredby; j; j = j->next) { if(!pm_list_is_strin((char *)j->data, packages)) { MALLOC(miss, sizeof(pmdepmissing_t)); miss->type = PM_DEP_TYPE_REQUIRED; miss->depend.mod = PM_DEP_MOD_ANY; miss->depend.version[0] = '\0'; STRNCPY(miss->target, tp->name, PKG_NAME_LEN); STRNCPY(miss->depend.name, (char *)j->data, PKG_NAME_LEN); if(!pm_list_is_ptrin(baddeps, miss)) { baddeps = pm_list_add(baddeps, miss); } } } } } return(baddeps); } int splitdep(char *depstr, pmdepend_t *depend) { char *str = NULL; char *ptr = NULL; if(depstr == NULL || depend == NULL) { return(-1); } depend->mod = 0; depend->name[0] = 0; depend->version[0] = 0; str = strdup(depstr); if((ptr = strstr(str, ">="))) { depend->mod = PM_DEP_MOD_GE; } else if((ptr = strstr(str, "<="))) { depend->mod = PM_DEP_MOD_LE; } else if((ptr = strstr(str, "="))) { depend->mod = PM_DEP_MOD_EQ; } else { /* no version specified - accept any */ depend->mod = PM_DEP_MOD_ANY; STRNCPY(depend->name, str, PKG_NAME_LEN); } if(ptr == NULL) { FREE(str); return(0); } *ptr = '\0'; STRNCPY(depend->name, str, PKG_NAME_LEN); ptr++; if(depend->mod != PM_DEP_MOD_EQ) { ptr++; } STRNCPY(depend->version, ptr, PKG_VERSION_LEN); FREE(str); return(0); } /* return a new PMList target list containing all packages in the original * target list, as well as all their un-needed dependencies. By un-needed, * I mean dependencies that are *only* required for packages in the target * list, so they can be safely removed. This function is recursive. */ PMList* removedeps(pmdb_t *db, PMList *targs) { PMList *i, *j, *k; PMList *newtargs = targs; if(db == NULL) { return(newtargs); } for(i = targs; i; i = i->next) { pmpkg_t *pkg = (pmpkg_t*)i->data; for(j = pkg->depends; j; j = j->next) { pmdepend_t depend; pmpkg_t *dep; int needed = 0; splitdep(j->data, &depend); dep = db_scan(db, depend.name, INFRQ_DESC | INFRQ_DEPENDS); if(pkg_isin(dep, targs)) { continue; } /* see if it was explicitly installed */ if(dep->reason == PM_PKG_REASON_EXPLICIT) { _alpm_log(PM_LOG_FLOW2, "excluding %s -- explicitly installed", dep->name); needed = 1; } /* see if other packages need it */ for(k = dep->requiredby; k && !needed; k = k->next) { pmpkg_t *dummy = db_scan(db, k->data, INFRQ_DESC); if(!pkg_isin(dummy, targs)) { needed = 1; } } if(!needed) { /* add it to the target list */ pkg_free(dep); dep = db_scan(db, depend.name, INFRQ_ALL); newtargs = pm_list_add(newtargs, dep); newtargs = removedeps(db, newtargs); } } } return(newtargs); } /* populates *list with packages that need to be installed to satisfy all * dependencies (recursive) for syncpkg * * make sure *list and *trail are already initialized */ int resolvedeps(pmdb_t *local, PMList *dbs_sync, pmpkg_t *syncpkg, PMList *list, PMList *trail) { PMList *i, *j; PMList *targ; PMList *deps = NULL; if(local == NULL || dbs_sync == NULL || syncpkg == NULL) { return(-1); } targ = pm_list_add(NULL, syncpkg); deps = checkdeps(local, PM_TRANS_TYPE_ADD, targ); FREELISTPTR(targ); if(deps == NULL) { return(0); } for(i = deps; i; i = i->next) { int found = 0; pmdepmissing_t *miss = i->data; /* XXX: conflicts are now treated specially in the _add and _sync functions */ /*if(miss->type == PM_DEP_TYPE_CONFLICT) { _alpm_log(PM_LOG_ERROR, "cannot resolve dependencies for \"%s\" (it conflict with %s)", miss->target, miss->depend.name); RET_ERR(???, -1); } else*/ if(miss->type == PM_DEP_TYPE_DEPEND) { pmpkg_t *sync = NULL; /* find the package in one of the repositories */ /* check literals */ for(j = dbs_sync; !sync && j; j = j->next) { PMList *k; pmdb_t *dbs = j->data; for(k = db_get_pkgcache(dbs); !sync && k; k = k->next) { pmpkg_t *pkg = k->data; if(!strcmp(miss->depend.name, pkg->name)) { sync = pkg; } } } /* check provides */ for(j = dbs_sync; !sync && j; j = j->next) { PMList *provides; pmdb_t *dbs = j->data; provides = _alpm_db_whatprovides(dbs, miss->depend.name); if(provides) { sync = provides->data; } FREELISTPTR(provides); } if(sync == NULL) { _alpm_log(PM_LOG_ERROR, "cannot resolve dependencies for \"%s\" (\"%s\" is not in the package set", miss->target, miss->depend.name); pm_errno = PM_ERR_UNRESOLVABLE_DEPS; goto error; } found = 0; for(j = list; j && !found; j = j->next) { pmpkg_t *tmp = j->data; if(tmp && !strcmp(tmp->name, sync->name)) { found = 1; } } if(found) { /* this dep is already in the target list */ continue; } _alpm_log(PM_LOG_DEBUG, "resolving %s", sync->name); found = 0; for(j = trail; j; j = j->next) { pmpkg_t *tmp = j->data; if(tmp && !strcmp(tmp->name, sync->name)) { found = 1; } } if(!found) { /* check pmo_ignorepkg and pmo_s_ignore to make sure we haven't pulled in * something we're not supposed to. */ int usedep = 1; found = 0; /* ORE for(j = handle->ignorepkg; j && !found; j = j->next) { if(!strcmp(j->data, sync->name)) { found = 1; } } for(j = pmo_s_ignore; j && !found; j = j->next) { if(!strcmp(j->data, sync->pkg->name)) { found = 1; } }*/ if(found) { /* ORE usedep = yesno("%s requires %s, but it is in IgnorePkg. Install anyway? [Y/n] ", miss->target, sync->pkg->name);*/ } if(usedep) { trail = pm_list_add(trail, sync); if(resolvedeps(local, dbs_sync, sync, list, trail)) { goto error; } _alpm_log(PM_LOG_FLOW2, "adding dependency %s-%s", sync->name, sync->version); list = pm_list_add(list, sync); } else { _alpm_log(PM_LOG_ERROR, "cannot resolve dependencies for \"%s\"", miss->target); pm_errno = PM_ERR_UNRESOLVABLE_DEPS; goto error; } } else { /* cycle detected -- skip it */ _alpm_log(PM_LOG_DEBUG, "dependency cycle detected: %s", sync->name); } } } FREELIST(deps); return(0); error: FREELIST(deps); return(-1); } /* vim: set ts=2 sw=2 noet: */