Pārlūkot izejas kodu

Add versioned Provides support

- Add a new dpkg --assert-versioned-provides command.
- Packages can provide a specific version, “virtual (= 1.0)” which will
  be honored, previously it would just be accepted when parsing.
- Non-versioned virtual packages will not satisfy versioned dependencies.
- Versioned virtual packages will satisfy non-versioned dependencies.

This commit is very slightly based on the code reverted in commit
7df7d53a57b9913f4a20b2c9dcd554a2d88ff9f3, which was mostly skeletal.

Closes: #7330, #24934, #112131, #134582, #180316

Based-on-patch-by: Ben Collins <bcollins@debian.org>
Guillem Jover 14 gadi atpakaļ
vecāks
revīzija
5bb02fe80e
10 mainītis faili ar 145 papildinājumiem un 51 dzēšanām
  1. 8 0
      debian/changelog
  2. 5 4
      dselect/pkgdepcon.cc
  3. 3 3
      dselect/pkgsublist.cc
  4. 40 0
      lib/dpkg/depcon.c
  5. 4 0
      lib/dpkg/dpkg-db.h
  6. 9 6
      src/depcon.c
  7. 10 2
      src/enquiry.c
  8. 2 1
      src/main.c
  9. 2 0
      src/main.h
  10. 62 35
      src/packages.c

+ 8 - 0
debian/changelog

@@ -24,6 +24,14 @@ dpkg (1.17.11) UNRELEASED; urgency=low
   * Do not write to the available file when unpacking binary packages. This
     information is not useful as dpkg has never recorded the archive path,
     so it has never been truly available for re-installation anyway.
+  * Add versioned Provides support:
+    - Add a new dpkg --assert-versioned-provides command.
+    - Packages can provide a specific version, “virtual (= 1.0)” which will
+      be honored, previously it would just be accepted when parsing.
+    - Non-versioned virtual packages will not satisfy versioned dependencies.
+    - Versioned virtual packages will satisfy non-versioned dependencies.
+    Based on skeletal code by Ben Collins <bcollins@debian.org>.
+    Closes: #7330, #24934, #112131, #134582, #180316
 
   [ Updated programs translations ]
   * Danish (Joe Dalton). Closes: #754127

+ 5 - 4
dselect/pkgdepcon.cc

@@ -414,8 +414,7 @@ packagelist::deppossatisfied(deppossi *possi, perpackagestate **fixbyupgrade)
       return false;
     }
   }
-  if (possi->verrel != DPKG_RELATION_NONE)
-    return false;
+
   deppossi *provider;
 
   for (provider = possi->ed->depended.installed;
@@ -427,7 +426,8 @@ packagelist::deppossatisfied(deppossi *possi, perpackagestate **fixbyupgrade)
         provider->up->up->clientdata &&
         !useavailable(provider->up->up) &&
         would_like_to_install(provider->up->up->clientdata->selected,
-                              provider->up->up))
+                              provider->up->up) &&
+        pkg_virtual_deppossi_satisfied(possi, provider))
       return true;
   }
   for (provider = possi->ed->depended.available;
@@ -438,7 +438,8 @@ packagelist::deppossatisfied(deppossi *possi, perpackagestate **fixbyupgrade)
          provider->up->up->set == possi->up->up->set) ||
         !provider->up->up->clientdata ||
         !would_like_to_install(provider->up->up->clientdata->selected,
-                               provider->up->up))
+                               provider->up->up) ||
+        !pkg_virtual_deppossi_satisfied(possi, provider))
       continue;
     if (useavailable(provider->up->up))
       return true;

+ 3 - 3
dselect/pkgsublist.cc

@@ -159,9 +159,9 @@ packagelist::add(dependency *depends, showpriority displayimportance)
   add(depends->up,info.string(),displayimportance);
   for (possi=depends->list; possi; possi=possi->next) {
     add(&possi->ed->pkg, info.string(), displayimportance);
-    if (possi->verrel == DPKG_RELATION_NONE && depends->type != dep_provides) {
-      // providers aren't relevant if a version was specified, or
-      // if we're looking at a provider relationship already
+    if (depends->type != dep_provides) {
+      /* Providers are not relevant if we are looking at a provider
+       * relationship already. */
       deppossi *provider;
       for (provider = possi->ed->depended.available;
            provider;

+ 40 - 0
lib/dpkg/depcon.c

@@ -83,3 +83,43 @@ archsatisfied(struct pkgbin *it, struct deppossi *against)
 {
 	return deparchsatisfied(it, it->arch, against);
 }
+
+/**
+ * Check if the dependency is satisfied by a virtual package.
+ *
+ * For versioned depends, we only check providers with #DPKG_RELATION_EQ. It
+ * does not make sense to check ones without a version since we have nothing
+ * to verify against. Also, it is way too complex to allow anything but an
+ * equal in a provided version. A few examples below to deter you from trying:
+ *
+ * - pkg1 depends on virt (>= 0.6), pkg2 provides virt (<= 1.0).
+ *   Should pass (easy enough).
+ *
+ * - pkg1 depends on virt (>= 0.7) and (<= 1.1), pkg2 provides virt (>= 1.2).
+ *   Should fail (little harder).
+ *
+ * - pkg1 depends on virt (>= 0.4), pkg2 provides virt (<= 1.0) and (>= 0.5),
+ *   IOW, inclusive of only those versions. This would require backchecking
+ *   the other provided versions in the possi, which would make things sickly
+ *   complex and overly time consuming. Should fail (very hard to implement).
+ *
+ * This could be handled by switching to a SAT solver, but that would imply
+ * lots of work for very little gain. Packages can easily get around most of
+ * these by providing multiple #DPKG_RELATION_EQ versions.
+ */
+bool
+pkg_virtual_deppossi_satisfied(struct deppossi *dependee,
+                               struct deppossi *provider)
+{
+	if (provider->verrel != DPKG_RELATION_NONE &&
+	    provider->verrel != DPKG_RELATION_EQ)
+		return false;
+
+	if (provider->verrel == DPKG_RELATION_NONE &&
+	    dependee->verrel != DPKG_RELATION_NONE)
+		return false;
+
+	return dpkg_version_relate(&provider->version,
+	                           dependee->verrel,
+	                           &dependee->version);
+}

+ 4 - 0
lib/dpkg/dpkg-db.h

@@ -406,6 +406,10 @@ bool deparchsatisfied(struct pkgbin *it, const struct dpkg_arch *arch,
                       struct deppossi *against);
 bool archsatisfied(struct pkgbin *it, struct deppossi *against);
 
+bool
+pkg_virtual_deppossi_satisfied(struct deppossi *dependee,
+                               struct deppossi *provider);
+
 /*** from nfmalloc.c ***/
 void *nfmalloc(size_t);
 char *nfstrsave(const char*);

+ 9 - 6
src/depcon.c

@@ -460,13 +460,13 @@ depisok(struct dependency *dep, struct varbuf *whynot,
       }
       deppossi_pkg_iter_free(possi_iter);
 
-      /* If there was no version specified we try looking for Providers. */
-      if (possi->verrel == DPKG_RELATION_NONE) {
         /* See if the package we're about to install Provides it. */
         for (provider = possi->ed->depended.available;
              provider;
              provider = provider->rev_next) {
           if (provider->up->type != dep_provides) continue;
+          if (!pkg_virtual_deppossi_satisfied(possi, provider))
+            continue;
           if (provider->up->up->clientdata->istobe == PKG_ISTOBE_INSTALLNEW)
             return true;
         }
@@ -476,6 +476,8 @@ depisok(struct dependency *dep, struct varbuf *whynot,
              provider;
              provider = provider->rev_next) {
           if (provider->up->type != dep_provides) continue;
+          if (!pkg_virtual_deppossi_satisfied(possi, provider))
+            continue;
 
           switch (provider->up->up->clientdata->istobe) {
           case PKG_ISTOBE_INSTALLNEW:
@@ -519,7 +521,6 @@ depisok(struct dependency *dep, struct varbuf *whynot,
           sprintf(linebuf, _("  %.250s is not installed.\n"), possi->ed->name);
           varbuf_add_str(whynot, linebuf);
         }
-      }
     }
 
     return false;
@@ -600,8 +601,6 @@ depisok(struct dependency *dep, struct varbuf *whynot,
       deppossi_pkg_iter_free(possi_iter);
     }
 
-    /* If there was no version specified we try looking for Providers. */
-    if (possi->verrel == DPKG_RELATION_NONE) {
       /* See if the package we're about to install Provides it. */
       for (provider = possi->ed->depended.available;
            provider;
@@ -611,6 +610,8 @@ depisok(struct dependency *dep, struct varbuf *whynot,
           continue;
         if (provider->up->up->set == dep->up->set)
           continue; /* Conflicts and provides the same. */
+        if (!pkg_virtual_deppossi_satisfied(possi, provider))
+          continue;
         sprintf(linebuf, _("  %.250s provides %.250s and is to be installed.\n"),
                 pkgbin_name(provider->up->up, &provider->up->up->available,
                             pnaw_nonambig), possi->ed->name);
@@ -630,6 +631,9 @@ depisok(struct dependency *dep, struct varbuf *whynot,
         if (provider->up->up->set == dep->up->set)
           continue; /* Conflicts and provides the same. */
 
+        if (!pkg_virtual_deppossi_satisfied(possi, provider))
+          continue;
+
         switch (provider->up->up->clientdata->istobe) {
         case PKG_ISTOBE_INSTALLNEW:
           /* Don't pay any attention to the Provides field of the
@@ -673,7 +677,6 @@ depisok(struct dependency *dep, struct varbuf *whynot,
                     provider->up->up->clientdata->istobe);
         }
       }
-    }
 
     if (!nconflicts)
       return true;

+ 10 - 2
src/enquiry.c

@@ -432,6 +432,14 @@ assertmultiarch(const char *const *argv)
   return assert_version_support(argv, &version, _("multi-arch"));
 }
 
+int
+assertverprovides(const char *const *argv)
+{
+  struct dpkg_version version = { 0, "1.17.11", NULL };
+
+  return assert_version_support(argv, &version, _("versioned Provides"));
+}
+
 /**
  * Print a single package which:
  *  (a) is the target of one or more relevant predependencies.
@@ -508,13 +516,13 @@ predeppackage(const char *const *argv)
           pkg = trypkg;
           break;
         }
-        if (possi->verrel != DPKG_RELATION_NONE)
-          continue;
         for (provider = possi->ed->depended.available;
              !pkg && provider;
              provider = provider->next) {
           if (provider->up->type != dep_provides)
             continue;
+          if (!pkg_virtual_deppossi_satisfied(possi, provider))
+            continue;
           trypkg = provider->up->up;
           if (!trypkg->files)
             continue;

+ 2 - 1
src/main.c

@@ -126,7 +126,7 @@ usage(const struct cmdinfo *ci, const char *value)
   printf(_(
 "For internal use: dpkg --assert-support-predepends | --predep-package |\n"
 "  --assert-working-epoch | --assert-long-filenames | --assert-multi-conrep |\n"
-"  --assert-multi-arch.\n"
+"  --assert-multi-arch | --assert-versioned-provides.\n"
 "\n"));
 
   printf(_(
@@ -672,6 +672,7 @@ static const struct cmdinfo cmdinfos[]= {
   ACTION( "assert-long-filenames",           0,  act_assertlongfilenames,  assertlongfilenames ),
   ACTION( "assert-multi-conrep",             0,  act_assertmulticonrep,    assertmulticonrep ),
   ACTION( "assert-multi-arch",               0,  act_assertmultiarch,      assertmultiarch ),
+  ACTION( "assert-versioned-provides",       0,  act_assertverprovides,    assertverprovides ),
   ACTION( "add-architecture",                0,  act_arch_add,             arch_add        ),
   ACTION( "remove-architecture",             0,  act_arch_remove,          arch_remove     ),
   ACTION( "print-architecture",              0,  act_printarch,            printarch   ),

+ 2 - 0
src/main.h

@@ -101,6 +101,7 @@ enum action {
 	act_assertlongfilenames,
 	act_assertmulticonrep,
 	act_assertmultiarch,
+	act_assertverprovides,
 
 	act_audit,
 	act_unpackchk,
@@ -165,6 +166,7 @@ int assertpredep(const char *const *argv);
 int assertlongfilenames(const char *const *argv);
 int assertmulticonrep(const char *const *argv);
 int assertmultiarch(const char *const *argv);
+int assertverprovides(const char *const *argv);
 int predeppackage(const char *const *argv);
 int printarch(const char *const *argv);
 int printinstarch(const char *const *argv);

+ 62 - 35
src/packages.c

@@ -328,7 +328,7 @@ enum found_status {
  */
 static enum found_status
 deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
-                  struct pkginfo *removing, struct pkgset *providing,
+                  struct pkginfo *removing, struct deppossi *provider,
                   struct pkginfo **fixbytrig,
                   bool *matched, struct deppossi *checkversion,
                   int *interestingwarnings, struct varbuf *oemsgs)
@@ -341,10 +341,11 @@ deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
   }
   thisf = FOUND_NONE;
   if (possdependee == removing) {
-    if (providing) {
+    if (provider) {
       varbuf_printf(oemsgs,
                     _("  Package %s which provides %s is to be removed.\n"),
-                    pkg_name(possdependee, pnaw_nonambig), providing->name);
+                    pkg_name(possdependee, pnaw_nonambig),
+                    provider->ed->name);
     } else {
       varbuf_printf(oemsgs, _("  Package %s is to be removed.\n"),
                     pkg_name(possdependee, pnaw_nonambig));
@@ -360,17 +361,35 @@ deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
   case PKG_STAT_TRIGGERSAWAITED:
   case PKG_STAT_TRIGGERSPENDING:
   case PKG_STAT_INSTALLED:
-    if (checkversion && !versionsatisfied(&possdependee->installed,checkversion)) {
-      varbuf_printf(oemsgs, _("  Version of %s on system is %s.\n"),
-                    pkg_name(possdependee, pnaw_nonambig),
-                    versiondescribe(&possdependee->installed.version,
-                                    vdew_nonambig));
-      assert(checkversion->verrel != DPKG_RELATION_NONE);
-      if (fc_dependsversion)
-        thisf = (dependtry >= 3) ? FOUND_FORCED : FOUND_DEFER;
-      debug(dbg_depcondetail,"      bad version, returning %d",thisf);
-      (*interestingwarnings)++;
-      return thisf;
+    if (checkversion) {
+      if (provider) {
+        debug(dbg_depcondetail, "      checking package %s provided by pkg %s",
+              checkversion->ed->name, pkg_name(possdependee, pnaw_always));
+        if (!pkg_virtual_deppossi_satisfied(checkversion, provider)) {
+          varbuf_printf(oemsgs,
+                        _("  Version of %s on system, provided by %s, is %s.\n"),
+                        checkversion->ed->name,
+                        pkg_name(possdependee, pnaw_always),
+                        versiondescribe(&provider->version, vdew_nonambig));
+          if (fc_dependsversion)
+            thisf = (dependtry >= 3) ? FOUND_FORCED : FOUND_DEFER;
+          debug(dbg_depcondetail, "      bad version");
+          goto unsuitable;
+        }
+      } else {
+        debug(dbg_depcondetail, "      checking non-provided pkg %s",
+              pkg_name(possdependee, pnaw_always));
+        if (!versionsatisfied(&possdependee->installed, checkversion)) {
+          varbuf_printf(oemsgs, _("  Version of %s on system is %s.\n"),
+                        pkg_name(possdependee, pnaw_nonambig),
+                        versiondescribe(&possdependee->installed.version,
+                                        vdew_nonambig));
+          if (fc_dependsversion)
+            thisf = (dependtry >= 3) ? FOUND_FORCED : FOUND_DEFER;
+          debug(dbg_depcondetail, "      bad version");
+          goto unsuitable;
+        }
+      }
     }
     if (possdependee->status == PKG_STAT_INSTALLED ||
         possdependee->status == PKG_STAT_TRIGGERSPENDING) {
@@ -382,10 +401,11 @@ deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
       if (removing ||
           !(f_triggers ||
             possdependee->clientdata->istobe == PKG_ISTOBE_INSTALLNEW)) {
-        if (providing) {
+        if (provider) {
           varbuf_printf(oemsgs,
                         _("  Package %s which provides %s awaits trigger processing.\n"),
-                        pkg_name(possdependee, pnaw_nonambig), providing->name);
+                        pkg_name(possdependee, pnaw_nonambig),
+                        provider->ed->name);
         } else {
           varbuf_printf(oemsgs,
                         _("  Package %s awaits trigger processing.\n"),
@@ -424,10 +444,11 @@ deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
       sincenothing = 0;
       return FOUND_DEFER;
     } else {
-      if (providing) {
+      if (provider) {
         varbuf_printf(oemsgs,
                       _("  Package %s which provides %s is not configured yet.\n"),
-                      pkg_name(possdependee, pnaw_nonambig), providing->name);
+                      pkg_name(possdependee, pnaw_nonambig),
+                      provider->ed->name);
       } else {
         varbuf_printf(oemsgs, _("  Package %s is not configured yet.\n"),
                       pkg_name(possdependee, pnaw_nonambig));
@@ -438,10 +459,11 @@ deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
     }
 
   default:
-    if (providing) {
+    if (provider) {
       varbuf_printf(oemsgs,
                     _("  Package %s which provides %s is not installed.\n"),
-                    pkg_name(possdependee, pnaw_nonambig), providing->name);
+                    pkg_name(possdependee, pnaw_nonambig),
+                    provider->ed->name);
     } else {
       varbuf_printf(oemsgs, _("  Package %s is not installed.\n"),
                     pkg_name(possdependee, pnaw_nonambig));
@@ -461,13 +483,13 @@ unsuitable:
 static void
 breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
                  struct deppossi *breaks, struct pkginfo *broken,
-                 struct pkginfo *breaker, struct pkgset *virtbroken)
+                 struct pkginfo *breaker, struct deppossi *virtbroken)
 {
   struct varbuf depmsg = VARBUF_INIT;
 
   debug(dbg_depcondetail, "      checking breaker %s virtbroken %s",
         pkg_name(breaker, pnaw_always),
-        virtbroken ? virtbroken->name : "<none>");
+        virtbroken ? virtbroken->ed->name : "<none>");
 
   if (breaker->status == PKG_STAT_NOTINSTALLED ||
       breaker->status == PKG_STAT_CONFIGFILES)
@@ -479,7 +501,9 @@ breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
   if (!archsatisfied(&broken->installed, breaks))
     return;
   if (ignore_depends(breaker)) return;
-  if (virtbroken && ignore_depends(&virtbroken->pkg))
+  if (virtbroken && ignore_depends(&virtbroken->ed->pkg))
+    return;
+  if (virtbroken && !pkg_virtual_deppossi_satisfied(breaks, virtbroken))
     return;
 
   varbufdependency(&depmsg, breaks->up);
@@ -494,7 +518,7 @@ breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
     varbuf_printf(aemsgs, _("  %s (%s) provides %s.\n"),
                   pkg_name(broken, pnaw_nonambig),
                   versiondescribe(&broken->installed.version, vdew_nonambig),
-                  virtbroken->name);
+                  virtbroken->ed->name);
   } else if (breaks->verrel != DPKG_RELATION_NONE) {
     varbuf_printf(aemsgs, _("  Version of %s to be configured is %s.\n"),
                   pkg_name(broken, pnaw_nonambig),
@@ -508,14 +532,12 @@ breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
 static void
 breaks_check_target(struct varbuf *aemsgs, enum dep_check *ok,
                     struct pkginfo *broken, struct pkgset *target,
-                    struct pkgset *virtbroken)
+                    struct deppossi *virtbroken)
 {
   struct deppossi *possi;
 
   for (possi = target->depended.installed; possi; possi = possi->rev_next) {
     if (possi->up->type != dep_breaks) continue;
-    if (virtbroken && possi->verrel != DPKG_RELATION_NONE)
-      continue;
     breaks_check_one(aemsgs, ok, possi, broken, possi->up->up, virtbroken);
   }
 }
@@ -524,7 +546,7 @@ enum dep_check
 breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs)
 {
   struct dependency *dep;
-  struct pkgset *virtbroken;
+  struct deppossi *virtbroken;
   enum dep_check ok = DEP_CHECK_OK;
 
   debug(dbg_depcon, "    checking Breaks");
@@ -533,9 +555,9 @@ breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs)
 
   for (dep= pkg->installed.depends; dep; dep= dep->next) {
     if (dep->type != dep_provides) continue;
-    virtbroken = dep->list->ed;
-    debug(dbg_depcondetail, "     checking virtbroken %s", virtbroken->name);
-    breaks_check_target(aemsgs, &ok, pkg, virtbroken, virtbroken);
+    virtbroken = dep->list;
+    debug(dbg_depcondetail, "     checking virtbroken %s", virtbroken->ed->name);
+    breaks_check_target(aemsgs, &ok, pkg, virtbroken->ed, virtbroken);
   }
   return ok;
 }
@@ -597,7 +619,7 @@ dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
       }
       deppossi_pkg_iter_free(possi_iter);
 
-      if (found != FOUND_OK && possi->verrel == DPKG_RELATION_NONE) {
+      if (found != FOUND_OK) {
         for (provider = possi->ed->depended.installed;
              found != FOUND_OK && provider;
              provider = provider->rev_next) {
@@ -610,10 +632,15 @@ dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
             debug(dbg_depcondetail, "       provider does not satisfy arch");
             continue;
           }
-          thisf = deppossi_ok_found(provider->up->up, pkg, removing,
-                                    possi->ed,
-                                    &possfixbytrig, &matched, NULL,
+          thisf = deppossi_ok_found(provider->up->up, pkg, removing, provider,
+                                    &possfixbytrig, &matched, possi,
                                     &interestingwarnings, &oemsgs);
+          if (thisf == FOUND_DEFER && provider->up->up == pkg && !removing) {
+            /* IOW, if the pkg satisfies its own dep (via a provide), then
+             * we let it pass, even if it isn't configured yet (as we're
+             * installing it). */
+             thisf = FOUND_OK;
+          }
           if (thisf > found)
             found = thisf;
         }