mirror of
https://github.com/moparisthebest/pacman
synced 2024-11-18 07:15:00 -05:00
5c1b83d9b1
This is done extremely crudely and is not very efficient, but it does push us down the path of being closer to right, as one additional test now passes. Signed-off-by: Dan McGee <dan@archlinux.org>
997 lines
30 KiB
C
997 lines
30 KiB
C
/*
|
|
* sync.c
|
|
*
|
|
* Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
|
|
* Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
|
|
* Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
|
|
* Copyright (c) 2005 by Christian Hamar <krics@linuxforum.hu>
|
|
* Copyright (c) 2005, 2006 by Miklos Vajna <vmiklos@frugalware.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <sys/types.h> /* off_t */
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdint.h> /* intmax_t */
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
/* libalpm */
|
|
#include "sync.h"
|
|
#include "alpm_list.h"
|
|
#include "log.h"
|
|
#include "package.h"
|
|
#include "db.h"
|
|
#include "deps.h"
|
|
#include "conflict.h"
|
|
#include "trans.h"
|
|
#include "add.h"
|
|
#include "util.h"
|
|
#include "handle.h"
|
|
#include "alpm.h"
|
|
#include "dload.h"
|
|
#include "delta.h"
|
|
#include "remove.h"
|
|
#include "diskspace.h"
|
|
#include "signing.h"
|
|
|
|
/** Check for new version of pkg in sync repos
|
|
* (only the first occurrence is considered in sync)
|
|
*/
|
|
alpm_pkg_t SYMEXPORT *alpm_sync_newversion(alpm_pkg_t *pkg, alpm_list_t *dbs_sync)
|
|
{
|
|
alpm_list_t *i;
|
|
alpm_pkg_t *spkg = NULL;
|
|
|
|
ASSERT(pkg != NULL, return NULL);
|
|
pkg->handle->pm_errno = 0;
|
|
|
|
for(i = dbs_sync; !spkg && i; i = i->next) {
|
|
spkg = _alpm_db_get_pkgfromcache(i->data, alpm_pkg_get_name(pkg));
|
|
}
|
|
|
|
if(spkg == NULL) {
|
|
_alpm_log(pkg->handle, ALPM_LOG_DEBUG, "'%s' not found in sync db => no upgrade\n",
|
|
alpm_pkg_get_name(pkg));
|
|
return NULL;
|
|
}
|
|
|
|
/* compare versions and see if spkg is an upgrade */
|
|
if(_alpm_pkg_compare_versions(spkg, pkg) > 0) {
|
|
_alpm_log(pkg->handle, ALPM_LOG_DEBUG, "new version of '%s' found (%s => %s)\n",
|
|
alpm_pkg_get_name(pkg), alpm_pkg_get_version(pkg),
|
|
alpm_pkg_get_version(spkg));
|
|
return spkg;
|
|
}
|
|
/* spkg is not an upgrade */
|
|
return NULL;
|
|
}
|
|
|
|
/** Search for packages to upgrade and add them to the transaction. */
|
|
int SYMEXPORT alpm_sync_sysupgrade(alpm_handle_t *handle, int enable_downgrade)
|
|
{
|
|
alpm_list_t *i, *j;
|
|
alpm_trans_t *trans;
|
|
|
|
CHECK_HANDLE(handle, return -1);
|
|
trans = handle->trans;
|
|
ASSERT(trans != NULL, RET_ERR(handle, ALPM_ERR_TRANS_NULL, -1));
|
|
ASSERT(trans->state == STATE_INITIALIZED, RET_ERR(handle, ALPM_ERR_TRANS_NOT_INITIALIZED, -1));
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "checking for package upgrades\n");
|
|
for(i = _alpm_db_get_pkgcache(handle->db_local); i; i = i->next) {
|
|
alpm_pkg_t *lpkg = i->data;
|
|
|
|
if(_alpm_pkg_find(trans->add, lpkg->name)) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "%s is already in the target list -- skipping\n", lpkg->name);
|
|
continue;
|
|
}
|
|
|
|
/* Search for literal then replacers in each sync database.
|
|
* If found, don't check other databases */
|
|
for(j = handle->dbs_sync; j; j = j->next) {
|
|
alpm_db_t *sdb = j->data;
|
|
/* Check sdb */
|
|
alpm_pkg_t *spkg = _alpm_db_get_pkgfromcache(sdb, lpkg->name);
|
|
if(spkg) {
|
|
/* 1. literal was found in sdb */
|
|
int cmp = _alpm_pkg_compare_versions(spkg, lpkg);
|
|
if(cmp > 0) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "new version of '%s' found (%s => %s)\n",
|
|
lpkg->name, lpkg->version, spkg->version);
|
|
/* check IgnorePkg/IgnoreGroup */
|
|
if(_alpm_pkg_should_ignore(handle, spkg)
|
|
|| _alpm_pkg_should_ignore(handle, lpkg)) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("%s: ignoring package upgrade (%s => %s)\n"),
|
|
lpkg->name, lpkg->version, spkg->version);
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "adding package %s-%s to the transaction targets\n",
|
|
spkg->name, spkg->version);
|
|
trans->add = alpm_list_add(trans->add, spkg);
|
|
}
|
|
} else if(cmp < 0) {
|
|
if(enable_downgrade) {
|
|
/* check IgnorePkg/IgnoreGroup */
|
|
if(_alpm_pkg_should_ignore(handle, spkg)
|
|
|| _alpm_pkg_should_ignore(handle, lpkg)) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("%s: ignoring package downgrade (%s => %s)\n"),
|
|
lpkg->name, lpkg->version, spkg->version);
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("%s: downgrading from version %s to version %s\n"),
|
|
lpkg->name, lpkg->version, spkg->version);
|
|
trans->add = alpm_list_add(trans->add, spkg);
|
|
}
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("%s: local (%s) is newer than %s (%s)\n"),
|
|
lpkg->name, lpkg->version, sdb->treename, spkg->version);
|
|
}
|
|
}
|
|
/* jump to next local package */
|
|
break;
|
|
} else {
|
|
/* 2. search for replacers in sdb */
|
|
int found = 0;
|
|
alpm_list_t *k, *l;
|
|
for(k = _alpm_db_get_pkgcache(sdb); k; k = k->next) {
|
|
spkg = k->data;
|
|
for(l = alpm_pkg_get_replaces(spkg); l; l = l->next) {
|
|
const char *replace = l->data;
|
|
alpm_depend_t *parsed_replace = _alpm_splitdep(replace);
|
|
if(_alpm_depcmp(lpkg, parsed_replace)) {
|
|
found = 1;
|
|
_alpm_dep_free(parsed_replace);
|
|
break;
|
|
}
|
|
_alpm_dep_free(parsed_replace);
|
|
}
|
|
if(found) {
|
|
/* check IgnorePkg/IgnoreGroup */
|
|
if(_alpm_pkg_should_ignore(handle, spkg)
|
|
|| _alpm_pkg_should_ignore(handle, lpkg)) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("ignoring package replacement (%s-%s => %s-%s)\n"),
|
|
lpkg->name, lpkg->version, spkg->name, spkg->version);
|
|
continue;
|
|
}
|
|
|
|
int doreplace = 0;
|
|
QUESTION(trans, ALPM_TRANS_CONV_REPLACE_PKG, lpkg, spkg, sdb->treename, &doreplace);
|
|
if(!doreplace) {
|
|
continue;
|
|
}
|
|
|
|
/* If spkg is already in the target list, we append lpkg to spkg's
|
|
* removes list */
|
|
alpm_pkg_t *tpkg = _alpm_pkg_find(trans->add, spkg->name);
|
|
if(tpkg) {
|
|
/* sanity check, multiple repos can contain spkg->name */
|
|
if(tpkg->origin_data.db != sdb) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("cannot replace %s by %s\n"),
|
|
lpkg->name, spkg->name);
|
|
continue;
|
|
}
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "appending %s to the removes list of %s\n",
|
|
lpkg->name, tpkg->name);
|
|
tpkg->removes = alpm_list_add(tpkg->removes, lpkg);
|
|
/* check the to-be-replaced package's reason field */
|
|
if(alpm_pkg_get_reason(lpkg) == ALPM_PKG_REASON_EXPLICIT) {
|
|
tpkg->reason = ALPM_PKG_REASON_EXPLICIT;
|
|
}
|
|
} else {
|
|
/* add spkg to the target list */
|
|
/* copy over reason */
|
|
spkg->reason = alpm_pkg_get_reason(lpkg);
|
|
spkg->removes = alpm_list_add(NULL, lpkg);
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "adding package %s-%s to the transaction targets\n",
|
|
spkg->name, spkg->version);
|
|
trans->add = alpm_list_add(trans->add, spkg);
|
|
}
|
|
}
|
|
}
|
|
if(found) {
|
|
/* jump to next local package */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Find group members across a list of databases.
|
|
* If a member exists in several databases, only the first database is used.
|
|
* IgnorePkg is also handled.
|
|
* @param dbs the list of alpm_db_t *
|
|
* @pram name the name of the group
|
|
* @return the list of alpm_pkg_t * (caller is responsible for alpm_list_free)
|
|
*/
|
|
alpm_list_t SYMEXPORT *alpm_find_group_pkgs(alpm_list_t *dbs,
|
|
const char *name)
|
|
{
|
|
alpm_list_t *i, *j, *pkgs = NULL, *ignorelist = NULL;
|
|
|
|
for(i = dbs; i; i = i->next) {
|
|
alpm_db_t *db = i->data;
|
|
alpm_group_t *grp = alpm_db_readgroup(db, name);
|
|
|
|
if(!grp)
|
|
continue;
|
|
|
|
for(j = grp->packages; j; j = j->next) {
|
|
alpm_pkg_t *pkg = j->data;
|
|
|
|
if(_alpm_pkg_find(ignorelist, alpm_pkg_get_name(pkg))) {
|
|
continue;
|
|
}
|
|
if(_alpm_pkg_should_ignore(db->handle, pkg)) {
|
|
ignorelist = alpm_list_add(ignorelist, pkg);
|
|
int install = 0;
|
|
QUESTION(db->handle->trans, ALPM_TRANS_CONV_INSTALL_IGNOREPKG, pkg,
|
|
NULL, NULL, &install);
|
|
if(!install)
|
|
continue;
|
|
}
|
|
if(!_alpm_pkg_find(pkgs, alpm_pkg_get_name(pkg))) {
|
|
pkgs = alpm_list_add(pkgs, pkg);
|
|
}
|
|
}
|
|
}
|
|
alpm_list_free(ignorelist);
|
|
return pkgs;
|
|
}
|
|
|
|
/** Compute the size of the files that will be downloaded to install a
|
|
* package.
|
|
* @param newpkg the new package to upgrade to
|
|
*/
|
|
static int compute_download_size(alpm_pkg_t *newpkg)
|
|
{
|
|
const char *fname;
|
|
char *fpath;
|
|
off_t size = 0;
|
|
alpm_handle_t *handle = newpkg->handle;
|
|
|
|
if(newpkg->origin != PKG_FROM_SYNCDB) {
|
|
newpkg->infolevel |= INFRQ_DSIZE;
|
|
newpkg->download_size = 0;
|
|
return 0;
|
|
}
|
|
|
|
fname = alpm_pkg_get_filename(newpkg);
|
|
ASSERT(fname != NULL, RET_ERR(handle, ALPM_ERR_PKG_INVALID_NAME, -1));
|
|
fpath = _alpm_filecache_find(handle, fname);
|
|
|
|
if(fpath) {
|
|
FREE(fpath);
|
|
size = 0;
|
|
} else if(handle->usedelta) {
|
|
off_t dltsize;
|
|
off_t pkgsize = alpm_pkg_get_size(newpkg);
|
|
|
|
dltsize = _alpm_shortest_delta_path(handle,
|
|
alpm_pkg_get_deltas(newpkg),
|
|
alpm_pkg_get_filename(newpkg),
|
|
&newpkg->delta_path);
|
|
|
|
if(newpkg->delta_path && (dltsize < pkgsize * MAX_DELTA_RATIO)) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "using delta size\n");
|
|
size = dltsize;
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "using package size\n");
|
|
size = alpm_pkg_get_size(newpkg);
|
|
alpm_list_free(newpkg->delta_path);
|
|
newpkg->delta_path = NULL;
|
|
}
|
|
} else {
|
|
size = alpm_pkg_get_size(newpkg);
|
|
}
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "setting download size %jd for pkg %s\n",
|
|
(intmax_t)size, alpm_pkg_get_name(newpkg));
|
|
|
|
newpkg->infolevel |= INFRQ_DSIZE;
|
|
newpkg->download_size = size;
|
|
return 0;
|
|
}
|
|
|
|
int _alpm_sync_prepare(alpm_handle_t *handle, alpm_list_t **data)
|
|
{
|
|
alpm_list_t *i, *j;
|
|
alpm_list_t *deps = NULL;
|
|
alpm_list_t *unresolvable = NULL;
|
|
alpm_list_t *remove = NULL;
|
|
int ret = 0;
|
|
alpm_trans_t *trans = handle->trans;
|
|
|
|
if(data) {
|
|
*data = NULL;
|
|
}
|
|
|
|
/* ensure all sync database are valid since we will be using them */
|
|
for(i = handle->dbs_sync; i; i = i->next) {
|
|
const alpm_db_t *db = i->data;
|
|
if(!(db->status & DB_STATUS_VALID)) {
|
|
RET_ERR(handle, ALPM_ERR_DB_INVALID, -1);
|
|
}
|
|
}
|
|
|
|
if(!(trans->flags & ALPM_TRANS_FLAG_NODEPS)) {
|
|
alpm_list_t *resolved = NULL; /* target list after resolvedeps */
|
|
|
|
/* Build up list by repeatedly resolving each transaction package */
|
|
/* Resolve targets dependencies */
|
|
EVENT(trans, ALPM_TRANS_EVT_RESOLVEDEPS_START, NULL, NULL);
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "resolving target's dependencies\n");
|
|
|
|
/* build remove list for resolvedeps */
|
|
for(i = trans->add; i; i = i->next) {
|
|
alpm_pkg_t *spkg = i->data;
|
|
for(j = spkg->removes; j; j = j->next) {
|
|
remove = alpm_list_add(remove, j->data);
|
|
}
|
|
}
|
|
|
|
/* Compute the fake local database for resolvedeps (partial fix for the
|
|
* phonon/qt issue) */
|
|
alpm_list_t *localpkgs = alpm_list_diff(_alpm_db_get_pkgcache(handle->db_local),
|
|
trans->add, _alpm_pkg_cmp);
|
|
|
|
/* Resolve packages in the transaction one at a time, in addition
|
|
building up a list of packages which could not be resolved. */
|
|
for(i = trans->add; i; i = i->next) {
|
|
alpm_pkg_t *pkg = i->data;
|
|
if(_alpm_resolvedeps(handle, localpkgs, pkg, trans->add,
|
|
&resolved, remove, data) == -1) {
|
|
unresolvable = alpm_list_add(unresolvable, pkg);
|
|
}
|
|
/* Else, [resolved] now additionally contains [pkg] and all of its
|
|
dependencies not already on the list */
|
|
}
|
|
alpm_list_free(localpkgs);
|
|
|
|
/* If there were unresolvable top-level packages, prompt the user to
|
|
see if they'd like to ignore them rather than failing the sync */
|
|
if(unresolvable != NULL) {
|
|
int remove_unresolvable = 0;
|
|
QUESTION(trans, ALPM_TRANS_CONV_REMOVE_PKGS, unresolvable,
|
|
NULL, NULL, &remove_unresolvable);
|
|
if(remove_unresolvable) {
|
|
/* User wants to remove the unresolvable packages from the
|
|
transaction. The packages will be removed from the actual
|
|
transaction when the transaction packages are replaced with a
|
|
dependency-reordered list below */
|
|
handle->pm_errno = 0; /* pm_errno was set by resolvedeps */
|
|
if(data) {
|
|
alpm_list_free_inner(*data, (alpm_list_fn_free)_alpm_depmiss_free);
|
|
alpm_list_free(*data);
|
|
*data = NULL;
|
|
}
|
|
} else {
|
|
/* pm_errno is set by resolvedeps */
|
|
alpm_list_free(resolved);
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Set DEPEND reason for pulled packages */
|
|
for(i = resolved; i; i = i->next) {
|
|
alpm_pkg_t *pkg = i->data;
|
|
if(!_alpm_pkg_find(trans->add, pkg->name)) {
|
|
pkg->reason = ALPM_PKG_REASON_DEPEND;
|
|
}
|
|
}
|
|
|
|
/* Unresolvable packages will be removed from the target list, so
|
|
we free the transaction specific fields */
|
|
alpm_list_free_inner(unresolvable, (alpm_list_fn_free)_alpm_pkg_free_trans);
|
|
|
|
/* re-order w.r.t. dependencies */
|
|
alpm_list_free(trans->add);
|
|
trans->add = _alpm_sortbydeps(handle, resolved, 0);
|
|
alpm_list_free(resolved);
|
|
|
|
EVENT(trans, ALPM_TRANS_EVT_RESOLVEDEPS_DONE, NULL, NULL);
|
|
}
|
|
|
|
if(!(trans->flags & ALPM_TRANS_FLAG_NOCONFLICTS)) {
|
|
/* check for inter-conflicts and whatnot */
|
|
EVENT(trans, ALPM_TRANS_EVT_INTERCONFLICTS_START, NULL, NULL);
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "looking for conflicts\n");
|
|
|
|
/* 1. check for conflicts in the target list */
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "check targets vs targets\n");
|
|
deps = _alpm_innerconflicts(handle, trans->add);
|
|
|
|
for(i = deps; i; i = i->next) {
|
|
alpm_conflict_t *conflict = i->data;
|
|
alpm_pkg_t *rsync, *sync, *sync1, *sync2;
|
|
|
|
/* have we already removed one of the conflicting targets? */
|
|
sync1 = _alpm_pkg_find(trans->add, conflict->package1);
|
|
sync2 = _alpm_pkg_find(trans->add, conflict->package2);
|
|
if(!sync1 || !sync2) {
|
|
continue;
|
|
}
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "conflicting packages in the sync list: '%s' <-> '%s'\n",
|
|
conflict->package1, conflict->package2);
|
|
|
|
/* if sync1 provides sync2, we remove sync2 from the targets, and vice versa */
|
|
alpm_depend_t *dep1 = _alpm_splitdep(conflict->package1);
|
|
alpm_depend_t *dep2 = _alpm_splitdep(conflict->package2);
|
|
if(_alpm_depcmp(sync1, dep2)) {
|
|
rsync = sync2;
|
|
sync = sync1;
|
|
} else if(_alpm_depcmp(sync2, dep1)) {
|
|
rsync = sync1;
|
|
sync = sync2;
|
|
} else {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("unresolvable package conflicts detected\n"));
|
|
handle->pm_errno = ALPM_ERR_CONFLICTING_DEPS;
|
|
ret = -1;
|
|
if(data) {
|
|
alpm_conflict_t *newconflict = _alpm_conflict_dup(conflict);
|
|
if(newconflict) {
|
|
*data = alpm_list_add(*data, newconflict);
|
|
}
|
|
}
|
|
alpm_list_free_inner(deps, (alpm_list_fn_free)_alpm_conflict_free);
|
|
alpm_list_free(deps);
|
|
_alpm_dep_free(dep1);
|
|
_alpm_dep_free(dep2);
|
|
goto cleanup;
|
|
}
|
|
_alpm_dep_free(dep1);
|
|
_alpm_dep_free(dep2);
|
|
|
|
/* Prints warning */
|
|
_alpm_log(handle, ALPM_LOG_WARNING,
|
|
_("removing '%s' from target list because it conflicts with '%s'\n"),
|
|
rsync->name, sync->name);
|
|
trans->add = alpm_list_remove(trans->add, rsync, _alpm_pkg_cmp, NULL);
|
|
_alpm_pkg_free_trans(rsync); /* rsync is not transaction target anymore */
|
|
continue;
|
|
}
|
|
|
|
alpm_list_free_inner(deps, (alpm_list_fn_free)_alpm_conflict_free);
|
|
alpm_list_free(deps);
|
|
deps = NULL;
|
|
|
|
/* 2. we check for target vs db conflicts (and resolve)*/
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "check targets vs db and db vs targets\n");
|
|
deps = _alpm_outerconflicts(handle->db_local, trans->add);
|
|
|
|
for(i = deps; i; i = i->next) {
|
|
alpm_conflict_t *conflict = i->data;
|
|
|
|
/* if conflict->package2 (the local package) is not elected for removal,
|
|
we ask the user */
|
|
int found = 0;
|
|
for(j = trans->add; j && !found; j = j->next) {
|
|
alpm_pkg_t *spkg = j->data;
|
|
if(_alpm_pkg_find(spkg->removes, conflict->package2)) {
|
|
found = 1;
|
|
}
|
|
}
|
|
if(found) {
|
|
continue;
|
|
}
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "package '%s' conflicts with '%s'\n",
|
|
conflict->package1, conflict->package2);
|
|
|
|
alpm_pkg_t *sync = _alpm_pkg_find(trans->add, conflict->package1);
|
|
alpm_pkg_t *local = _alpm_db_get_pkgfromcache(handle->db_local, conflict->package2);
|
|
int doremove = 0;
|
|
QUESTION(trans, ALPM_TRANS_CONV_CONFLICT_PKG, conflict->package1,
|
|
conflict->package2, conflict->reason, &doremove);
|
|
if(doremove) {
|
|
/* append to the removes list */
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "electing '%s' for removal\n", conflict->package2);
|
|
sync->removes = alpm_list_add(sync->removes, local);
|
|
} else { /* abort */
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("unresolvable package conflicts detected\n"));
|
|
handle->pm_errno = ALPM_ERR_CONFLICTING_DEPS;
|
|
ret = -1;
|
|
if(data) {
|
|
alpm_conflict_t *newconflict = _alpm_conflict_dup(conflict);
|
|
if(newconflict) {
|
|
*data = alpm_list_add(*data, newconflict);
|
|
}
|
|
}
|
|
alpm_list_free_inner(deps, (alpm_list_fn_free)_alpm_conflict_free);
|
|
alpm_list_free(deps);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
EVENT(trans, ALPM_TRANS_EVT_INTERCONFLICTS_DONE, NULL, NULL);
|
|
alpm_list_free_inner(deps, (alpm_list_fn_free)_alpm_conflict_free);
|
|
alpm_list_free(deps);
|
|
}
|
|
|
|
/* Build trans->remove list */
|
|
for(i = trans->add; i; i = i->next) {
|
|
alpm_pkg_t *spkg = i->data;
|
|
for(j = spkg->removes; j; j = j->next) {
|
|
alpm_pkg_t *rpkg = j->data;
|
|
if(!_alpm_pkg_find(trans->remove, rpkg->name)) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "adding '%s' to remove list\n", rpkg->name);
|
|
trans->remove = alpm_list_add(trans->remove, _alpm_pkg_dup(rpkg));
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!(trans->flags & ALPM_TRANS_FLAG_NODEPS)) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "checking dependencies\n");
|
|
deps = alpm_checkdeps(handle, _alpm_db_get_pkgcache(handle->db_local),
|
|
trans->remove, trans->add, 1);
|
|
if(deps) {
|
|
handle->pm_errno = ALPM_ERR_UNSATISFIED_DEPS;
|
|
ret = -1;
|
|
if(data) {
|
|
*data = deps;
|
|
} else {
|
|
alpm_list_free_inner(deps, (alpm_list_fn_free)_alpm_depmiss_free);
|
|
alpm_list_free(deps);
|
|
}
|
|
goto cleanup;
|
|
}
|
|
}
|
|
for(i = trans->add; i; i = i->next) {
|
|
/* update download size field */
|
|
alpm_pkg_t *spkg = i->data;
|
|
if(compute_download_size(spkg) != 0) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
alpm_list_free(unresolvable);
|
|
alpm_list_free(remove);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Returns the size of the files that will be downloaded to install a
|
|
* package.
|
|
* @param newpkg the new package to upgrade to
|
|
* @return the size of the download
|
|
*/
|
|
off_t SYMEXPORT alpm_pkg_download_size(alpm_pkg_t *newpkg)
|
|
{
|
|
if(!(newpkg->infolevel & INFRQ_DSIZE)) {
|
|
compute_download_size(newpkg);
|
|
}
|
|
return newpkg->download_size;
|
|
}
|
|
|
|
static int endswith(const char *filename, const char *extension)
|
|
{
|
|
const char *s = filename + strlen(filename) - strlen(extension);
|
|
return strcmp(s, extension) == 0;
|
|
}
|
|
|
|
/** Applies delta files to create an upgraded package file.
|
|
*
|
|
* All intermediate files are deleted, leaving only the starting and
|
|
* ending package files.
|
|
*
|
|
* @param handle the context handle
|
|
*
|
|
* @return 0 if all delta files were able to be applied, 1 otherwise.
|
|
*/
|
|
static int apply_deltas(alpm_handle_t *handle)
|
|
{
|
|
alpm_list_t *i;
|
|
int ret = 0;
|
|
const char *cachedir = _alpm_filecache_setup(handle);
|
|
alpm_trans_t *trans = handle->trans;
|
|
|
|
for(i = trans->add; i; i = i->next) {
|
|
alpm_pkg_t *spkg = i->data;
|
|
alpm_list_t *delta_path = spkg->delta_path;
|
|
alpm_list_t *dlts = NULL;
|
|
|
|
if(!delta_path) {
|
|
continue;
|
|
}
|
|
|
|
for(dlts = delta_path; dlts; dlts = dlts->next) {
|
|
alpm_delta_t *d = dlts->data;
|
|
char *delta, *from, *to;
|
|
char command[PATH_MAX];
|
|
size_t len = 0;
|
|
|
|
delta = _alpm_filecache_find(handle, d->delta);
|
|
/* the initial package might be in a different cachedir */
|
|
if(dlts == delta_path) {
|
|
from = _alpm_filecache_find(handle, d->from);
|
|
} else {
|
|
/* len = cachedir len + from len + '/' + null */
|
|
len = strlen(cachedir) + strlen(d->from) + 2;
|
|
CALLOC(from, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, 1));
|
|
snprintf(from, len, "%s/%s", cachedir, d->from);
|
|
}
|
|
len = strlen(cachedir) + strlen(d->to) + 2;
|
|
CALLOC(to, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, 1));
|
|
snprintf(to, len, "%s/%s", cachedir, d->to);
|
|
|
|
/* build the patch command */
|
|
if(endswith(to, ".gz")) {
|
|
/* special handling for gzip : we disable timestamp with -n option */
|
|
snprintf(command, PATH_MAX, "xdelta3 -d -q -R -c -s %s %s | gzip -n > %s", from, delta, to);
|
|
} else {
|
|
snprintf(command, PATH_MAX, "xdelta3 -d -q -s %s %s %s", from, delta, to);
|
|
}
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "command: %s\n", command);
|
|
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_PATCH_START, d->to, d->delta);
|
|
|
|
int retval = system(command);
|
|
if(retval == 0) {
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_PATCH_DONE, NULL, NULL);
|
|
|
|
/* delete the delta file */
|
|
unlink(delta);
|
|
|
|
/* Delete the 'from' package but only if it is an intermediate
|
|
* package. The starting 'from' package should be kept, just
|
|
* as if deltas were not used. */
|
|
if(dlts != delta_path) {
|
|
unlink(from);
|
|
}
|
|
}
|
|
FREE(from);
|
|
FREE(to);
|
|
FREE(delta);
|
|
|
|
if(retval != 0) {
|
|
/* one delta failed for this package, cancel the remaining ones */
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_PATCH_FAILED, NULL, NULL);
|
|
handle->pm_errno = ALPM_ERR_DLT_PATCHFAILED;
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Compares the md5sum of a file to the expected value.
|
|
*
|
|
* If the md5sum does not match, the user is asked whether the file
|
|
* should be deleted.
|
|
*
|
|
* @param trans the transaction
|
|
* @param filename the absolute path of the file to test
|
|
* @param reason an error code indicating the reason for package invalidity
|
|
*
|
|
* @return 1 if file was removed, 0 otherwise
|
|
*/
|
|
static int prompt_to_delete(alpm_trans_t *trans, const char *filepath,
|
|
enum _alpm_errno_t reason)
|
|
{
|
|
int doremove = 0;
|
|
QUESTION(trans, ALPM_TRANS_CONV_CORRUPTED_PKG, (char *)filepath,
|
|
&reason, NULL, &doremove);
|
|
if(doremove) {
|
|
unlink(filepath);
|
|
}
|
|
return doremove;
|
|
}
|
|
|
|
static int validate_deltas(alpm_handle_t *handle, alpm_list_t *deltas,
|
|
alpm_list_t **data)
|
|
{
|
|
int errors = 0, ret = 0;
|
|
alpm_list_t *i;
|
|
alpm_trans_t *trans = handle->trans;
|
|
|
|
if(!deltas) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check integrity of deltas */
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_INTEGRITY_START, NULL, NULL);
|
|
|
|
for(i = deltas; i; i = i->next) {
|
|
alpm_delta_t *d = alpm_list_getdata(i);
|
|
char *filepath = _alpm_filecache_find(handle, d->delta);
|
|
|
|
ret = _alpm_test_md5sum(filepath, d->delta_md5);
|
|
if(ret != 0) {
|
|
prompt_to_delete(trans, filepath, ALPM_ERR_DLT_INVALID);
|
|
errors++;
|
|
*data = alpm_list_add(*data, strdup(d->delta));
|
|
}
|
|
FREE(filepath);
|
|
}
|
|
if(errors) {
|
|
handle->pm_errno = ALPM_ERR_DLT_INVALID;
|
|
return -1;
|
|
}
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_INTEGRITY_DONE, NULL, NULL);
|
|
|
|
/* Use the deltas to generate the packages */
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_PATCHES_START, NULL, NULL);
|
|
ret = apply_deltas(handle);
|
|
EVENT(trans, ALPM_TRANS_EVT_DELTA_PATCHES_DONE, NULL, NULL);
|
|
return ret;
|
|
}
|
|
|
|
static int download_files(alpm_handle_t *handle, alpm_list_t **deltas)
|
|
{
|
|
const char *cachedir;
|
|
alpm_list_t *i, *j;
|
|
alpm_list_t *files = NULL;
|
|
int errors = 0;
|
|
|
|
cachedir = _alpm_filecache_setup(handle);
|
|
handle->trans->state = STATE_DOWNLOADING;
|
|
|
|
/* Total progress - figure out the total download size if required to
|
|
* pass to the callback. This function is called once, and it is up to the
|
|
* frontend to compute incremental progress. */
|
|
if(handle->totaldlcb) {
|
|
off_t total_size = (off_t)0;
|
|
/* sum up the download size for each package and store total */
|
|
for(i = handle->trans->add; i; i = i->next) {
|
|
alpm_pkg_t *spkg = i->data;
|
|
total_size += spkg->download_size;
|
|
}
|
|
handle->totaldlcb(total_size);
|
|
}
|
|
|
|
/* group sync records by repository and download */
|
|
for(i = handle->dbs_sync; i; i = i->next) {
|
|
alpm_db_t *current = i->data;
|
|
|
|
for(j = handle->trans->add; j; j = j->next) {
|
|
alpm_pkg_t *spkg = j->data;
|
|
|
|
if(spkg->origin != PKG_FROM_FILE && current == spkg->origin_data.db) {
|
|
alpm_list_t *delta_path = spkg->delta_path;
|
|
if(delta_path) {
|
|
/* using deltas */
|
|
alpm_list_t *dlts;
|
|
for(dlts = delta_path; dlts; dlts = dlts->next) {
|
|
alpm_delta_t *delta = dlts->data;
|
|
if(delta->download_size != 0) {
|
|
struct dload_payload *dpayload;
|
|
|
|
CALLOC(dpayload, 1, sizeof(*dpayload), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
STRDUP(dpayload->filename, delta->delta, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
dpayload->max_size = delta->download_size;
|
|
|
|
files = alpm_list_add(files, dpayload);
|
|
}
|
|
/* keep a list of all the delta files for md5sums */
|
|
*deltas = alpm_list_add(*deltas, delta);
|
|
}
|
|
|
|
} else if(spkg->download_size != 0) {
|
|
struct dload_payload *payload;
|
|
|
|
ASSERT(spkg->filename != NULL, RET_ERR(handle, ALPM_ERR_PKG_INVALID_NAME, -1));
|
|
CALLOC(payload, 1, sizeof(*payload), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
STRDUP(payload->filename, spkg->filename, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
payload->max_size = alpm_pkg_get_size(spkg);
|
|
|
|
files = alpm_list_add(files, payload);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if(files) {
|
|
EVENT(handle->trans, ALPM_TRANS_EVT_RETRIEVE_START, current->treename, NULL);
|
|
for(j = files; j; j = j->next) {
|
|
struct dload_payload *payload = j->data;
|
|
alpm_list_t *server;
|
|
int ret = -1;
|
|
for(server = current->servers; server; server = server->next) {
|
|
const char *server_url = server->data;
|
|
size_t len;
|
|
|
|
/* print server + filename into a buffer */
|
|
len = strlen(server_url) + strlen(payload->filename) + 2;
|
|
CALLOC(payload->fileurl, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
|
|
snprintf(payload->fileurl, len, "%s/%s", server_url, payload->filename);
|
|
payload->handle = handle;
|
|
payload->allow_resume = 1;
|
|
|
|
ret = _alpm_download(payload, cachedir, NULL);
|
|
if(ret != -1) {
|
|
break;
|
|
}
|
|
}
|
|
if(ret == -1) {
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
alpm_list_free_inner(files, (alpm_list_fn_free)_alpm_dload_payload_free);
|
|
alpm_list_free(files);
|
|
files = NULL;
|
|
if(errors) {
|
|
_alpm_log(handle, ALPM_LOG_WARNING, _("failed to retrieve some files from %s\n"),
|
|
current->treename);
|
|
if(handle->pm_errno == 0) {
|
|
handle->pm_errno = ALPM_ERR_RETRIEVE;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(j = handle->trans->add; j; j = j->next) {
|
|
alpm_pkg_t *pkg = j->data;
|
|
pkg->infolevel &= ~INFRQ_DSIZE;
|
|
pkg->download_size = 0;
|
|
}
|
|
|
|
/* clear out value to let callback know we are done */
|
|
if(handle->totaldlcb) {
|
|
handle->totaldlcb(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data)
|
|
{
|
|
alpm_list_t *i;
|
|
alpm_list_t *deltas = NULL;
|
|
size_t numtargs, current = 0, replaces = 0;
|
|
int errors;
|
|
alpm_trans_t *trans = handle->trans;
|
|
|
|
if(download_files(handle, &deltas)) {
|
|
alpm_list_free(deltas);
|
|
return -1;
|
|
}
|
|
|
|
if(validate_deltas(handle, deltas, data)) {
|
|
alpm_list_free(deltas);
|
|
return -1;
|
|
}
|
|
alpm_list_free(deltas);
|
|
|
|
/* Check integrity of packages */
|
|
numtargs = alpm_list_count(trans->add);
|
|
EVENT(trans, ALPM_TRANS_EVT_INTEGRITY_START, NULL, NULL);
|
|
|
|
errors = 0;
|
|
|
|
for(i = trans->add; i; i = i->next, current++) {
|
|
alpm_pkg_t *spkg = i->data;
|
|
int percent = (current * 100) / numtargs;
|
|
const char *filename;
|
|
char *filepath;
|
|
alpm_siglevel_t level;
|
|
|
|
PROGRESS(trans, ALPM_TRANS_PROGRESS_INTEGRITY_START, "", percent,
|
|
numtargs, current);
|
|
if(spkg->origin == PKG_FROM_FILE) {
|
|
continue; /* pkg_load() has been already called, this package is valid */
|
|
}
|
|
|
|
filename = alpm_pkg_get_filename(spkg);
|
|
filepath = _alpm_filecache_find(handle, filename);
|
|
alpm_db_t *sdb = alpm_pkg_get_db(spkg);
|
|
level = alpm_db_get_siglevel(sdb);
|
|
|
|
/* load the package file and replace pkgcache entry with it in the target list */
|
|
/* TODO: alpm_pkg_get_db() will not work on this target anymore */
|
|
_alpm_log(handle, ALPM_LOG_DEBUG,
|
|
"replacing pkgcache entry with package file for target %s\n",
|
|
spkg->name);
|
|
alpm_pkg_t *pkgfile =_alpm_pkg_load_internal(handle, filepath, 1, spkg->md5sum,
|
|
spkg->base64_sig, level);
|
|
if(!pkgfile) {
|
|
prompt_to_delete(trans, filepath, handle->pm_errno);
|
|
errors++;
|
|
*data = alpm_list_add(*data, strdup(filename));
|
|
FREE(filepath);
|
|
continue;
|
|
}
|
|
FREE(filepath);
|
|
pkgfile->reason = spkg->reason; /* copy over install reason */
|
|
i->data = pkgfile;
|
|
_alpm_pkg_free_trans(spkg); /* spkg has been removed from the target list */
|
|
}
|
|
|
|
PROGRESS(trans, ALPM_TRANS_PROGRESS_INTEGRITY_START, "", 100,
|
|
numtargs, current);
|
|
EVENT(trans, ALPM_TRANS_EVT_INTEGRITY_DONE, NULL, NULL);
|
|
|
|
|
|
if(errors) {
|
|
if(!handle->pm_errno) {
|
|
RET_ERR(handle, ALPM_ERR_PKG_INVALID, -1);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if(trans->flags & ALPM_TRANS_FLAG_DOWNLOADONLY) {
|
|
return 0;
|
|
}
|
|
|
|
trans->state = STATE_COMMITING;
|
|
|
|
replaces = alpm_list_count(trans->remove);
|
|
|
|
/* fileconflict check */
|
|
if(!(trans->flags & ALPM_TRANS_FLAG_FORCE)) {
|
|
EVENT(trans, ALPM_TRANS_EVT_FILECONFLICTS_START, NULL, NULL);
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "looking for file conflicts\n");
|
|
alpm_list_t *conflict = _alpm_db_find_fileconflicts(handle,
|
|
trans->add, trans->remove);
|
|
if(conflict) {
|
|
if(data) {
|
|
*data = conflict;
|
|
} else {
|
|
alpm_list_free_inner(conflict, (alpm_list_fn_free)_alpm_fileconflict_free);
|
|
alpm_list_free(conflict);
|
|
}
|
|
RET_ERR(handle, ALPM_ERR_FILE_CONFLICTS, -1);
|
|
}
|
|
|
|
EVENT(trans, ALPM_TRANS_EVT_FILECONFLICTS_DONE, NULL, NULL);
|
|
}
|
|
|
|
/* check available disk space */
|
|
if(handle->checkspace) {
|
|
EVENT(trans, ALPM_TRANS_EVT_DISKSPACE_START, NULL, NULL);
|
|
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "checking available disk space\n");
|
|
if(_alpm_check_diskspace(handle) == -1) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, "%s\n", _("not enough free disk space"));
|
|
return -1;
|
|
}
|
|
|
|
EVENT(trans, ALPM_TRANS_EVT_DISKSPACE_DONE, NULL, NULL);
|
|
}
|
|
|
|
/* remove conflicting and to-be-replaced packages */
|
|
if(replaces) {
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "removing conflicting and to-be-replaced packages\n");
|
|
/* we want the frontend to be aware of commit details */
|
|
if(_alpm_remove_packages(handle) == -1) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("could not commit removal transaction\n"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* install targets */
|
|
_alpm_log(handle, ALPM_LOG_DEBUG, "installing packages\n");
|
|
if(_alpm_upgrade_packages(handle) == -1) {
|
|
_alpm_log(handle, ALPM_LOG_ERROR, _("could not commit transaction\n"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* vim: set ts=2 sw=2 noet: */
|