diff options
Diffstat (limited to 'main/depcon.c')
-rw-r--r-- | main/depcon.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/main/depcon.c b/main/depcon.c new file mode 100644 index 000000000..7e90464a5 --- /dev/null +++ b/main/depcon.c @@ -0,0 +1,490 @@ +/* + * dpkg - main program for package management + * depcon.c - dependency and conflict checking + * + * Copyright (C) 1994,1995 Ian Jackson <iwj10@cus.cam.ac.uk> + * + * This 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, + * or (at your option) any later version. + * + * This 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 dpkg; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <assert.h> + +#include "config.h" +#include "dpkg.h" +#include "dpkg-db.h" + +#include "main.h" + +int versionsatisfied5(const char *itver, const char *itrev, + const char *refver, const char *refrev, + enum depverrel verrel) { + int r; + if (verrel == dvr_none) return 1; + r= versioncompare(itver,itrev,refver,refrev); + switch (verrel) { + case dvr_earlierequal: return r <= 0; + case dvr_laterequal: return r >= 0; + case dvr_earlierstrict: return r < 0; + case dvr_laterstrict: return r > 0; + case dvr_exact: return r == 0; + default: internerr("unknown verrel"); + } +} + +int versionsatisfied(struct pkginfoperfile *it, struct deppossi *against) { + return versionsatisfied5(it->version, it->revision, + against->version, against->revision, against->verrel); +} + +const char *versiondescribe(const char *ver, const char *rev) { + static char bufs[10][512]; + static int bufnum=0; + char *buf; + + buf= bufs[bufnum]; bufnum++; if (bufnum == 10) bufnum= 0; + + if (!ver || !*ver) { + strcpy(buf,"<unknown>"); + } else { + if (rev && *rev) { + sprintf(buf, "%.250s-%.250s", ver, rev); + } else { + sprintf(buf, "%.250s", ver); + } + } + return buf; +} + +struct cyclesofarlink { + struct cyclesofarlink *back; + struct pkginfo *pkg; + struct deppossi *possi; +}; + +static int foundcyclebroken(struct cyclesofarlink *thislink, + struct cyclesofarlink *sofar, + struct pkginfo *dependedon, + struct deppossi *possi) { + struct cyclesofarlink *sol; + const char *postinstfilename; + struct stat stab; + + /* We're investigating the dependency `possi' to see if it + * is part of a loop. To this end we look to see whether the + * depended-on package is already one of the packages whose + * dependencies we're searching. + */ + for (sol=sofar; sol && sol->pkg != dependedon; sol=sol->back); + + /* If not, we do a recursive search on it to see what we find. */ + if (!sol) return findbreakcycle(possi->ed,thislink); + + debug(dbg_depcon,"found cycle"); + /* Right, we now break one of the links. We prefer to break + * a dependency of a package without a postinst script, as + * this is a null operation. If this is not possible we break + * the other link in the recursive calling tree which mentions + * this package (this being the first package involved in the + * cycle). It doesn't particularly matter which we pick, but if + * we break the earliest dependency we came across we may be + * able to do something straight away when findbreakcycle returns. + */ + sofar= thislink; + for (sol= sofar; !(sol != sofar && sol->pkg == dependedon); sol=sol->back) { + postinstfilename= pkgadminfile(sol->pkg,POSTINSTFILE); + if (lstat(postinstfilename,&stab)) { + if (errno == ENOENT) break; + ohshite("unable to check for existence of `%.250s'",postinstfilename); + } + } + /* Now we have either a package with no postinst, or the other + * occurrence of the current package in the list. + */ + sol->possi->cyclebreak= 1; + debug(dbg_depcon,"cycle broken at %s -> %s\n", + sol->possi->up->up->name, sol->possi->ed->name); + return 1; +} + +int findbreakcycle(struct pkginfo *pkg, struct cyclesofarlink *sofar) { + /* Cycle breaking works recursively down the package dependency + * tree. `sofar' is the list of packages we've descended down + * already - if we encounter any of its packages again in a + * dependency we have found a cycle. + */ + struct cyclesofarlink thislink, *sol; + struct dependency *dep; + struct deppossi *possi, *providelink; + struct pkginfo *provider; + + if (f_debug & dbg_depcondetail) { + fprintf(stderr,"D0%05o: findbreakcycle %s ",dbg_depcondetail,pkg->name); + for (sol=sofar; sol; sol=sol->back) fprintf(stderr," <- %s",sol->pkg->name); + fprintf(stderr,"\n"); + } + thislink.pkg= pkg; + thislink.back= sofar; + thislink.possi= 0; + for (dep= pkg->installed.depends; dep; dep= dep->next) { + if (dep->type != dep_depends && dep->type != dep_predepends) continue; + for (possi= dep->list; possi; possi= possi->next) { + /* We can't have any cycles involving packages we're not trying + * to do anything with. + */ + if (possi->ed->clientdata->istobe == itb_normal) continue; + /* Don't find the same cycles again. */ + if (possi->cyclebreak) continue; + thislink.possi= possi; + if (foundcyclebroken(&thislink,sofar,possi->ed,possi)) return 1; + /* Right, now we try all the providers ... */ + for (providelink= possi->ed->installed.depended; + providelink; + providelink= providelink->nextrev) { + if (providelink->up->type != dep_provides) continue; + provider= providelink->up->up; + if (provider->clientdata->istobe == itb_normal) continue; + /* We don't break things at `provides' links, so `possi' is + * still the one we use. + */ + if (foundcyclebroken(&thislink,sofar,provider,possi)) return 1; + } + } + } + /* Nope, we didn't find a cycle to break. */ + return 0; +} + +void describedepcon(struct varbuf *addto, struct dependency *dep) { + varbufaddstr(addto,dep->up->name); + switch (dep->type) { + case dep_depends: varbufaddstr(addto, " depends on "); break; + case dep_predepends: varbufaddstr(addto, " pre-depends on "); break; + case dep_recommends: varbufaddstr(addto, " recommends "); break; + case dep_conflicts: varbufaddstr(addto, " conflicts with "); break; + default: internerr("unknown deptype"); + } + varbufdependency(addto, dep); +} + +int depisok(struct dependency *dep, struct varbuf *whynot, + struct pkginfo **canfixbyremove, int allowunconfigd) { + /* *whynot must already have been initialised; it need not be + * empty though - it will be reset before use. + * If depisok returns 0 for `not OK' it will contain a description, + * newline-terminated BUT NOT NULL-TERMINATED, of the reason. + * If depisok returns 1 it will contain garbage. + * allowunconfigd should be non-zero during the `Pre-Depends' checking + * before a package is unpacked, when it is sufficient for the package + * to be unpacked provided that both the unpacked and previously-configured + * versions are acceptable. + */ + struct deppossi *possi; + struct deppossi *provider; + int nconflicts; + + /* Use this buffer so that when internationalisation comes along we + * don't have to rewrite the code completely, only redo the sprintf strings + * (assuming we have the fancy argument-number-specifiers). + * Allow 250x3 for package names, versions, &c, + 250 for ourselves. + */ + char linebuf[1024]; + + assert(dep->type == dep_depends || dep->type == dep_predepends || + dep->type == dep_conflicts || dep->type == dep_recommends); + + /* The dependency is always OK if we're trying to remove the depend*ing* + * package. + */ + switch (dep->up->clientdata->istobe) { + case itb_remove: case itb_deconfigure: + return 1; + case itb_normal: + /* Only `installed' packages can be make dependency problems */ + switch (dep->up->status) { + case stat_installed: + break; + case stat_notinstalled: case stat_configfiles: case stat_halfinstalled: + case stat_halfconfigured: case stat_unpacked: + return 1; + default: + internerr("unknown status depending"); + } + break; + case itb_installnew: case itb_preinstall: + break; + default: + internerr("unknown istobe depending"); + } + + /* Describe the dependency, in case we have to moan about it. */ + varbufreset(whynot); + varbufaddc(whynot, ' '); + describedepcon(whynot, dep); + varbufaddc(whynot,'\n'); + + if (dep->type == dep_depends || dep->type == dep_predepends || + dep->type == dep_recommends) { + + /* Go through the alternatives. As soon as we find one that + * we like, we return `1' straight away. Otherwise, when we get to + * the end we'll have accumulated all the reasons in whynot and + * can return `0'. + */ + + for (possi= dep->list; possi; possi= possi->next) { + switch (possi->ed->clientdata->istobe) { + case itb_remove: + sprintf(linebuf, " %.250s is to be removed.\n", possi->ed->name); + break; + case itb_deconfigure: + sprintf(linebuf, " %.250s is to be deconfigured.\n", possi->ed->name); + break; + case itb_installnew: + if (versionsatisfied(&possi->ed->available, possi)) return 1; + sprintf(linebuf, " %.250s is to be installed, but is version %.250s.\n", + possi->ed->name, + versiondescribe(possi->ed->available.version, + possi->ed->available.revision)); + break; + case itb_normal: case itb_preinstall: + switch (possi->ed->status) { + case stat_installed: + if (versionsatisfied(&possi->ed->installed, possi)) return 1; + sprintf(linebuf, " %.250s is installed, but is version %.250s.\n", + possi->ed->name, + versiondescribe(possi->ed->available.version, + possi->ed->available.revision)); + break; + case stat_notinstalled: + /* Don't say anything about this yet - it might be a virtual package. + * Later on, if nothing has put anything in linebuf, we know that it + * isn't and issue a diagnostic then. + */ + *linebuf= 0; + break; + case stat_unpacked: + case stat_halfconfigured: + if (allowunconfigd) { + if (!possi->ed->configversion || !*possi->ed->configversion) { + sprintf(linebuf, " %.250s is unpacked, but has never been configured.\n", + possi->ed->name); + break; + } else if (!versionsatisfied(&possi->ed->installed, possi)) { + sprintf(linebuf, " %.250s is unpacked, but is version %.250s.\n", + possi->ed->name, + versiondescribe(possi->ed->available.version, + possi->ed->available.revision)); + break; + } else if (!versionsatisfied5(possi->ed->configversion, + possi->ed->configrevision, + possi->version, possi->revision, + possi->verrel)) { + sprintf(linebuf, " %.250s latest configured version is %.250s.\n", + possi->ed->name, + versiondescribe(possi->ed->configversion, + possi->ed->configrevision)); + break; + } else { + return 1; + } + } + default: + sprintf(linebuf, " %.250s is %s.\n", + possi->ed->name, statusstrings[possi->ed->status]); + break; + } + break; + default: + internerr("unknown istobe depended"); + } + varbufaddstr(whynot, linebuf); + + /* If there was no version specified we try looking for Providers. */ + if (possi->verrel == dvr_none) { + + /* See if the package we're about to install Provides it. */ + for (provider= possi->ed->available.depended; + provider; + provider= provider->nextrev) { + if (provider->up->type != dep_provides) continue; + if (provider->up->up->clientdata->istobe == itb_installnew) return 1; + } + + /* Now look at the packages already on the system. */ + for (provider= possi->ed->installed.depended; + provider; + provider= provider->nextrev) { + if (provider->up->type != dep_provides) continue; + + switch (provider->up->up->clientdata->istobe) { + case itb_installnew: + /* Don't pay any attention to the Provides field of the + * currently-installed version of the package we're trying + * to install. We dealt with that by using the available + * information above. + */ + continue; + case itb_remove: + sprintf(linebuf, " %.250s provides %.250s but is to be removed.\n", + provider->up->up->name, possi->ed->name); + break; + case itb_deconfigure: + sprintf(linebuf, " %.250s provides %.250s but is to be deconfigured.\n", + provider->up->up->name, possi->ed->name); + break; + case itb_normal: case itb_preinstall: + if (provider->up->up->status == stat_installed) return 1; + sprintf(linebuf, " %.250s provides %.250s but is %s.\n", + provider->up->up->name, possi->ed->name, + statusstrings[provider->up->up->status]); + break; + default: + internerr("unknown istobe provider"); + } + varbufaddstr(whynot, linebuf); + } + + if (!*linebuf) { + /* If the package wasn't installed at all, and we haven't said + * yet why this isn't satisfied, we should say so now. + */ + sprintf(linebuf, " %.250s is not installed.\n", possi->ed->name); + varbufaddstr(whynot, linebuf); + } + } + } + + if (canfixbyremove) *canfixbyremove= 0; + return 0; + + } else { + + /* It's a conflict. There's only one main alternative, + * but we also have to consider Providers. We return `0' as soon + * as we find something that matches the conflict, and only describe + * it then. If we get to the end without finding anything we return `1'. + */ + + possi= dep->list; + nconflicts= 0; + + if (possi->ed != possi->up->up) { + /* If the package conflicts with itself it must mean that it conflicts + * with other packages which provide the same virtual name. We therefore + * don't look at the real package and go on to the virtual ones. + */ + + switch (possi->ed->clientdata->istobe) { + case itb_remove: + break; + case itb_installnew: + if (!versionsatisfied(&possi->ed->available, possi)) break; + sprintf(linebuf, " %.250s (version %.250s) is to be installed.\n", + possi->ed->name, + versiondescribe(possi->ed->available.version, + possi->ed->available.revision)); + varbufaddstr(whynot, linebuf); + if (!canfixbyremove) return 0; + nconflicts++; + *canfixbyremove= possi->ed; + break; + case itb_normal: case itb_deconfigure: case itb_preinstall: + switch (possi->ed->status) { + case stat_notinstalled: case stat_configfiles: + break; + default: + if (!versionsatisfied(&possi->ed->installed, possi)) break; + sprintf(linebuf, " %.250s (version %.250s) is %s.\n", + possi->ed->name, + versiondescribe(possi->ed->available.version, + possi->ed->available.revision), + statusstrings[possi->ed->status]); + varbufaddstr(whynot, linebuf); + if (!canfixbyremove) return 0; + nconflicts++; + *canfixbyremove= possi->ed; + } + break; + default: + internerr("unknown istobe conflict"); + } + } + + /* If there was no version specified we try looking for Providers. */ + if (possi->verrel == dvr_none) { + + /* See if the package we're about to install Provides it. */ + for (provider= possi->ed->available.depended; + provider; + provider= provider->nextrev) { + if (provider->up->type != dep_provides) continue; + if (provider->up->up->clientdata->istobe != itb_installnew) continue; + if (provider->up->up == dep->up) continue; /* conflicts and provides the same */ + sprintf(linebuf, " %.250s provides %.250s and is to be installed.\n", + provider->up->up->name, possi->ed->name); + varbufaddstr(whynot, linebuf); + /* We can't remove the one we're about to install: */ + if (canfixbyremove) *canfixbyremove= 0; + return 0; + } + + /* Now look at the packages already on the system. */ + for (provider= possi->ed->installed.depended; + provider; + provider= provider->nextrev) { + if (provider->up->type != dep_provides) continue; + + if (provider->up->up == dep->up) continue; /* conflicts and provides the same */ + + switch (provider->up->up->clientdata->istobe) { + case itb_installnew: + /* Don't pay any attention to the Provides field of the + * currently-installed version of the package we're trying + * to install. We dealt with that by using the available + * information above. + */ + continue; + case itb_remove: + continue; + case itb_normal: case itb_deconfigure: case itb_preinstall: + switch (provider->up->up->status) { + case stat_notinstalled: case stat_configfiles: + continue; + default: + sprintf(linebuf, " %.250s provides %.250s and is %s.\n", + provider->up->up->name, possi->ed->name, + statusstrings[provider->up->up->status]); + varbufaddstr(whynot, linebuf); + if (!canfixbyremove) return 0; + nconflicts++; + *canfixbyremove= provider->up->up; + break; + } + break; + default: + internerr("unknown istobe conflict provider"); + } + } + } + + if (!nconflicts) return 1; + if (nconflicts>1) *canfixbyremove= 0; + return 0; + + } /* if (dependency) {...} else {...} */ +} |