Abort package removal if the prerm script of a package returns non zero.
[oweals/opkg-lede.git] / libopkg / opkg_remove.c
index aadfa6aa6d4b9925775b978fba780fbf9c36d4f3..c6f2e37141c3bb7339311344eb9e52adfd988372 100644 (file)
    General Public License for more details.
 */
 
-#include "includes.h"
-#include "opkg_message.h"
-
+#include <stdio.h>
 #include <glob.h>
+#include <unistd.h>
 
+#include "opkg_message.h"
 #include "opkg_remove.h"
-#include "opkg_error.h"
 #include "opkg_cmd.h"
-
 #include "file_util.h"
 #include "sprintf_alloc.h"
-#include "str_util.h"
 #include "libbb/libbb.h"
 
 /*
  * Returns number of the number of packages depending on the packages provided by this package.
  * Every package implicitly provides itself.
  */
-int pkg_has_installed_dependents(opkg_conf_t *conf, abstract_pkg_t *parent_apkg, pkg_t *pkg, abstract_pkg_t *** pdependents)
+int
+pkg_has_installed_dependents(pkg_t *pkg, abstract_pkg_t *** pdependents)
 {
      int nprovides = pkg->provides_count;
      abstract_pkg_t **provides = pkg->provides;
-     int n_installed_dependents = 0;
+     unsigned int n_installed_dependents = 0;
      int i;
      for (i = 0; i < nprovides; i++) {
          abstract_pkg_t *providee = provides[i];
@@ -81,7 +79,8 @@ int pkg_has_installed_dependents(opkg_conf_t *conf, abstract_pkg_t *parent_apkg,
      return n_installed_dependents;
 }
 
-int opkg_remove_dependent_pkgs (opkg_conf_t *conf, pkg_t *pkg, abstract_pkg_t **dependents)
+static int
+opkg_remove_dependent_pkgs(pkg_t *pkg, abstract_pkg_t **dependents)
 {
     int i;
     int a;
@@ -90,11 +89,11 @@ int opkg_remove_dependent_pkgs (opkg_conf_t *conf, pkg_t *pkg, abstract_pkg_t **
     abstract_pkg_t * ab_pkg;
 
     if((ab_pkg = pkg->parent) == NULL){
-       fprintf(stderr, "%s: unable to get dependent pkgs. pkg %s isn't in hash table\n",
-               __FUNCTION__, pkg->name);
+       opkg_msg(ERROR, "Internal error: pkg %s isn't in hash table\n",
+               pkg->name);
        return 0;
     }
-    
+
     if (dependents == NULL)
            return 0;
 
@@ -110,10 +109,10 @@ int opkg_remove_dependent_pkgs (opkg_conf_t *conf, pkg_t *pkg, abstract_pkg_t **
 
     while (dependents [i] != NULL) {
         abstract_pkg_t *dep_ab_pkg = dependents[i];
-       
+
        if (dep_ab_pkg->dependencies_checked == 2){
            i++;
-           continue;   
+           continue;
         }
         if (dep_ab_pkg->state_status == SS_INSTALLED) {
             for (a = 0; a < dep_ab_pkg->pkgs->len; a++) {
@@ -126,116 +125,127 @@ int opkg_remove_dependent_pkgs (opkg_conf_t *conf, pkg_t *pkg, abstract_pkg_t **
         }
        i++;
        /* 1 - to keep track of visited ab_pkgs when checking for possiblility of a broken removal of pkgs.
-        * 2 - to keep track of pkgs whose deps have been checked alrdy  - Karthik */   
+        * 2 - to keep track of pkgs whose deps have been checked alrdy  - Karthik */
     }
-    
+
     if (count == 1) {
-        free(dependent_pkgs);  
+        pkg_vec_free(dependent_pkgs);
        return 0;
     }
-    
-    
+
+
     int err=0;
     for (i = 0; i < dependent_pkgs->len; i++) {
-        err = opkg_remove_pkg(conf, dependent_pkgs->pkgs[i],0);
-        if (err)
+        err = opkg_remove_pkg(dependent_pkgs->pkgs[i],0);
+        if (err) {
+            pkg_vec_free(dependent_pkgs);
             break;
+       }
     }
-    free(dependent_pkgs);
+    pkg_vec_free(dependent_pkgs);
     return err;
 }
 
-static int user_prefers_removing_dependents(opkg_conf_t *conf, abstract_pkg_t *abpkg, pkg_t *pkg, abstract_pkg_t **dependents)
+static void
+print_dependents_warning(pkg_t *pkg, abstract_pkg_t **dependents)
 {
     abstract_pkg_t *dep_ab_pkg;
-    opkg_message(conf, OPKG_ERROR, "Package %s is depended upon by packages:\n", pkg->name);
+    opkg_msg(ERROR, "Package %s is depended upon by packages:\n", pkg->name);
     while ((dep_ab_pkg = *dependents++) != NULL) {
         if (dep_ab_pkg->state_status == SS_INSTALLED)
-             opkg_message(conf, OPKG_ERROR, "\t%s\n", dep_ab_pkg->name);
+             opkg_msg(ERROR, "\t%s\n", dep_ab_pkg->name);
     }
-    opkg_message(conf, OPKG_ERROR, "These might cease to work if package %s is removed.\n\n", pkg->name);
-    opkg_message(conf, OPKG_ERROR, "");
-    opkg_message(conf, OPKG_ERROR, "You can force removal of this package with -force-depends.\n");
-    opkg_message(conf, OPKG_ERROR, "You can force removal of this package and its dependents\n");
-    opkg_message(conf, OPKG_ERROR, "with -force-removal-of-dependent-packages or -recursive\n");
-    opkg_message(conf, OPKG_ERROR, "or by setting option force_removal_of_dependent_packages\n");
-    opkg_message(conf, OPKG_ERROR, "in opkg.conf.\n");
-    return 0;
+    opkg_msg(ERROR, "These might cease to work if package %s is removed.\n\n",
+                   pkg->name);
+    opkg_msg(ERROR, "Force removal of this package with --force-depends.\n");
+    opkg_msg(ERROR, "Force removal of this package and its dependents\n");
+    opkg_msg(ERROR, "with --force-removal-of-dependent-packages.\n");
 }
 
-static int remove_autoinstalled (opkg_conf_t *conf, pkg_t *pkg)
+/*
+ * Find and remove packages that were autoinstalled and are orphaned
+ * by the removal of pkg.
+ */
+static int
+remove_autoinstalled(pkg_t *pkg)
 {
-  /*
-   * find and remove packages that were autoinstalled and are orphaned by the removal of pkg
-   */
-
-  char *buffer, *d_str;
-  int i;
-
-  for (i = 0; i < pkg->depends_count; ++i)
-  {
-    int x = 0;
-    pkg_t *p;
-    d_str = pkg->depends_str[i];
-    buffer = xcalloc(1, strlen (d_str) + 1);
-
-    while (d_str[x] != '\0' && d_str[x] != ' ')
-    {
-      buffer[x] = d_str[x];
-      ++x;
-    }
-    buffer[x] = '\0';
-    buffer = xrealloc (buffer, strlen (buffer) + 1);
-    p = pkg_hash_fetch_installed_by_name (&conf->pkg_hash, buffer);
-
-    /* if the package is not installed, this could have been a circular
-     * depenancy and the package has already been removed */
-    if (!p)
-      return -1;
-
-    if (p->auto_installed)
-    {
-      int deps;
-      abstract_pkg_t **dependents;
-
-      deps = pkg_has_installed_dependents(conf, NULL, p, &dependents);
-      if (deps == 0)
-      {
-        opkg_message (conf, OPKG_INFO,
-                      "%s was autoinstalled but is now orphaned\n", buffer);
-         opkg_remove_pkg(conf, p,0);
-      }
-       else
-          opkg_message (conf, OPKG_INFO, "%s was autoinstalled and is still required by "
-                        "%d installed packages\n", buffer, deps);
-    }
-    free (buffer);
-  }
+       int i, j;
+       int err = 0;
+       int n_deps;
+       pkg_t *p;
+       struct compound_depend *cdep;
+       abstract_pkg_t **dependents;
+
+       int count = pkg->pre_depends_count +
+                               pkg->depends_count +
+                               pkg->recommends_count +
+                               pkg->suggests_count;
+
+       for (i=0; i<count; i++) {
+               cdep = &pkg->depends[i];
+               if (cdep->type != PREDEPEND
+                   && cdep->type != DEPEND
+                   && cdep->type != RECOMMEND)
+                       continue;
+               for (j=0; j<cdep->possibility_count; j++) {
+                       p = pkg_hash_fetch_installed_by_name(
+                                       cdep->possibilities[j]->pkg->name);
+
+                       /* If the package is not installed, this could have
+                        * been a circular dependency and the package has
+                        * already been removed.
+                        */
+                       if (!p)
+                               return -1;
+
+                       if (!p->auto_installed)
+                               continue;
+
+                       n_deps = pkg_has_installed_dependents(p, &dependents);
+                       if (n_deps == 0) {
+                                opkg_msg(NOTICE, "%s was autoinstalled and is "
+                                              "now orphaned, removing.\n",
+                                              p->name);
+                               if (opkg_remove_pkg(p, 0) != 0) {
+                                       err = -1;
+                               }
+                       } else
+                               opkg_msg(INFO, "%s was autoinstalled and is "
+                                               "still required by %d "
+                                               "installed packages.\n",
+                                               p->name, n_deps);
+
+                       if (dependents)
+                               free(dependents);
+               }
+       }
 
-  return 0;
+       return err;
 }
 
-int opkg_remove_pkg(opkg_conf_t *conf, pkg_t *pkg,int message)
+int
+opkg_remove_pkg(pkg_t *pkg, int from_upgrade)
 {
-/* Actually, when "message == 1" I have been called from an upgrade, and not from a normal remove
-   thus I wan't check for essential, as I'm upgrading.
-   I hope it won't break anything :) 
-*/
      int err;
      abstract_pkg_t *parent_pkg = NULL;
 
-     if (pkg->essential && !message) {
+/*
+ * If called from an upgrade and not from a normal remove,
+ * ignore the essential flag.
+ */
+     if (pkg->essential && !from_upgrade) {
          if (conf->force_removal_of_essential_packages) {
-              fprintf(stderr, "WARNING: Removing essential package %s under your coercion.\n"
+              opkg_msg(NOTICE,
+                      "Removing essential package %s under your coercion.\n"
                       "\tIf your system breaks, you get to keep both pieces\n",
                       pkg->name);
          } else {
-              fprintf(stderr, "ERROR: Refusing to remove essential package %s.\n"
+              opkg_msg(NOTICE, "Refusing to remove essential package %s.\n"
                       "\tRemoving an essential package may lead to an unusable system, but if\n"
                       "\tyou enjoy that kind of pain, you can force opkg to proceed against\n"
-                      "\tits will with the option: -force-removal-of-essential-packages\n",
+                      "\tits will with the option: --force-removal-of-essential-packages\n",
                       pkg->name);
-              return OPKG_PKG_IS_ESSENTIAL;
+              return -1;
          }
      }
 
@@ -249,23 +259,23 @@ int opkg_remove_pkg(opkg_conf_t *conf, pkg_t *pkg,int message)
      if (!conf->force_depends
         && !(pkg->state_flag & SF_REPLACE)) {
          abstract_pkg_t **dependents;
-         int has_installed_dependents = 
-              pkg_has_installed_dependents(conf, parent_pkg, pkg, &dependents);
+         int has_installed_dependents =
+              pkg_has_installed_dependents(pkg, &dependents);
 
          if (has_installed_dependents) {
               /*
-               * if this package is depended up by others, then either we should
-               * not remove it or we should remove it and all of its dependents 
+               * if this package is depended upon by others, then either we should
+               * not remove it or we should remove it and all of its dependents
                */
 
-              if (!conf->force_removal_of_dependent_packages
-                  && !user_prefers_removing_dependents(conf, parent_pkg, pkg, dependents)) {
+              if (!conf->force_removal_of_dependent_packages) {
+                   print_dependents_warning(pkg, dependents);
                    free(dependents);
-                   return OPKG_PKG_HAS_DEPENDENTS;
+                   return -1;
               }
 
               /* remove packages depending on this package - Karthik */
-              err = opkg_remove_dependent_pkgs (conf, pkg, dependents);
+              err = opkg_remove_dependent_pkgs(pkg, dependents);
               if (err) {
                 free(dependents);
                  return err;
@@ -275,57 +285,52 @@ int opkg_remove_pkg(opkg_conf_t *conf, pkg_t *pkg,int message)
               free(dependents);
      }
 
-     if ( message==0 ){
-         opkg_message (conf, OPKG_NOTICE,
-                      "Removing package %s from %s...\n", pkg->name, pkg->dest->name);
-         fflush(stdout);
+     if (from_upgrade == 0) {
+         opkg_msg(NOTICE, "Removing package %s from %s...\n",
+                        pkg->name, pkg->dest->name);
      }
      pkg->state_flag |= SF_FILELIST_CHANGED;
 
      pkg->state_want = SW_DEINSTALL;
      opkg_state_changed++;
 
-     pkg_run_script(conf, pkg, "prerm", "remove");
+     if (pkg_run_script(pkg, "prerm", "remove") != 0) {
+         if (!conf->force_remove) {
+             opkg_msg(ERROR, "not removing package \"%s\", "
+                             "prerm script failed\n", pkg->name);
+             opkg_msg(NOTICE, "You can force removal of packages with failed "
+                              "prerm scripts with the option: \n"
+                              "\t--force-remove\n");
+             return -1;
+         }
+     }
 
      /* DPKG_INCOMPATIBILITY: dpkg is slightly different here. It
        maintains an empty filelist rather than deleting it. That seems
        like a big pain, and I don't see that that should make a big
        difference, but for anyone who wants tighter compatibility,
        feel free to fix this. */
-     remove_data_files_and_list(conf, pkg);
-
-     pkg_run_script(conf, pkg, "postrm", "remove");
+     remove_data_files_and_list(pkg);
 
-     remove_maintainer_scripts_except_postrm(conf, pkg);
+     err = pkg_run_script(pkg, "postrm", "remove");
 
-     /* Aman Gupta - Since opkg is made for handheld devices with limited
-      * space, it doesn't make sense to leave extra configurations, files, 
-      * and maintainer scripts left around. So, we make remove like purge, 
-      * and take out all the crap :) */
-
-     remove_postrm(conf, pkg);
+     remove_maintainer_scripts(pkg);
      pkg->state_status = SS_NOT_INSTALLED;
 
-     if (parent_pkg) 
+     if (parent_pkg)
          parent_pkg->state_status = SS_NOT_INSTALLED;
 
-
      /* remove autoinstalled packages that are orphaned by the removal of this one */
-     if (conf->autoremove)
-       remove_autoinstalled (conf, pkg);
-
-
-
-     return 0;
-}
-
-int opkg_purge_pkg(opkg_conf_t *conf, pkg_t *pkg)
-{
-    opkg_remove_pkg(conf, pkg,0);
-    return 0;
+     if (conf->autoremove) {
+         if (remove_autoinstalled(pkg) != 0) {
+             err = -1;
+         }
+     }
+     return err;
 }
 
-int remove_data_files_and_list(opkg_conf_t *conf, pkg_t *pkg)
+void
+remove_data_files_and_list(pkg_t *pkg)
 {
      str_list_t installed_dirs;
      str_list_t *installed_files;
@@ -336,8 +341,14 @@ int remove_data_files_and_list(opkg_conf_t *conf, pkg_t *pkg)
      pkg_t *owner;
      int rootdirlen = 0;
 
+     installed_files = pkg_get_installed_files(pkg);
+     if (installed_files == NULL) {
+            opkg_msg(ERROR, "Failed to determine installed "
+                    "files for %s. None removed.\n", pkg->name);
+            return;
+     }
+
      str_list_init(&installed_dirs);
-     installed_files = pkg_get_installed_files(conf, pkg);
 
      /* don't include trailing slash */
      if (conf->offline_root)
@@ -346,6 +357,11 @@ int remove_data_files_and_list(opkg_conf_t *conf, pkg_t *pkg)
      for (iter = str_list_first(installed_files); iter; iter = str_list_next(installed_files, iter)) {
          file_name = (char *)iter->data;
 
+         owner = file_hash_get_file_owner(file_name);
+         if (owner != pkg)
+                 /* File may have been claimed by another package. */
+                 continue;
+
          if (file_is_dir(file_name)) {
               str_list_append(&installed_dirs, file_name);
               continue;
@@ -353,32 +369,32 @@ int remove_data_files_and_list(opkg_conf_t *conf, pkg_t *pkg)
 
          conffile = pkg_get_conffile(pkg, file_name+rootdirlen);
          if (conffile) {
-              /* XXX: QUESTION: Is this right? I figure we only need to
-                 save the conffile if it has been modified. Is that what
-                 dpkg does? Or does dpkg preserve all conffiles? If so,
-                 this seems like a better thing to do to conserve
-                 space. */
-              if (conffile_has_been_modified(conf, conffile)) {
-                   opkg_message (conf, OPKG_NOTICE,
-                                 "  not deleting modified conffile %s\n", file_name);
-                   fflush(stdout);
+              if (conffile_has_been_modified(conffile)) {
+                   opkg_msg(NOTICE, "Not deleting modified conffile %s.\n",
+                                   file_name);
                    continue;
               }
          }
 
-         opkg_message(conf, OPKG_INFO, "  deleting %s (noaction=%d)\n", file_name, conf->noaction);
-         if (!conf->noaction)
+         if (!conf->noaction) {
+               opkg_msg(INFO, "Deleting %s.\n", file_name);
               unlink(file_name);
+         } else
+               opkg_msg(INFO, "Not deleting %s. (noaction)\n",
+                               file_name);
+
+         file_hash_remove(file_name);
      }
 
+     /* Remove empty directories */
      if (!conf->noaction) {
          do {
               removed_a_dir = 0;
               for (iter = str_list_first(&installed_dirs); iter; iter = str_list_next(&installed_dirs, iter)) {
                    file_name = (char *)iter->data;
-           
+
                    if (rmdir(file_name) == 0) {
-                        opkg_message(conf, OPKG_INFO, "  deleting %s\n", file_name);
+                        opkg_msg(INFO, "Deleting %s.\n", file_name);
                         removed_a_dir = 1;
                         str_list_remove(&installed_dirs, &iter);
                    }
@@ -387,15 +403,13 @@ int remove_data_files_and_list(opkg_conf_t *conf, pkg_t *pkg)
      }
 
      pkg_free_installed_files(pkg);
-     /* We have to remove the file list now, so that
-       find_pkg_owning_file does not always just report this package */
-     pkg_remove_installed_files_list(conf, pkg);
+     pkg_remove_installed_files_list(pkg);
 
      /* Don't print warning for dirs that are provided by other packages */
      for (iter = str_list_first(&installed_dirs); iter; iter = str_list_next(&installed_dirs, iter)) {
          file_name = (char *)iter->data;
 
-         owner = file_hash_get_file_owner(conf, file_name);
+         owner = file_hash_get_file_owner(file_name);
          if (owner) {
               free(iter->data);
               iter->data = NULL;
@@ -410,48 +424,29 @@ int remove_data_files_and_list(opkg_conf_t *conf, pkg_t *pkg)
         free(iter);
      }
      str_list_deinit(&installed_dirs);
-
-     return 0;
 }
 
-int remove_maintainer_scripts_except_postrm(opkg_conf_t *conf, pkg_t *pkg)
+void
+remove_maintainer_scripts(pkg_t *pkg)
 {
-    int i, err;
-    char *globpattern;
-    glob_t globbuf;
-    
-    if (conf->noaction) return 0;
-
-    sprintf_alloc(&globpattern, "%s/%s.*",
-                 pkg->dest->info_dir, pkg->name);
-    err = glob(globpattern, 0, NULL, &globbuf);
-    free(globpattern);
-    if (err) {
-       return 0;
-    }
+       int i, err;
+       char *globpattern;
+       glob_t globbuf;
 
-    for (i = 0; i < globbuf.gl_pathc; i++) {
-       if (str_ends_with(globbuf.gl_pathv[i], ".postrm")) {
-           continue;
-       }
-        opkg_message(conf, OPKG_INFO, "  deleting %s\n", globbuf.gl_pathv[i]);
-       unlink(globbuf.gl_pathv[i]);
-    }
-    globfree(&globbuf);
+       if (conf->noaction)
+               return;
 
-    return 0;
-}
-
-int remove_postrm(opkg_conf_t *conf, pkg_t *pkg)
-{
-    char *postrm_file_name;
+       sprintf_alloc(&globpattern, "%s/%s.*",
+                       pkg->dest->info_dir, pkg->name);
 
-    if (conf->noaction) return 0;
+       err = glob(globpattern, 0, NULL, &globbuf);
+       free(globpattern);
+       if (err)
+               return;
 
-    sprintf_alloc(&postrm_file_name, "%s/%s.postrm",
-                 pkg->dest->info_dir, pkg->name);
-    unlink(postrm_file_name);
-    free(postrm_file_name);
-
-    return 0;
+       for (i = 0; i < globbuf.gl_pathc; i++) {
+               opkg_msg(INFO, "Deleting %s.\n", globbuf.gl_pathv[i]);
+               unlink(globbuf.gl_pathv[i]);
+       }
+       globfree(&globbuf);
 }