libalpm
Arch Linux Package Manager Library
remove.c
Go to the documentation of this file.
00001 /*
00002  *  remove.c
00003  *
00004  *  Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
00005  *  Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
00006  *  Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
00007  *  Copyright (c) 2005 by Christian Hamar <krics@linuxforum.hu>
00008  *  Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org>
00009  *  Copyright (c) 2005, 2006 by Miklos Vajna <vmiklos@frugalware.org>
00010  *
00011  *  This program is free software; you can redistribute it and/or modify
00012  *  it under the terms of the GNU General Public License as published by
00013  *  the Free Software Foundation; either version 2 of the License, or
00014  *  (at your option) any later version.
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU General Public License for more details.
00020  *
00021  *  You should have received a copy of the GNU General Public License
00022  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00023  */
00024 
00025 #include <stdlib.h>
00026 #include <errno.h>
00027 #include <string.h>
00028 #include <limits.h>
00029 #include <unistd.h>
00030 #include <sys/stat.h>
00031 
00032 /* libalpm */
00033 #include "remove.h"
00034 #include "alpm_list.h"
00035 #include "alpm.h"
00036 #include "trans.h"
00037 #include "util.h"
00038 #include "log.h"
00039 #include "backup.h"
00040 #include "package.h"
00041 #include "db.h"
00042 #include "deps.h"
00043 #include "handle.h"
00044 #include "conflict.h"
00045 
00046 int SYMEXPORT alpm_remove_pkg(alpm_handle_t *handle, alpm_pkg_t *pkg)
00047 {
00048     const char *pkgname;
00049     alpm_trans_t *trans;
00050     alpm_pkg_t *copy;
00051 
00052     /* Sanity checks */
00053     CHECK_HANDLE(handle, return -1);
00054     ASSERT(pkg != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1));
00055     ASSERT(handle == pkg->handle, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1));
00056     trans = handle->trans;
00057     ASSERT(trans != NULL, RET_ERR(handle, ALPM_ERR_TRANS_NULL, -1));
00058     ASSERT(trans->state == STATE_INITIALIZED,
00059             RET_ERR(handle, ALPM_ERR_TRANS_NOT_INITIALIZED, -1));
00060 
00061     pkgname = pkg->name;
00062 
00063     if(_alpm_pkg_find(trans->remove, pkgname)) {
00064         RET_ERR(handle, ALPM_ERR_TRANS_DUP_TARGET, -1);
00065     }
00066 
00067     _alpm_log(handle, ALPM_LOG_DEBUG, "adding package %s to the transaction remove list\n",
00068             pkgname);
00069     if(_alpm_pkg_dup(pkg, &copy) == -1) {
00070         return -1;
00071     }
00072     trans->remove = alpm_list_add(trans->remove, copy);
00073     return 0;
00074 }
00075 
00076 static int remove_prepare_cascade(alpm_handle_t *handle, alpm_list_t *lp)
00077 {
00078     alpm_trans_t *trans = handle->trans;
00079 
00080     while(lp) {
00081         alpm_list_t *i;
00082         for(i = lp; i; i = i->next) {
00083             alpm_depmissing_t *miss = i->data;
00084             alpm_pkg_t *info = _alpm_db_get_pkgfromcache(handle->db_local, miss->target);
00085             if(info) {
00086                 alpm_pkg_t *copy;
00087                 if(!_alpm_pkg_find(trans->remove, info->name)) {
00088                     _alpm_log(handle, ALPM_LOG_DEBUG, "pulling %s in target list\n",
00089                             info->name);
00090                     if(_alpm_pkg_dup(info, &copy) == -1) {
00091                         return -1;
00092                     }
00093                     trans->remove = alpm_list_add(trans->remove, copy);
00094                 }
00095             } else {
00096                 _alpm_log(handle, ALPM_LOG_ERROR,
00097                         _("could not find %s in database -- skipping\n"), miss->target);
00098             }
00099         }
00100         alpm_list_free_inner(lp, (alpm_list_fn_free)_alpm_depmiss_free);
00101         alpm_list_free(lp);
00102         lp = alpm_checkdeps(handle, _alpm_db_get_pkgcache(handle->db_local),
00103                 trans->remove, NULL, 1);
00104     }
00105     return 0;
00106 }
00107 
00108 static void remove_prepare_keep_needed(alpm_handle_t *handle, alpm_list_t *lp)
00109 {
00110     alpm_trans_t *trans = handle->trans;
00111 
00112     /* Remove needed packages (which break dependencies) from target list */
00113     while(lp != NULL) {
00114         alpm_list_t *i;
00115         for(i = lp; i; i = i->next) {
00116             alpm_depmissing_t *miss = i->data;
00117             void *vpkg;
00118             alpm_pkg_t *pkg = _alpm_pkg_find(trans->remove, miss->causingpkg);
00119             if(pkg == NULL) {
00120                 continue;
00121             }
00122             trans->remove = alpm_list_remove(trans->remove, pkg, _alpm_pkg_cmp,
00123                     &vpkg);
00124             pkg = vpkg;
00125             if(pkg) {
00126                 _alpm_log(handle, ALPM_LOG_WARNING, _("removing %s from target list\n"),
00127                         pkg->name);
00128                 _alpm_pkg_free(pkg);
00129             }
00130         }
00131         alpm_list_free_inner(lp, (alpm_list_fn_free)_alpm_depmiss_free);
00132         alpm_list_free(lp);
00133         lp = alpm_checkdeps(handle, _alpm_db_get_pkgcache(handle->db_local),
00134                 trans->remove, NULL, 1);
00135     }
00136 }
00137 
00138 /** Transaction preparation for remove actions.
00139  * This functions takes a pointer to a alpm_list_t which will be
00140  * filled with a list of alpm_depmissing_t* objects representing
00141  * the packages blocking the transaction.
00142  * @param handle the context handle
00143  * @param data a pointer to an alpm_list_t* to fill
00144  * @return 0 on success, -1 on error
00145  */
00146 int _alpm_remove_prepare(alpm_handle_t *handle, alpm_list_t **data)
00147 {
00148     alpm_list_t *lp;
00149     alpm_trans_t *trans = handle->trans;
00150     alpm_db_t *db = handle->db_local;
00151 
00152     if((trans->flags & ALPM_TRANS_FLAG_RECURSE)
00153             && !(trans->flags & ALPM_TRANS_FLAG_CASCADE)) {
00154         _alpm_log(handle, ALPM_LOG_DEBUG, "finding removable dependencies\n");
00155         if(_alpm_recursedeps(db, trans->remove,
00156                 trans->flags & ALPM_TRANS_FLAG_RECURSEALL)) {
00157             return -1;
00158         }
00159     }
00160 
00161     if(!(trans->flags & ALPM_TRANS_FLAG_NODEPS)) {
00162         EVENT(handle, ALPM_EVENT_CHECKDEPS_START, NULL, NULL);
00163 
00164         _alpm_log(handle, ALPM_LOG_DEBUG, "looking for unsatisfied dependencies\n");
00165         lp = alpm_checkdeps(handle, _alpm_db_get_pkgcache(db), trans->remove, NULL, 1);
00166         if(lp != NULL) {
00167 
00168             if(trans->flags & ALPM_TRANS_FLAG_CASCADE) {
00169                 if(remove_prepare_cascade(handle, lp)) {
00170                     return -1;
00171                 }
00172             } else if(trans->flags & ALPM_TRANS_FLAG_UNNEEDED) {
00173                 /* Remove needed packages (which would break dependencies)
00174                  * from target list */
00175                 remove_prepare_keep_needed(handle, lp);
00176             } else {
00177                 if(data) {
00178                     *data = lp;
00179                 } else {
00180                     alpm_list_free_inner(lp, (alpm_list_fn_free)_alpm_depmiss_free);
00181                     alpm_list_free(lp);
00182                 }
00183                 RET_ERR(handle, ALPM_ERR_UNSATISFIED_DEPS, -1);
00184             }
00185         }
00186     }
00187 
00188     /* re-order w.r.t. dependencies */
00189     _alpm_log(handle, ALPM_LOG_DEBUG, "sorting by dependencies\n");
00190     lp = _alpm_sortbydeps(handle, trans->remove, 1);
00191     /* free the old alltargs */
00192     alpm_list_free(trans->remove);
00193     trans->remove = lp;
00194 
00195     /* -Rcs == -Rc then -Rs */
00196     if((trans->flags & ALPM_TRANS_FLAG_CASCADE)
00197             && (trans->flags & ALPM_TRANS_FLAG_RECURSE)) {
00198         _alpm_log(handle, ALPM_LOG_DEBUG, "finding removable dependencies\n");
00199         if(_alpm_recursedeps(db, trans->remove,
00200                     trans->flags & ALPM_TRANS_FLAG_RECURSEALL)) {
00201             return -1;
00202         }
00203     }
00204 
00205     if(!(trans->flags & ALPM_TRANS_FLAG_NODEPS)) {
00206         EVENT(handle, ALPM_EVENT_CHECKDEPS_DONE, NULL, NULL);
00207     }
00208 
00209     return 0;
00210 }
00211 
00212 static int can_remove_file(alpm_handle_t *handle, const alpm_file_t *file,
00213         alpm_list_t *skip_remove)
00214 {
00215     char filepath[PATH_MAX];
00216 
00217     if(alpm_list_find(skip_remove, file->name, _alpm_fnmatch)) {
00218         /* return success because we will never actually remove this file */
00219         return 1;
00220     }
00221 
00222     snprintf(filepath, PATH_MAX, "%s%s", handle->root, file->name);
00223     /* If we fail write permissions due to a read-only filesystem, abort.
00224      * Assume all other possible failures are covered somewhere else */
00225     if(_alpm_access(handle, NULL, filepath, W_OK) == -1) {
00226         if(errno != EACCES && errno != ETXTBSY && access(filepath, F_OK) == 0) {
00227             /* only return failure if the file ACTUALLY exists and we can't write to
00228              * it - ignore "chmod -w" simple permission failures */
00229             _alpm_log(handle, ALPM_LOG_ERROR, _("cannot remove file '%s': %s\n"),
00230                     filepath, strerror(errno));
00231             return 0;
00232         }
00233     }
00234 
00235     return 1;
00236 }
00237 
00238 /* Helper function for iterating through a package's file and deleting them
00239  * Used by _alpm_remove_commit. */
00240 static int unlink_file(alpm_handle_t *handle, alpm_pkg_t *oldpkg,
00241         alpm_pkg_t *newpkg, const alpm_file_t *fileobj, alpm_list_t *skip_remove,
00242         int nosave)
00243 {
00244     struct stat buf;
00245     char file[PATH_MAX];
00246 
00247     snprintf(file, PATH_MAX, "%s%s", handle->root, fileobj->name);
00248 
00249     /* check the remove skip list before removing the file.
00250      * see the big comment block in db_find_fileconflicts() for an
00251      * explanation. */
00252     if(alpm_list_find(skip_remove, fileobj->name, _alpm_fnmatch)) {
00253         _alpm_log(handle, ALPM_LOG_DEBUG,
00254                 "%s is in skip_remove, skipping removal\n", file);
00255         return 1;
00256     }
00257 
00258     /* we want to do a lstat here, and not a _alpm_lstat.
00259      * if a directory in the package is actually a directory symlink on the
00260      * filesystem, we want to work with the linked directory instead of the
00261      * actual symlink */
00262     if(lstat(file, &buf)) {
00263         _alpm_log(handle, ALPM_LOG_DEBUG, "file %s does not exist\n", file);
00264         return 1;
00265     }
00266 
00267     if(S_ISDIR(buf.st_mode)) {
00268         ssize_t files = _alpm_files_in_directory(handle, file, 0);
00269         /* if we have files, no need to remove the directory */
00270         if(files > 0) {
00271             _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (contains files)\n",
00272                     file);
00273         } else if(files < 0) {
00274             _alpm_log(handle, ALPM_LOG_DEBUG,
00275                     "keeping directory %s (could not count files)\n", file);
00276         } else if(newpkg && _alpm_filelist_contains(alpm_pkg_get_files(newpkg),
00277                     fileobj->name)) {
00278             _alpm_log(handle, ALPM_LOG_DEBUG,
00279                     "keeping directory %s (in new package)\n", file);
00280         } else {
00281             /* one last check- does any other package own this file? */
00282             alpm_list_t *local, *local_pkgs;
00283             int found = 0;
00284             local_pkgs = _alpm_db_get_pkgcache(handle->db_local);
00285             for(local = local_pkgs; local && !found; local = local->next) {
00286                 alpm_pkg_t *local_pkg = local->data;
00287                 alpm_filelist_t *filelist;
00288 
00289                 /* we duplicated the package when we put it in the removal list, so we
00290                  * so we can't use direct pointer comparison here. */
00291                 if(oldpkg->name_hash == local_pkg->name_hash
00292                         && strcmp(oldpkg->name, local_pkg->name) == 0) {
00293                     continue;
00294                 }
00295                 filelist = alpm_pkg_get_files(local_pkg);
00296                 if(_alpm_filelist_contains(filelist, fileobj->name)) {
00297                     _alpm_log(handle, ALPM_LOG_DEBUG,
00298                             "keeping directory %s (owned by %s)\n", file, local_pkg->name);
00299                     found = 1;
00300                 }
00301             }
00302             if(!found) {
00303                 if(rmdir(file)) {
00304                     _alpm_log(handle, ALPM_LOG_DEBUG,
00305                             "directory removal of %s failed: %s\n", file, strerror(errno));
00306                     return -1;
00307                 } else {
00308                     _alpm_log(handle, ALPM_LOG_DEBUG,
00309                             "removed directory %s (no remaining owners)\n", file);
00310                 }
00311             }
00312         }
00313     } else {
00314         /* if the file needs backup and has been modified, back it up to .pacsave */
00315         alpm_backup_t *backup = _alpm_needbackup(fileobj->name, oldpkg);
00316         if(backup) {
00317             if(nosave) {
00318                 _alpm_log(handle, ALPM_LOG_DEBUG, "transaction is set to NOSAVE, not backing up '%s'\n", file);
00319             } else {
00320                 char *filehash = alpm_compute_md5sum(file);
00321                 int cmp = filehash ? strcmp(filehash, backup->hash) : 0;
00322                 FREE(filehash);
00323                 if(cmp != 0) {
00324                     char *newpath;
00325                     size_t len = strlen(file) + 8 + 1;
00326                     MALLOC(newpath, len, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
00327                     snprintf(newpath, len, "%s.pacsave", file);
00328                     if(rename(file, newpath)) {
00329                         _alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
00330                                 file, newpath, strerror(errno));
00331                         alpm_logaction(handle, "error: could not rename %s to %s (%s)\n",
00332                                 file, newpath, strerror(errno));
00333                         free(newpath);
00334                         return -1;
00335                     }
00336                     _alpm_log(handle, ALPM_LOG_WARNING, _("%s saved as %s\n"), file, newpath);
00337                     alpm_logaction(handle, "warning: %s saved as %s\n", file, newpath);
00338                     free(newpath);
00339                     return 0;
00340                 }
00341             }
00342         }
00343 
00344         _alpm_log(handle, ALPM_LOG_DEBUG, "unlinking %s\n", file);
00345 
00346         if(unlink(file) == -1) {
00347             _alpm_log(handle, ALPM_LOG_ERROR, _("cannot remove %s (%s)\n"),
00348                     file, strerror(errno));
00349             alpm_logaction(handle, "error: cannot remove %s (%s)\n",
00350                     file, strerror(errno));
00351             return -1;
00352         }
00353     }
00354     return 0;
00355 }
00356 
00357 static int remove_package_files(alpm_handle_t *handle,
00358         alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg,
00359         size_t targ_count, size_t pkg_count)
00360 {
00361     alpm_list_t *skip_remove;
00362     alpm_filelist_t *filelist;
00363     size_t i;
00364     int err = 0;
00365     int nosave = handle->trans->flags & ALPM_TRANS_FLAG_NOSAVE;
00366 
00367     if(newpkg) {
00368         alpm_filelist_t *newfiles;
00369         alpm_list_t *b;
00370         skip_remove = alpm_list_join(
00371                 alpm_list_strdup(handle->trans->skip_remove),
00372                 alpm_list_strdup(handle->noupgrade));
00373         /* Add files in the NEW backup array to the skip_remove array
00374          * so this removal operation doesn't kill them */
00375         /* old package backup list */
00376         newfiles = alpm_pkg_get_files(newpkg);
00377         for(b = alpm_pkg_get_backup(newpkg); b; b = b->next) {
00378             const alpm_backup_t *backup = b->data;
00379             /* safety check (fix the upgrade026 pactest) */
00380             if(!_alpm_filelist_contains(newfiles, backup->name)) {
00381                 continue;
00382             }
00383             _alpm_log(handle, ALPM_LOG_DEBUG, "adding %s to the skip_remove array\n",
00384                     backup->name);
00385             skip_remove = alpm_list_add(skip_remove, strdup(backup->name));
00386         }
00387     } else {
00388         skip_remove = alpm_list_strdup(handle->trans->skip_remove);
00389     }
00390 
00391     filelist = alpm_pkg_get_files(oldpkg);
00392     for(i = 0; i < filelist->count; i++) {
00393         alpm_file_t *file = filelist->files + i;
00394         if(!can_remove_file(handle, file, skip_remove)) {
00395             _alpm_log(handle, ALPM_LOG_DEBUG,
00396                     "not removing package '%s', can't remove all files\n",
00397                     oldpkg->name);
00398             FREELIST(skip_remove);
00399             RET_ERR(handle, ALPM_ERR_PKG_CANT_REMOVE, -1);
00400         }
00401     }
00402 
00403     _alpm_log(handle, ALPM_LOG_DEBUG, "removing %zd files\n", filelist->count);
00404 
00405     if(!newpkg) {
00406         /* init progress bar, but only on true remove transactions */
00407         PROGRESS(handle, ALPM_PROGRESS_REMOVE_START, oldpkg->name, 0,
00408                 pkg_count, targ_count);
00409     }
00410 
00411     /* iterate through the list backwards, unlinking files */
00412     for(i = filelist->count; i > 0; i--) {
00413         alpm_file_t *file = filelist->files + i - 1;
00414         if(unlink_file(handle, oldpkg, newpkg, file, skip_remove, nosave) < 0) {
00415             err++;
00416         }
00417 
00418         if(!newpkg) {
00419             /* update progress bar after each file */
00420             int percent = ((filelist->count - i) * 100) / filelist->count;
00421             PROGRESS(handle, ALPM_PROGRESS_REMOVE_START, oldpkg->name,
00422                     percent, pkg_count, targ_count);
00423         }
00424     }
00425     FREELIST(skip_remove);
00426 
00427     if(!newpkg) {
00428         /* set progress to 100% after we finish unlinking files */
00429         PROGRESS(handle, ALPM_PROGRESS_REMOVE_START, oldpkg->name, 100,
00430                 pkg_count, targ_count);
00431     }
00432 
00433     return err;
00434 }
00435 
00436 int _alpm_remove_single_package(alpm_handle_t *handle,
00437         alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg,
00438         size_t targ_count, size_t pkg_count)
00439 {
00440     const char *pkgname = oldpkg->name;
00441     const char *pkgver = oldpkg->version;
00442 
00443     if(newpkg) {
00444         _alpm_log(handle, ALPM_LOG_DEBUG, "removing old package first (%s-%s)\n",
00445                 pkgname, pkgver);
00446     } else {
00447         EVENT(handle, ALPM_EVENT_REMOVE_START, oldpkg, NULL);
00448         _alpm_log(handle, ALPM_LOG_DEBUG, "removing package %s-%s\n",
00449                 pkgname, pkgver);
00450 
00451         /* run the pre-remove scriptlet if it exists  */
00452         if(alpm_pkg_has_scriptlet(oldpkg) &&
00453                 !(handle->trans->flags & ALPM_TRANS_FLAG_NOSCRIPTLET)) {
00454             char *scriptlet = _alpm_local_db_pkgpath(handle->db_local,
00455                     oldpkg, "install");
00456             _alpm_runscriptlet(handle, scriptlet, "pre_remove", pkgver, NULL, 0);
00457             free(scriptlet);
00458         }
00459     }
00460 
00461     if(!(handle->trans->flags & ALPM_TRANS_FLAG_DBONLY)) {
00462         /* TODO check returned errors if any */
00463         remove_package_files(handle, oldpkg, newpkg, targ_count, pkg_count);
00464     }
00465 
00466     /* run the post-remove script if it exists  */
00467     if(!newpkg && alpm_pkg_has_scriptlet(oldpkg) &&
00468             !(handle->trans->flags & ALPM_TRANS_FLAG_NOSCRIPTLET)) {
00469         char *scriptlet = _alpm_local_db_pkgpath(handle->db_local,
00470                 oldpkg, "install");
00471         _alpm_runscriptlet(handle, scriptlet, "post_remove", pkgver, NULL, 0);
00472         free(scriptlet);
00473     }
00474 
00475     if(!newpkg) {
00476         EVENT(handle, ALPM_EVENT_REMOVE_DONE, oldpkg, NULL);
00477     }
00478 
00479     /* remove the package from the database */
00480     _alpm_log(handle, ALPM_LOG_DEBUG, "removing database entry '%s'\n", pkgname);
00481     if(_alpm_local_db_remove(handle->db_local, oldpkg) == -1) {
00482         _alpm_log(handle, ALPM_LOG_ERROR, _("could not remove database entry %s-%s\n"),
00483                 pkgname, pkgver);
00484     }
00485     /* remove the package from the cache */
00486     if(_alpm_db_remove_pkgfromcache(handle->db_local, oldpkg) == -1) {
00487         _alpm_log(handle, ALPM_LOG_ERROR, _("could not remove entry '%s' from cache\n"),
00488                 pkgname);
00489     }
00490 
00491     return 0;
00492 }
00493 
00494 int _alpm_remove_packages(alpm_handle_t *handle, int run_ldconfig)
00495 {
00496     alpm_list_t *targ;
00497     size_t pkg_count, targ_count;
00498     alpm_trans_t *trans = handle->trans;
00499     int ret = 0;
00500 
00501     pkg_count = alpm_list_count(trans->remove);
00502     targ_count = 1;
00503 
00504     for(targ = trans->remove; targ; targ = targ->next) {
00505         alpm_pkg_t *pkg = targ->data;
00506 
00507         if(trans->state == STATE_INTERRUPTED) {
00508             return ret;
00509         }
00510 
00511         if(_alpm_remove_single_package(handle, pkg, NULL,
00512                     targ_count, pkg_count) == -1) {
00513             handle->pm_errno = ALPM_ERR_TRANS_ABORT;
00514             /* running ldconfig at this point could possibly screw system */
00515             run_ldconfig = 0;
00516             ret = -1;
00517         }
00518 
00519         targ_count++;
00520     }
00521 
00522     if(run_ldconfig) {
00523         /* run ldconfig if it exists */
00524         _alpm_ldconfig(handle);
00525     }
00526 
00527     return ret;
00528 }
00529 
00530 /* vim: set ts=2 sw=2 noet: */