libopkg: fix double-free crash on recursive package removal
[oweals/opkg-lede.git] / libopkg / opkg_remove.c
index b5045ea9905263cdc5ce0bcd149c82e5043d759e..1ff270d7197b78b5412c3592f1d78099ebcd6dc9 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_cmd.h"
-
 #include "file_util.h"
 #include "sprintf_alloc.h"
 #include "libbb/libbb.h"
@@ -94,7 +93,7 @@ opkg_remove_dependent_pkgs(pkg_t *pkg, abstract_pkg_t **dependents)
                pkg->name);
        return 0;
     }
-    
+
     if (dependents == NULL)
            return 0;
 
@@ -110,10 +109,10 @@ opkg_remove_dependent_pkgs(pkg_t *pkg, abstract_pkg_t **dependents)
 
     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,20 +125,19 @@ opkg_remove_dependent_pkgs(pkg_t *pkg, abstract_pkg_t **dependents)
         }
        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) {
-        pkg_vec_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(dependent_pkgs->pkgs[i],0);
         if (err) {
-            pkg_vec_free(dependent_pkgs);
             break;
        }
     }
@@ -171,6 +169,7 @@ static int
 remove_autoinstalled(pkg_t *pkg)
 {
        int i, j;
+       int err = 0;
        int n_deps;
        pkg_t *p;
        struct compound_depend *cdep;
@@ -183,7 +182,9 @@ remove_autoinstalled(pkg_t *pkg)
 
        for (i=0; i<count; i++) {
                cdep = &pkg->depends[i];
-               if (cdep->type != DEPEND)
+               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(
@@ -204,7 +205,9 @@ remove_autoinstalled(pkg_t *pkg)
                                 opkg_msg(NOTICE, "%s was autoinstalled and is "
                                               "now orphaned, removing.\n",
                                               p->name);
-                                opkg_remove_pkg(p, 0);
+                               if (opkg_remove_pkg(p, 0) != 0) {
+                                       err = -1;
+                               }
                        } else
                                opkg_msg(INFO, "%s was autoinstalled and is "
                                                "still required by %d "
@@ -216,7 +219,7 @@ remove_autoinstalled(pkg_t *pkg)
                }
        }
 
-       return 0;
+       return err;
 }
 
 int
@@ -255,13 +258,13 @@ opkg_remove_pkg(pkg_t *pkg, int from_upgrade)
      if (!conf->force_depends
         && !(pkg->state_flag & SF_REPLACE)) {
          abstract_pkg_t **dependents;
-         int has_installed_dependents = 
+         int has_installed_dependents =
               pkg_has_installed_dependents(pkg, &dependents);
 
          if (has_installed_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 
+               * not remove it or we should remove it and all of its dependents
                */
 
               if (!conf->force_removal_of_dependent_packages) {
@@ -290,7 +293,16 @@ opkg_remove_pkg(pkg_t *pkg, int from_upgrade)
      pkg->state_want = SW_DEINSTALL;
      opkg_state_changed++;
 
-     pkg_run_script(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
@@ -299,19 +311,21 @@ opkg_remove_pkg(pkg_t *pkg, int from_upgrade)
        feel free to fix this. */
      remove_data_files_and_list(pkg);
 
-     pkg_run_script(pkg, "postrm", "remove");
+     err = pkg_run_script(pkg, "postrm", "remove");
 
      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(pkg);
-
-     return 0;
+     if (conf->autoremove) {
+         if (remove_autoinstalled(pkg) != 0) {
+             err = -1;
+         }
+     }
+     return err;
 }
 
 void
@@ -342,6 +356,11 @@ remove_data_files_and_list(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;
@@ -357,12 +376,13 @@ remove_data_files_and_list(pkg_t *pkg)
          }
 
          if (!conf->noaction) {
-               opkg_msg(INFO, "Deleting %s.\n", file_name, 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 */
@@ -371,7 +391,7 @@ remove_data_files_and_list(pkg_t *pkg)
               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_msg(INFO, "Deleting %s.\n", file_name);
                         removed_a_dir = 1;