libalpm/remove.c - add doxygen
[pacman-ng.git] / lib / libalpm / remove.c
blob148cb86305a407c932d24c91e0c61faac8d184b1
1 /*
2 * remove.c
4 * Copyright (c) 2006-2012 Pacman Development Team <pacman-dev@archlinux.org>
5 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
6 * Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
7 * Copyright (c) 2005 by Christian Hamar <krics@linuxforum.hu>
8 * Copyright (c) 2006 by David Kimpe <dnaku@frugalware.org>
9 * Copyright (c) 2005, 2006 by Miklos Vajna <vmiklos@frugalware.org>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <limits.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
32 /* libalpm */
33 #include "remove.h"
34 #include "alpm_list.h"
35 #include "alpm.h"
36 #include "trans.h"
37 #include "util.h"
38 #include "log.h"
39 #include "backup.h"
40 #include "package.h"
41 #include "db.h"
42 #include "deps.h"
43 #include "handle.h"
44 #include "conflict.h"
46 /**
47 * @brief Add a package removal action to the transaction.
49 * @param handle the context handle
50 * @param pkg the package to uninstall
52 * @return 0 on success, -1 on error
54 int SYMEXPORT alpm_remove_pkg(alpm_handle_t *handle, alpm_pkg_t *pkg)
56 const char *pkgname;
57 alpm_trans_t *trans;
58 alpm_pkg_t *copy;
60 /* Sanity checks */
61 CHECK_HANDLE(handle, return -1);
62 ASSERT(pkg != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1));
63 ASSERT(handle == pkg->handle, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1));
64 trans = handle->trans;
65 ASSERT(trans != NULL, RET_ERR(handle, ALPM_ERR_TRANS_NULL, -1));
66 ASSERT(trans->state == STATE_INITIALIZED,
67 RET_ERR(handle, ALPM_ERR_TRANS_NOT_INITIALIZED, -1));
69 pkgname = pkg->name;
71 if(_alpm_pkg_find(trans->remove, pkgname)) {
72 RET_ERR(handle, ALPM_ERR_TRANS_DUP_TARGET, -1);
75 _alpm_log(handle, ALPM_LOG_DEBUG, "adding package %s to the transaction remove list\n",
76 pkgname);
77 if(_alpm_pkg_dup(pkg, &copy) == -1) {
78 return -1;
80 trans->remove = alpm_list_add(trans->remove, copy);
81 return 0;
84 /**
85 * @brief Add dependencies to the removal transaction for cascading.
87 * @param handle the context handle
88 * @param lp list of missing dependencies caused by the removal transaction
90 * @return 0 on success, -1 on error
92 static int remove_prepare_cascade(alpm_handle_t *handle, alpm_list_t *lp)
94 alpm_trans_t *trans = handle->trans;
96 while(lp) {
97 alpm_list_t *i;
98 for(i = lp; i; i = i->next) {
99 alpm_depmissing_t *miss = i->data;
100 alpm_pkg_t *info = _alpm_db_get_pkgfromcache(handle->db_local, miss->target);
101 if(info) {
102 alpm_pkg_t *copy;
103 if(!_alpm_pkg_find(trans->remove, info->name)) {
104 _alpm_log(handle, ALPM_LOG_DEBUG, "pulling %s in target list\n",
105 info->name);
106 if(_alpm_pkg_dup(info, &copy) == -1) {
107 return -1;
109 trans->remove = alpm_list_add(trans->remove, copy);
111 } else {
112 _alpm_log(handle, ALPM_LOG_ERROR,
113 _("could not find %s in database -- skipping\n"), miss->target);
116 alpm_list_free_inner(lp, (alpm_list_fn_free)_alpm_depmiss_free);
117 alpm_list_free(lp);
118 lp = alpm_checkdeps(handle, _alpm_db_get_pkgcache(handle->db_local),
119 trans->remove, NULL, 1);
121 return 0;
125 * @brief Remove needed packages from the removal transaction.
127 * @param handle the context handle
128 * @param lp list of missing dependencies caused by the removal transaction
130 static void remove_prepare_keep_needed(alpm_handle_t *handle, alpm_list_t *lp)
132 alpm_trans_t *trans = handle->trans;
134 /* Remove needed packages (which break dependencies) from target list */
135 while(lp != NULL) {
136 alpm_list_t *i;
137 for(i = lp; i; i = i->next) {
138 alpm_depmissing_t *miss = i->data;
139 void *vpkg;
140 alpm_pkg_t *pkg = _alpm_pkg_find(trans->remove, miss->causingpkg);
141 if(pkg == NULL) {
142 continue;
144 trans->remove = alpm_list_remove(trans->remove, pkg, _alpm_pkg_cmp,
145 &vpkg);
146 pkg = vpkg;
147 if(pkg) {
148 _alpm_log(handle, ALPM_LOG_WARNING, _("removing %s from target list\n"),
149 pkg->name);
150 _alpm_pkg_free(pkg);
153 alpm_list_free_inner(lp, (alpm_list_fn_free)_alpm_depmiss_free);
154 alpm_list_free(lp);
155 lp = alpm_checkdeps(handle, _alpm_db_get_pkgcache(handle->db_local),
156 trans->remove, NULL, 1);
161 * @brief Transaction preparation for remove actions.
163 * This functions takes a pointer to a alpm_list_t which will be
164 * filled with a list of alpm_depmissing_t* objects representing
165 * the packages blocking the transaction.
167 * @param handle the context handle
168 * @param data a pointer to an alpm_list_t* to fill
170 * @return 0 on success, -1 on error
172 int _alpm_remove_prepare(alpm_handle_t *handle, alpm_list_t **data)
174 alpm_list_t *lp;
175 alpm_trans_t *trans = handle->trans;
176 alpm_db_t *db = handle->db_local;
178 if((trans->flags & ALPM_TRANS_FLAG_RECURSE)
179 && !(trans->flags & ALPM_TRANS_FLAG_CASCADE)) {
180 _alpm_log(handle, ALPM_LOG_DEBUG, "finding removable dependencies\n");
181 if(_alpm_recursedeps(db, trans->remove,
182 trans->flags & ALPM_TRANS_FLAG_RECURSEALL)) {
183 return -1;
187 if(!(trans->flags & ALPM_TRANS_FLAG_NODEPS)) {
188 EVENT(handle, ALPM_EVENT_CHECKDEPS_START, NULL, NULL);
190 _alpm_log(handle, ALPM_LOG_DEBUG, "looking for unsatisfied dependencies\n");
191 lp = alpm_checkdeps(handle, _alpm_db_get_pkgcache(db), trans->remove, NULL, 1);
192 if(lp != NULL) {
194 if(trans->flags & ALPM_TRANS_FLAG_CASCADE) {
195 if(remove_prepare_cascade(handle, lp)) {
196 return -1;
198 } else if(trans->flags & ALPM_TRANS_FLAG_UNNEEDED) {
199 /* Remove needed packages (which would break dependencies)
200 * from target list */
201 remove_prepare_keep_needed(handle, lp);
202 } else {
203 if(data) {
204 *data = lp;
205 } else {
206 alpm_list_free_inner(lp, (alpm_list_fn_free)_alpm_depmiss_free);
207 alpm_list_free(lp);
209 RET_ERR(handle, ALPM_ERR_UNSATISFIED_DEPS, -1);
214 /* re-order w.r.t. dependencies */
215 _alpm_log(handle, ALPM_LOG_DEBUG, "sorting by dependencies\n");
216 lp = _alpm_sortbydeps(handle, trans->remove, 1);
217 /* free the old alltargs */
218 alpm_list_free(trans->remove);
219 trans->remove = lp;
221 /* -Rcs == -Rc then -Rs */
222 if((trans->flags & ALPM_TRANS_FLAG_CASCADE)
223 && (trans->flags & ALPM_TRANS_FLAG_RECURSE)) {
224 _alpm_log(handle, ALPM_LOG_DEBUG, "finding removable dependencies\n");
225 if(_alpm_recursedeps(db, trans->remove,
226 trans->flags & ALPM_TRANS_FLAG_RECURSEALL)) {
227 return -1;
231 if(!(trans->flags & ALPM_TRANS_FLAG_NODEPS)) {
232 EVENT(handle, ALPM_EVENT_CHECKDEPS_DONE, NULL, NULL);
235 return 0;
239 * @brief Check if alpm can delete a file.
241 * @param handle the context handle
242 * @param file file to be removed
243 * @param skip_remove list of files that will not be removed
245 * @return 1 if the file can be deleted, 0 if it cannot be deleted
247 static int can_remove_file(alpm_handle_t *handle, const alpm_file_t *file,
248 alpm_list_t *skip_remove)
250 char filepath[PATH_MAX];
252 if(alpm_list_find(skip_remove, file->name, _alpm_fnmatch)) {
253 /* return success because we will never actually remove this file */
254 return 1;
257 snprintf(filepath, PATH_MAX, "%s%s", handle->root, file->name);
258 /* If we fail write permissions due to a read-only filesystem, abort.
259 * Assume all other possible failures are covered somewhere else */
260 if(_alpm_access(handle, NULL, filepath, W_OK) == -1) {
261 if(errno != EACCES && errno != ETXTBSY && access(filepath, F_OK) == 0) {
262 /* only return failure if the file ACTUALLY exists and we can't write to
263 * it - ignore "chmod -w" simple permission failures */
264 _alpm_log(handle, ALPM_LOG_ERROR, _("cannot remove file '%s': %s\n"),
265 filepath, strerror(errno));
266 return 0;
270 return 1;
274 * @brief Unlink a package file, backing it up if necessary.
276 * @note Helper function for iterating through a package's file and deleting
277 * them.
278 * @note Used by _alpm_remove_commit.
280 * @param handle the context handle
281 * @param oldpkg the package being removed
282 * @param newpkg the package replacing \a oldpkg
283 * @param fileobj file to remove
284 * @param skip_remove list of files that shouldn't be removed
285 * @param nosave whether files should be backed up
287 * @return 0 on success, -1 if there was an error unlinking the file, 1 if the
288 * file was skipped or did not exist
290 static int unlink_file(alpm_handle_t *handle, alpm_pkg_t *oldpkg,
291 alpm_pkg_t *newpkg, const alpm_file_t *fileobj, alpm_list_t *skip_remove,
292 int nosave)
294 struct stat buf;
295 char file[PATH_MAX];
297 snprintf(file, PATH_MAX, "%s%s", handle->root, fileobj->name);
299 /* check the remove skip list before removing the file.
300 * see the big comment block in db_find_fileconflicts() for an
301 * explanation. */
302 if(alpm_list_find(skip_remove, fileobj->name, _alpm_fnmatch)) {
303 _alpm_log(handle, ALPM_LOG_DEBUG,
304 "%s is in skip_remove, skipping removal\n", file);
305 return 1;
308 /* we want to do a lstat here, and not a _alpm_lstat.
309 * if a directory in the package is actually a directory symlink on the
310 * filesystem, we want to work with the linked directory instead of the
311 * actual symlink */
312 if(lstat(file, &buf)) {
313 _alpm_log(handle, ALPM_LOG_DEBUG, "file %s does not exist\n", file);
314 return 1;
317 if(S_ISDIR(buf.st_mode)) {
318 ssize_t files = _alpm_files_in_directory(handle, file, 0);
319 /* if we have files, no need to remove the directory */
320 if(files > 0) {
321 _alpm_log(handle, ALPM_LOG_DEBUG, "keeping directory %s (contains files)\n",
322 file);
323 } else if(files < 0) {
324 _alpm_log(handle, ALPM_LOG_DEBUG,
325 "keeping directory %s (could not count files)\n", file);
326 } else if(newpkg && _alpm_filelist_contains(alpm_pkg_get_files(newpkg),
327 fileobj->name)) {
328 _alpm_log(handle, ALPM_LOG_DEBUG,
329 "keeping directory %s (in new package)\n", file);
330 } else {
331 /* one last check- does any other package own this file? */
332 alpm_list_t *local, *local_pkgs;
333 int found = 0;
334 local_pkgs = _alpm_db_get_pkgcache(handle->db_local);
335 for(local = local_pkgs; local && !found; local = local->next) {
336 alpm_pkg_t *local_pkg = local->data;
337 alpm_filelist_t *filelist;
339 /* we duplicated the package when we put it in the removal list, so we
340 * so we can't use direct pointer comparison here. */
341 if(oldpkg->name_hash == local_pkg->name_hash
342 && strcmp(oldpkg->name, local_pkg->name) == 0) {
343 continue;
345 filelist = alpm_pkg_get_files(local_pkg);
346 if(_alpm_filelist_contains(filelist, fileobj->name)) {
347 _alpm_log(handle, ALPM_LOG_DEBUG,
348 "keeping directory %s (owned by %s)\n", file, local_pkg->name);
349 found = 1;
352 if(!found) {
353 if(rmdir(file)) {
354 _alpm_log(handle, ALPM_LOG_DEBUG,
355 "directory removal of %s failed: %s\n", file, strerror(errno));
356 return -1;
357 } else {
358 _alpm_log(handle, ALPM_LOG_DEBUG,
359 "removed directory %s (no remaining owners)\n", file);
363 } else {
364 /* if the file needs backup and has been modified, back it up to .pacsave */
365 alpm_backup_t *backup = _alpm_needbackup(fileobj->name, oldpkg);
366 if(backup) {
367 if(nosave) {
368 _alpm_log(handle, ALPM_LOG_DEBUG, "transaction is set to NOSAVE, not backing up '%s'\n", file);
369 } else {
370 char *filehash = alpm_compute_md5sum(file);
371 int cmp = filehash ? strcmp(filehash, backup->hash) : 0;
372 FREE(filehash);
373 if(cmp != 0) {
374 char *newpath;
375 size_t len = strlen(file) + 8 + 1;
376 MALLOC(newpath, len, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
377 snprintf(newpath, len, "%s.pacsave", file);
378 if(rename(file, newpath)) {
379 _alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
380 file, newpath, strerror(errno));
381 alpm_logaction(handle, "error: could not rename %s to %s (%s)\n",
382 file, newpath, strerror(errno));
383 free(newpath);
384 return -1;
386 _alpm_log(handle, ALPM_LOG_WARNING, _("%s saved as %s\n"), file, newpath);
387 alpm_logaction(handle, "warning: %s saved as %s\n", file, newpath);
388 free(newpath);
389 return 0;
394 _alpm_log(handle, ALPM_LOG_DEBUG, "unlinking %s\n", file);
396 if(unlink(file) == -1) {
397 _alpm_log(handle, ALPM_LOG_ERROR, _("cannot remove %s (%s)\n"),
398 file, strerror(errno));
399 alpm_logaction(handle, "error: cannot remove %s (%s)\n",
400 file, strerror(errno));
401 return -1;
404 return 0;
408 * @brief Remove a package's files, optionally skipping its replacement's
409 * files.
411 * @param handle the context handle
412 * @param oldpkg package to remove
413 * @param newpkg package to replace \a oldpkg (optional)
414 * @param targ_count current index within the transaction (1-based)
415 * @param pkg_count the number of packages affected by the transaction
417 * @return 0 on success, -1 if alpm lacks permission to delete some of the
418 * files, >0 the number of files alpm was unable to delete
420 static int remove_package_files(alpm_handle_t *handle,
421 alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg,
422 size_t targ_count, size_t pkg_count)
424 alpm_list_t *skip_remove;
425 alpm_filelist_t *filelist;
426 size_t i;
427 int err = 0;
428 int nosave = handle->trans->flags & ALPM_TRANS_FLAG_NOSAVE;
430 if(newpkg) {
431 alpm_filelist_t *newfiles;
432 alpm_list_t *b;
433 skip_remove = alpm_list_join(
434 alpm_list_strdup(handle->trans->skip_remove),
435 alpm_list_strdup(handle->noupgrade));
436 /* Add files in the NEW backup array to the skip_remove array
437 * so this removal operation doesn't kill them */
438 /* old package backup list */
439 newfiles = alpm_pkg_get_files(newpkg);
440 for(b = alpm_pkg_get_backup(newpkg); b; b = b->next) {
441 const alpm_backup_t *backup = b->data;
442 /* safety check (fix the upgrade026 pactest) */
443 if(!_alpm_filelist_contains(newfiles, backup->name)) {
444 continue;
446 _alpm_log(handle, ALPM_LOG_DEBUG, "adding %s to the skip_remove array\n",
447 backup->name);
448 skip_remove = alpm_list_add(skip_remove, strdup(backup->name));
450 } else {
451 skip_remove = alpm_list_strdup(handle->trans->skip_remove);
454 filelist = alpm_pkg_get_files(oldpkg);
455 for(i = 0; i < filelist->count; i++) {
456 alpm_file_t *file = filelist->files + i;
457 if(!can_remove_file(handle, file, skip_remove)) {
458 _alpm_log(handle, ALPM_LOG_DEBUG,
459 "not removing package '%s', can't remove all files\n",
460 oldpkg->name);
461 FREELIST(skip_remove);
462 RET_ERR(handle, ALPM_ERR_PKG_CANT_REMOVE, -1);
466 _alpm_log(handle, ALPM_LOG_DEBUG, "removing %zd files\n", filelist->count);
468 if(!newpkg) {
469 /* init progress bar, but only on true remove transactions */
470 PROGRESS(handle, ALPM_PROGRESS_REMOVE_START, oldpkg->name, 0,
471 pkg_count, targ_count);
474 /* iterate through the list backwards, unlinking files */
475 for(i = filelist->count; i > 0; i--) {
476 alpm_file_t *file = filelist->files + i - 1;
477 if(unlink_file(handle, oldpkg, newpkg, file, skip_remove, nosave) < 0) {
478 err++;
481 if(!newpkg) {
482 /* update progress bar after each file */
483 int percent = ((filelist->count - i) * 100) / filelist->count;
484 PROGRESS(handle, ALPM_PROGRESS_REMOVE_START, oldpkg->name,
485 percent, pkg_count, targ_count);
488 FREELIST(skip_remove);
490 if(!newpkg) {
491 /* set progress to 100% after we finish unlinking files */
492 PROGRESS(handle, ALPM_PROGRESS_REMOVE_START, oldpkg->name, 100,
493 pkg_count, targ_count);
496 return err;
500 * @brief Remove a package from the filesystem.
502 * @param handle the context handle
503 * @param oldpkg package to remove
504 * @param newpkg package to replace \a oldpkg (optional)
505 * @param targ_count current index within the transaction (1-based)
506 * @param pkg_count the number of packages affected by the transaction
508 * @return 0
510 int _alpm_remove_single_package(alpm_handle_t *handle,
511 alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg,
512 size_t targ_count, size_t pkg_count)
514 const char *pkgname = oldpkg->name;
515 const char *pkgver = oldpkg->version;
517 if(newpkg) {
518 _alpm_log(handle, ALPM_LOG_DEBUG, "removing old package first (%s-%s)\n",
519 pkgname, pkgver);
520 } else {
521 EVENT(handle, ALPM_EVENT_REMOVE_START, oldpkg, NULL);
522 _alpm_log(handle, ALPM_LOG_DEBUG, "removing package %s-%s\n",
523 pkgname, pkgver);
525 /* run the pre-remove scriptlet if it exists */
526 if(alpm_pkg_has_scriptlet(oldpkg) &&
527 !(handle->trans->flags & ALPM_TRANS_FLAG_NOSCRIPTLET)) {
528 char *scriptlet = _alpm_local_db_pkgpath(handle->db_local,
529 oldpkg, "install");
530 _alpm_runscriptlet(handle, scriptlet, "pre_remove", pkgver, NULL, 0);
531 free(scriptlet);
535 if(!(handle->trans->flags & ALPM_TRANS_FLAG_DBONLY)) {
536 /* TODO check returned errors if any */
537 remove_package_files(handle, oldpkg, newpkg, targ_count, pkg_count);
540 /* run the post-remove script if it exists */
541 if(!newpkg && alpm_pkg_has_scriptlet(oldpkg) &&
542 !(handle->trans->flags & ALPM_TRANS_FLAG_NOSCRIPTLET)) {
543 char *scriptlet = _alpm_local_db_pkgpath(handle->db_local,
544 oldpkg, "install");
545 _alpm_runscriptlet(handle, scriptlet, "post_remove", pkgver, NULL, 0);
546 free(scriptlet);
549 if(!newpkg) {
550 EVENT(handle, ALPM_EVENT_REMOVE_DONE, oldpkg, NULL);
553 /* remove the package from the database */
554 _alpm_log(handle, ALPM_LOG_DEBUG, "removing database entry '%s'\n", pkgname);
555 if(_alpm_local_db_remove(handle->db_local, oldpkg) == -1) {
556 _alpm_log(handle, ALPM_LOG_ERROR, _("could not remove database entry %s-%s\n"),
557 pkgname, pkgver);
559 /* remove the package from the cache */
560 if(_alpm_db_remove_pkgfromcache(handle->db_local, oldpkg) == -1) {
561 _alpm_log(handle, ALPM_LOG_ERROR, _("could not remove entry '%s' from cache\n"),
562 pkgname);
565 /* TODO: useful return values */
566 return 0;
570 * @brief Remove packages in the current transaction.
572 * @param handle the context handle
573 * @param run_ldconfig whether to run ld_config after removing the packages
575 * @return 0 on success, -1 if errors occurred while removing files
577 int _alpm_remove_packages(alpm_handle_t *handle, int run_ldconfig)
579 alpm_list_t *targ;
580 size_t pkg_count, targ_count;
581 alpm_trans_t *trans = handle->trans;
582 int ret = 0;
584 pkg_count = alpm_list_count(trans->remove);
585 targ_count = 1;
587 for(targ = trans->remove; targ; targ = targ->next) {
588 alpm_pkg_t *pkg = targ->data;
590 if(trans->state == STATE_INTERRUPTED) {
591 return ret;
594 if(_alpm_remove_single_package(handle, pkg, NULL,
595 targ_count, pkg_count) == -1) {
596 handle->pm_errno = ALPM_ERR_TRANS_ABORT;
597 /* running ldconfig at this point could possibly screw system */
598 run_ldconfig = 0;
599 ret = -1;
602 targ_count++;
605 if(run_ldconfig) {
606 /* run ldconfig if it exists */
607 _alpm_ldconfig(handle);
610 return ret;
613 /* vim: set ts=2 sw=2 noet: */