2019-06-16 21:08:38 +05:30
|
|
|
/*-
|
|
|
|
* Copyright (c) 2019 Juan Romero Pardines.
|
|
|
|
* Copyright (c) 2019 Duncan Overbruck <mail@duncano.de>.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
#include <dirent.h>
|
2019-06-16 21:08:38 +05:30
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2019-06-17 15:02:46 +05:30
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2019-06-16 21:08:38 +05:30
|
|
|
|
|
|
|
#include "xbps_api_impl.h"
|
|
|
|
|
|
|
|
enum type {
|
|
|
|
TYPE_LINK = 1,
|
|
|
|
TYPE_DIR,
|
|
|
|
TYPE_FILE,
|
|
|
|
TYPE_CONFFILE,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct item {
|
|
|
|
struct item *hnext;
|
|
|
|
const char *file;
|
2019-06-17 15:02:46 +05:30
|
|
|
size_t len;
|
2019-06-16 21:08:38 +05:30
|
|
|
struct {
|
2019-06-17 15:02:46 +05:30
|
|
|
const char *pkgname;
|
2019-06-16 21:08:38 +05:30
|
|
|
const char *pkgver;
|
2019-06-17 15:02:46 +05:30
|
|
|
const char *sha256;
|
2019-08-05 18:25:21 +05:30
|
|
|
const char *target;
|
2019-06-16 21:08:38 +05:30
|
|
|
uint64_t size;
|
|
|
|
enum type type;
|
2019-06-17 15:02:46 +05:30
|
|
|
unsigned int index;
|
2019-06-18 17:19:24 +05:30
|
|
|
bool preserve;
|
|
|
|
bool update;
|
2019-08-05 18:25:21 +05:30
|
|
|
bool remove;
|
2019-06-16 21:08:38 +05:30
|
|
|
} old, new;
|
2019-06-17 15:02:46 +05:30
|
|
|
bool deleted;
|
2019-06-16 21:08:38 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
#define ITHSIZE 1024
|
|
|
|
#define ITHMASK (ITHSIZE - 1)
|
|
|
|
|
|
|
|
static struct item *ItemHash[ITHSIZE];
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
static struct item **items;
|
|
|
|
static size_t itemsidx = 0;
|
|
|
|
static size_t itemssz = 0;
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
static int
|
|
|
|
itemhash(const char *file)
|
|
|
|
{
|
|
|
|
int hv = 0xA1B5F342;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
assert(file);
|
|
|
|
|
|
|
|
for (i = 0; file[i]; ++i)
|
|
|
|
hv = (hv << 5) ^ (hv >> 23) ^ file[i];
|
|
|
|
|
|
|
|
return hv & ITHMASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct item *
|
|
|
|
lookupItem(const char *file)
|
|
|
|
{
|
|
|
|
struct item *item;
|
|
|
|
|
|
|
|
assert(file);
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
for (item = ItemHash[itemhash(file+1)]; item; item = item->hnext) {
|
|
|
|
if (strcmp(file, item->file+1) == 0)
|
2019-06-16 21:08:38 +05:30
|
|
|
return item;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct item *
|
|
|
|
addItem(const char *file)
|
|
|
|
{
|
|
|
|
struct item **itemp;
|
|
|
|
struct item *item = calloc(sizeof(*item), 1);
|
2019-06-17 15:02:46 +05:30
|
|
|
if (item == NULL)
|
|
|
|
return NULL;
|
2019-06-16 21:08:38 +05:30
|
|
|
|
|
|
|
assert(file);
|
|
|
|
assert(item);
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
if (itemsidx+1 >= itemssz) {
|
|
|
|
itemssz = itemssz ? itemssz*2 : 64;
|
|
|
|
items = realloc(items, itemssz*sizeof (struct item *));
|
|
|
|
if (items == NULL) {
|
|
|
|
free(item);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
items[itemsidx++] = item;
|
|
|
|
|
|
|
|
itemp = &ItemHash[itemhash(file+1)];
|
2019-06-16 21:08:38 +05:30
|
|
|
item->hnext = *itemp;
|
2019-06-17 15:02:46 +05:30
|
|
|
if ((item->file = xbps_xasprintf(".%s", file)) == NULL) {
|
|
|
|
free(item);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
item->len = strlen(file);
|
2019-06-16 21:08:38 +05:30
|
|
|
assert(item->file);
|
|
|
|
*itemp = item;
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2019-06-18 16:30:06 +05:30
|
|
|
static const char *
|
|
|
|
typestr(enum type typ)
|
|
|
|
{
|
|
|
|
switch (typ) {
|
|
|
|
case TYPE_LINK: return "symlink";
|
|
|
|
case TYPE_DIR: return "directory";
|
|
|
|
case TYPE_FILE: return "file";
|
|
|
|
case TYPE_CONFFILE: return "configuration file";
|
|
|
|
default: return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-20 16:33:29 +05:30
|
|
|
static bool
|
|
|
|
match_preserved_file(struct xbps_handle *xhp, const char *file)
|
|
|
|
{
|
|
|
|
if (xhp->preserved_files == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
assert(file && *file == '.');
|
|
|
|
return xbps_match_string_in_array(xhp->preserved_files, file+1);
|
|
|
|
}
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
static bool
|
2019-06-17 19:05:10 +05:30
|
|
|
can_delete_directory(struct xbps_handle *xhp, const char *file, size_t len, size_t max)
|
2019-06-17 15:02:46 +05:30
|
|
|
{
|
|
|
|
struct item *item;
|
|
|
|
size_t rmcount = 0, fcount = 0;
|
|
|
|
DIR *dp;
|
|
|
|
|
|
|
|
dp = opendir(file);
|
|
|
|
if (dp == NULL) {
|
2019-06-17 19:05:10 +05:30
|
|
|
if (errno == ENOENT) {
|
2019-06-17 15:02:46 +05:30
|
|
|
return true;
|
2019-06-17 19:05:10 +05:30
|
|
|
} else {
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_dbg_printf(xhp, "[files] %s: %s: %s\n",
|
|
|
|
__func__, file, strerror(errno));
|
2019-06-17 15:02:46 +05:30
|
|
|
return false;
|
2019-06-17 19:05:10 +05:30
|
|
|
}
|
2019-06-17 15:02:46 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 1. Check if there is tracked directory content,
|
|
|
|
* which can't be deleted.
|
|
|
|
* 2. Count deletable directory content.
|
|
|
|
*/
|
|
|
|
for (size_t i = 0; i < max; i++) {
|
|
|
|
item = items[i];
|
2019-06-17 19:05:10 +05:30
|
|
|
if (strncmp(item->file, file, len) == 0) {
|
2019-06-17 15:02:46 +05:30
|
|
|
if (!item->deleted) {
|
|
|
|
closedir(dp);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
rmcount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if directory contains more files than we can
|
|
|
|
* delete.
|
|
|
|
*/
|
|
|
|
while (readdir(dp) != 0)
|
|
|
|
fcount++;
|
|
|
|
|
|
|
|
/* ignore '.' and '..' */
|
|
|
|
fcount -= 2;
|
|
|
|
|
2019-06-18 16:30:06 +05:30
|
|
|
if (fcount <= rmcount) {
|
2019-06-20 21:34:39 +05:30
|
|
|
xbps_dbg_printf(xhp, "[files] only removed %zu out of %zu files: %s\n",
|
2019-06-18 16:30:06 +05:30
|
|
|
rmcount, fcount, file);
|
|
|
|
}
|
2019-06-17 19:05:10 +05:30
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
return fcount <= rmcount;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
collect_obsoletes(struct xbps_handle *xhp)
|
|
|
|
{
|
|
|
|
/* These are symlinks in Void and must not be removed */
|
|
|
|
const char *basesymlinks[] = {
|
|
|
|
"/bin",
|
|
|
|
"/sbin",
|
|
|
|
"/usr/sbin",
|
|
|
|
"/lib",
|
|
|
|
"/lib32",
|
|
|
|
"/lib64",
|
|
|
|
"/usr/lib32",
|
|
|
|
"/usr/lib64",
|
|
|
|
"/var/run",
|
|
|
|
};
|
|
|
|
xbps_dictionary_t obsd;
|
|
|
|
struct item *item;
|
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
if (xhp->transd == NULL)
|
|
|
|
return ENOTSUP;
|
|
|
|
|
|
|
|
if (!xbps_dictionary_get_dict(xhp->transd, "obsolete_files", &obsd))
|
|
|
|
return ENOENT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Iterate over all files, longest paths first,
|
|
|
|
* to check if directory contents of removed
|
|
|
|
* directories can be deleted.
|
|
|
|
*
|
|
|
|
* - Check if a file is obsolete
|
|
|
|
* - Check if obsolete file can be deleted.
|
|
|
|
* - Check if directory needs and can be deleted.
|
|
|
|
*/
|
|
|
|
for (size_t i = 0; i < itemsidx; i++) {
|
|
|
|
xbps_array_t a;
|
|
|
|
const char *pkgname;
|
|
|
|
bool alloc = false, found = false;
|
|
|
|
|
|
|
|
item = items[i];
|
|
|
|
|
2019-06-20 16:33:29 +05:30
|
|
|
if (match_preserved_file(xhp, item->file)) {
|
|
|
|
xbps_dbg_printf(xhp, "[obsoletes] %s: file exists on disk"
|
|
|
|
" and must be preserved: %s\n", item->old.pkgver, item->file);
|
|
|
|
continue;
|
|
|
|
}
|
2019-06-18 17:19:24 +05:30
|
|
|
|
|
|
|
if (item->new.type == 0) {
|
2019-06-17 15:02:46 +05:30
|
|
|
/*
|
|
|
|
* File was removed and is not provided by any
|
|
|
|
* new package.
|
|
|
|
* Probably obsolete.
|
|
|
|
*/
|
2019-06-18 17:19:24 +05:30
|
|
|
if (item->old.preserve && item->old.update) {
|
|
|
|
xbps_dbg_printf(xhp, "[files] %s: skipping `preserve` %s: %s\n",
|
|
|
|
item->old.pkgver, typestr(item->old.type), item->file);
|
|
|
|
continue;
|
|
|
|
}
|
2019-06-17 15:02:46 +05:30
|
|
|
} else if (item->new.type == TYPE_CONFFILE) {
|
|
|
|
/*
|
|
|
|
* Ignore conf files.
|
|
|
|
*/
|
|
|
|
continue;
|
|
|
|
} else if (item->old.type == 0) {
|
|
|
|
/* XXX: add this new behaviour? */
|
|
|
|
#if 0
|
|
|
|
/*
|
|
|
|
* Check if new file (untracked until now) exists.
|
|
|
|
*/
|
|
|
|
if (access(item->file, F_OK) == 0) {
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
2019-06-18 16:30:06 +05:30
|
|
|
EEXIST, item->new.pkgver,
|
|
|
|
"%s: file `%s' already exists.",
|
|
|
|
item->new.pkgver, item->file);
|
2019-06-17 15:02:46 +05:30
|
|
|
rv = EEXIST;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
continue;
|
2019-06-17 19:05:10 +05:30
|
|
|
} else if (item->old.type == TYPE_DIR &&
|
|
|
|
item->new.type != TYPE_DIR && item->new.type != 0) {
|
2019-06-17 15:02:46 +05:30
|
|
|
/*
|
|
|
|
* Directory replaced by a file or symlink.
|
|
|
|
* We MUST be able to delete the directory.
|
|
|
|
*/
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_dbg_printf(xhp, "[files] %s: directory changed to %s: %s\n",
|
|
|
|
item->new.pkgver, typestr(item->new.type), item->file);
|
2019-06-17 19:05:10 +05:30
|
|
|
if (!can_delete_directory(xhp, item->file, item->len, i)) {
|
2019-06-17 15:02:46 +05:30
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
2019-06-18 16:30:06 +05:30
|
|
|
ENOTEMPTY, item->old.pkgver,
|
|
|
|
"%s: directory `%s' can not be deleted.",
|
|
|
|
item->old.pkgver, item->file);
|
2019-06-17 15:02:46 +05:30
|
|
|
return ENOTEMPTY;
|
|
|
|
}
|
|
|
|
} else if (item->new.type != item->old.type) {
|
|
|
|
/*
|
|
|
|
* File type changed, we have to delete it.
|
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure to not remove any symlink of root directory.
|
|
|
|
*/
|
|
|
|
for (uint8_t x = 0; x < __arraycount(basesymlinks); x++) {
|
|
|
|
if (strcmp(item->file+1, basesymlinks[x]) == 0) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip unexisting files and keep files with hash mismatch.
|
|
|
|
*/
|
|
|
|
if (item->old.sha256) {
|
|
|
|
rv = xbps_file_hash_check(item->file, item->old.sha256);
|
|
|
|
switch (rv) {
|
|
|
|
case 0:
|
|
|
|
/* hash matches, we can safely delete and/or overwrite it */
|
|
|
|
break;
|
|
|
|
case ENOENT:
|
|
|
|
/* mark unexisting files as deleted and ignore ENOENT */
|
|
|
|
rv = 0;
|
|
|
|
item->deleted = true;
|
|
|
|
continue;
|
|
|
|
case ERANGE:
|
|
|
|
/* hash mismatch don't delete it */
|
2019-06-26 16:37:16 +05:30
|
|
|
rv = 0;
|
2019-08-05 18:25:21 +05:30
|
|
|
/*
|
|
|
|
* If the file is removed by uninstalling the package,
|
|
|
|
* no new package provides it and its not force removed,
|
|
|
|
* keep the file.
|
|
|
|
*/
|
|
|
|
if (item->old.remove && !item->new.pkgname &&
|
|
|
|
(xhp->flags & XBPS_FLAG_FORCE_REMOVE_FILES) != 0) {
|
|
|
|
xbps_dbg_printf(xhp, "[obsoletes] %s: SHA256 mismatch,"
|
|
|
|
" force remove %s: %s\n",
|
|
|
|
item->old.pkgname, typestr(item->old.type),
|
|
|
|
item->file+1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
xbps_dbg_printf(xhp, "[obsoletes] %s: SHA256 mismatch,"
|
|
|
|
" skipping remove %s: %s\n",
|
|
|
|
item->old.pkgname, typestr(item->old.type),
|
|
|
|
item->file+1);
|
2019-06-17 15:02:46 +05:30
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-05 18:25:21 +05:30
|
|
|
/*
|
|
|
|
* On package removal without force, keep symlinks if target changed.
|
|
|
|
*/
|
|
|
|
if (item->old.pkgname && item->old.remove &&
|
|
|
|
item->old.type == TYPE_LINK && !item->new.pkgname &&
|
|
|
|
(xhp->flags & XBPS_FLAG_FORCE_REMOVE_FILES) == 0) {
|
|
|
|
char path[PATH_MAX], *lnk;
|
|
|
|
const char *file = item->file+1;
|
|
|
|
if (strcmp(xhp->rootdir, "/") != 0) {
|
|
|
|
snprintf(path, sizeof(path), "%s%s",
|
|
|
|
xhp->rootdir, item->file+1);
|
|
|
|
file = path;
|
|
|
|
}
|
|
|
|
lnk = xbps_symlink_target(xhp, file, item->old.target);
|
|
|
|
if (lnk == NULL) {
|
|
|
|
xbps_dbg_printf(xhp, "[obsoletes] %s "
|
|
|
|
"symlink_target: %s\n", item->file+1, strerror(errno));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (strcmp(lnk, item->old.target) != 0) {
|
|
|
|
xbps_dbg_printf(xhp, "[obsoletes] %s: skipping modified"
|
|
|
|
" symlink (stored `%s' current `%s'): %s\n",
|
|
|
|
item->old.pkgname, item->old.target, lnk, item->file+1);
|
|
|
|
free(lnk);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
free(lnk);
|
|
|
|
}
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
/*
|
|
|
|
* Choose which package removes the obsolete files,
|
|
|
|
* based which packages is installed/unpacked first.
|
|
|
|
* This is necessary to not delete files
|
|
|
|
* after it was installed by another package.
|
|
|
|
*/
|
|
|
|
if (item->old.pkgname && item->new.pkgname) {
|
|
|
|
pkgname = item->old.index > item->new.index ?
|
|
|
|
item->new.pkgname : item->old.pkgname;
|
|
|
|
} else if (item->old.pkgname) {
|
|
|
|
pkgname = item->old.pkgname;
|
|
|
|
} else {
|
|
|
|
pkgname = item->new.pkgname;
|
|
|
|
}
|
|
|
|
assert(pkgname);
|
|
|
|
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_dbg_printf(xhp, "[obsoletes] %s: removes %s: %s\n",
|
2019-08-05 18:25:21 +05:30
|
|
|
pkgname, typestr(item->old.type), item->file+1);
|
2019-06-17 19:05:10 +05:30
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
/*
|
|
|
|
* Mark file as being deleted, this is used when
|
|
|
|
* checking if a directory can be deleted.
|
|
|
|
*/
|
|
|
|
item->deleted = true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add file to the packages `obsolete_files` dict
|
|
|
|
*/
|
|
|
|
if ((a = xbps_dictionary_get(obsd, pkgname)) == NULL) {
|
|
|
|
if (!(a = xbps_array_create()) ||
|
|
|
|
!(xbps_dictionary_set(obsd, pkgname, a)))
|
|
|
|
return ENOMEM;
|
|
|
|
alloc = true;
|
|
|
|
}
|
|
|
|
if (!xbps_array_add_cstring(a, item->file)) {
|
|
|
|
if (alloc)
|
|
|
|
xbps_object_release(a);
|
|
|
|
return ENOMEM;
|
|
|
|
}
|
|
|
|
if (alloc)
|
|
|
|
xbps_object_release(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
static int
|
|
|
|
collect_file(struct xbps_handle *xhp, const char *file, size_t size,
|
2019-06-17 15:02:46 +05:30
|
|
|
const char *pkgname, const char *pkgver, unsigned int idx,
|
2019-08-05 18:25:21 +05:30
|
|
|
const char *sha256, enum type type, bool update, bool remove,
|
|
|
|
bool preserve, bool removefile, const char *target)
|
2019-06-16 21:08:38 +05:30
|
|
|
{
|
|
|
|
struct item *item;
|
|
|
|
|
|
|
|
assert(file);
|
|
|
|
|
|
|
|
if ((item = lookupItem(file)) == NULL) {
|
|
|
|
item = addItem(file);
|
2019-06-17 15:02:46 +05:30
|
|
|
if (item == NULL)
|
|
|
|
return ENOMEM;
|
|
|
|
item->deleted = false;
|
|
|
|
goto add;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
|
2019-06-27 20:39:43 +05:30
|
|
|
if (removefile) {
|
2019-06-16 21:08:38 +05:30
|
|
|
if (item->old.type == 0) {
|
|
|
|
/*
|
|
|
|
* File wasn't removed before.
|
|
|
|
*/
|
|
|
|
} else if (type == TYPE_DIR && item->old.type == TYPE_DIR) {
|
|
|
|
/*
|
|
|
|
* Multiple packages removing the same directory.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Multiple packages removing the same file.
|
|
|
|
* Shouldn't happen, but its not fatal.
|
|
|
|
*/
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_dbg_printf(xhp, "[files] %s: file already removed"
|
|
|
|
" by package `%s': %s\n", pkgver, item->old.pkgver, file);
|
|
|
|
|
2019-06-18 17:19:24 +05:30
|
|
|
/*
|
|
|
|
* Check if `preserve` is violated.
|
|
|
|
*/
|
|
|
|
if (item->old.preserve && !preserve) {
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
|
|
|
EPERM, item->old.pkgver,
|
|
|
|
"%s: preserved file `%s' removed by %s.",
|
|
|
|
item->old.pkgver, file, pkgver);
|
|
|
|
return EPERM;
|
|
|
|
} else if (preserve && !item->old.preserve) {
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
|
|
|
EPERM, pkgver,
|
|
|
|
"%s: preserved file `%s' removed by %s.",
|
|
|
|
pkgver, file, item->old.pkgver);
|
|
|
|
return EPERM;
|
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
return 0;
|
|
|
|
}
|
2019-06-17 15:02:46 +05:30
|
|
|
goto add;
|
2019-06-16 21:08:38 +05:30
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Multiple packages creating the same directory.
|
|
|
|
*/
|
|
|
|
if (item->new.type == 0) {
|
|
|
|
/*
|
|
|
|
* File wasn't created before.
|
|
|
|
*/
|
|
|
|
} else if (type == TYPE_DIR && item->new.type == TYPE_DIR) {
|
|
|
|
/*
|
|
|
|
* Multiple packages creating the same directory.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Multiple packages creating the same file.
|
|
|
|
* This should never happen in a transaction.
|
|
|
|
*/
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
2019-06-18 16:30:06 +05:30
|
|
|
EEXIST, pkgver,
|
|
|
|
"%s: file `%s' already installed by package %s.",
|
|
|
|
pkgver, file, item->new.pkgver);
|
2019-06-21 18:51:58 +05:30
|
|
|
if (xhp->flags & XBPS_FLAG_IGNORE_FILE_CONFLICTS)
|
|
|
|
return 0;
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
return EEXIST;
|
|
|
|
}
|
2019-06-17 15:02:46 +05:30
|
|
|
goto add;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
add:
|
2019-06-27 20:39:43 +05:30
|
|
|
if (removefile) {
|
2019-06-17 15:02:46 +05:30
|
|
|
item->old.pkgname = strdup(pkgname);
|
|
|
|
item->old.pkgver = strdup(pkgver);
|
|
|
|
item->old.type = type;
|
|
|
|
item->old.size = size;
|
|
|
|
item->old.index = idx;
|
2019-06-18 17:19:24 +05:30
|
|
|
item->old.preserve = preserve;
|
|
|
|
item->old.update = update;
|
2019-08-05 18:25:21 +05:30
|
|
|
item->old.remove = remove;
|
|
|
|
item->old.target = target;
|
2019-06-17 15:02:46 +05:30
|
|
|
if (sha256)
|
|
|
|
item->old.sha256 = strdup(sha256);
|
|
|
|
} else {
|
|
|
|
item->new.pkgname = strdup(pkgname);
|
|
|
|
item->new.pkgver = strdup(pkgver);
|
2019-06-16 21:08:38 +05:30
|
|
|
item->new.type = type;
|
|
|
|
item->new.size = size;
|
2019-06-17 15:02:46 +05:30
|
|
|
item->new.index = idx;
|
2019-06-18 17:19:24 +05:30
|
|
|
item->new.preserve = preserve;
|
|
|
|
item->new.update = update;
|
2019-08-05 18:25:21 +05:30
|
|
|
item->new.remove = remove;
|
|
|
|
item->new.target = target;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
if (item->old.type && item->new.type) {
|
|
|
|
/*
|
|
|
|
* The file was removed by one package
|
|
|
|
* and installed by another package.
|
|
|
|
*/
|
2019-06-17 15:02:46 +05:30
|
|
|
if (strcmp(item->new.pkgname, item->old.pkgname) != 0) {
|
2019-06-27 20:39:43 +05:30
|
|
|
if (removefile) {
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_dbg_printf(xhp, "[files] %s: %s moved to"
|
|
|
|
" package `%s': %s\n", pkgver, typestr(item->old.type),
|
|
|
|
item->new.pkgver, file);
|
2019-06-16 21:08:38 +05:30
|
|
|
} else {
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_dbg_printf(xhp, "[files] %s: %s moved from"
|
|
|
|
" package `%s': %s\n", pkgver, typestr(item->new.type),
|
|
|
|
item->old.pkgver, file);
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
collect_files(struct xbps_handle *xhp, xbps_dictionary_t d,
|
2019-06-17 15:02:46 +05:30
|
|
|
const char *pkgname, const char *pkgver, unsigned int idx,
|
2019-08-05 18:25:21 +05:30
|
|
|
bool update, bool remove, bool preserve, bool removefile)
|
2019-06-16 21:08:38 +05:30
|
|
|
{
|
|
|
|
xbps_array_t a;
|
|
|
|
xbps_dictionary_t filed;
|
|
|
|
uint64_t size;
|
|
|
|
unsigned int i;
|
|
|
|
int rv = 0;
|
2019-06-17 15:02:46 +05:30
|
|
|
const char *file, *sha256 = NULL;
|
2019-06-21 17:18:47 +05:30
|
|
|
bool error = false;
|
2019-06-16 21:08:38 +05:30
|
|
|
|
|
|
|
if ((a = xbps_dictionary_get(d, "files"))) {
|
|
|
|
for (i = 0; i < xbps_array_count(a); i++) {
|
|
|
|
filed = xbps_array_get(a, i);
|
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
|
2019-06-27 20:39:43 +05:30
|
|
|
if (removefile)
|
2019-06-17 15:02:46 +05:30
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "sha256", &sha256);
|
2019-06-16 21:08:38 +05:30
|
|
|
size = 0;
|
|
|
|
xbps_dictionary_get_uint64(filed, "size", &size);
|
2019-06-17 15:02:46 +05:30
|
|
|
rv = collect_file(xhp, file, size, pkgname, pkgver, idx, sha256,
|
2019-08-05 18:25:21 +05:30
|
|
|
TYPE_FILE, update, remove, preserve, removefile, NULL);
|
2019-06-21 17:18:47 +05:30
|
|
|
if (rv == EEXIST) {
|
|
|
|
error = true;
|
|
|
|
continue;
|
|
|
|
} else if (rv != 0) {
|
2019-06-16 21:08:38 +05:30
|
|
|
goto out;
|
2019-06-21 17:18:47 +05:30
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((a = xbps_dictionary_get(d, "conf_files"))) {
|
|
|
|
for (i = 0; i < xbps_array_count(a); i++) {
|
|
|
|
filed = xbps_array_get(a, i);
|
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
|
|
|
|
size = 0;
|
|
|
|
xbps_dictionary_get_uint64(filed, "size", &size);
|
2019-06-27 20:39:43 +05:30
|
|
|
if (removefile)
|
2019-06-17 15:02:46 +05:30
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "sha256", &sha256);
|
|
|
|
#if 0
|
2019-06-16 21:08:38 +05:30
|
|
|
/* XXX: how to handle conf_file size */
|
|
|
|
if (remove && stat(file, &st) != -1 && size != (uint64_t)st.st_size)
|
|
|
|
size = 0;
|
2019-06-17 15:02:46 +05:30
|
|
|
#endif
|
|
|
|
rv = collect_file(xhp, file, size, pkgname, pkgver, idx, sha256,
|
2019-08-05 18:25:21 +05:30
|
|
|
TYPE_FILE, update, remove, preserve, removefile, NULL);
|
2019-06-21 17:18:47 +05:30
|
|
|
if (rv == EEXIST) {
|
|
|
|
error = true;
|
|
|
|
continue;
|
|
|
|
} else if (rv != 0) {
|
2019-06-16 21:08:38 +05:30
|
|
|
goto out;
|
2019-06-21 17:18:47 +05:30
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((a = xbps_dictionary_get(d, "links"))) {
|
|
|
|
for (i = 0; i < xbps_array_count(a); i++) {
|
2019-08-05 18:25:21 +05:30
|
|
|
const char *target = NULL;
|
2019-06-16 21:08:38 +05:30
|
|
|
filed = xbps_array_get(a, i);
|
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
|
2019-08-05 18:25:21 +05:30
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "target", &target);
|
|
|
|
assert(target);
|
2019-06-17 15:02:46 +05:30
|
|
|
rv = collect_file(xhp, file, 0, pkgname, pkgver, idx, NULL,
|
2019-08-05 18:25:21 +05:30
|
|
|
TYPE_LINK, update, remove, preserve, removefile, target);
|
2019-06-21 17:18:47 +05:30
|
|
|
if (rv == EEXIST) {
|
|
|
|
error = true;
|
|
|
|
continue;
|
|
|
|
} else if (rv != 0) {
|
2019-06-16 21:08:38 +05:30
|
|
|
goto out;
|
2019-06-21 17:18:47 +05:30
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((a = xbps_dictionary_get(d, "dirs"))) {
|
|
|
|
for (i = 0; i < xbps_array_count(a); i++) {
|
|
|
|
filed = xbps_array_get(a, i);
|
|
|
|
xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
|
2019-06-17 15:02:46 +05:30
|
|
|
rv = collect_file(xhp, file, 0, pkgname, pkgver, idx, NULL,
|
2019-08-05 18:25:21 +05:30
|
|
|
TYPE_DIR, update, remove, preserve, removefile, NULL);
|
2019-06-21 17:18:47 +05:30
|
|
|
if (rv == EEXIST) {
|
|
|
|
error = true;
|
|
|
|
continue;
|
|
|
|
} else if (rv != 0) {
|
2019-06-16 21:08:38 +05:30
|
|
|
goto out;
|
2019-06-21 17:18:47 +05:30
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2019-06-21 17:18:47 +05:30
|
|
|
if (error)
|
|
|
|
rv = EEXIST;
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2019-06-18 16:30:06 +05:30
|
|
|
collect_binpkg_files(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod,
|
2019-06-18 17:19:24 +05:30
|
|
|
unsigned int idx, bool update)
|
2019-06-16 21:08:38 +05:30
|
|
|
{
|
|
|
|
xbps_dictionary_t filesd;
|
|
|
|
struct archive *ar = NULL;
|
|
|
|
struct archive_entry *entry;
|
|
|
|
struct stat st;
|
|
|
|
const char *pkgver;
|
2019-06-17 15:02:46 +05:30
|
|
|
char *bpkg, *pkgname;
|
2019-06-16 21:08:38 +05:30
|
|
|
/* size_t entry_size; */
|
|
|
|
int rv = 0, pkg_fd = -1;
|
|
|
|
|
|
|
|
xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
|
|
|
|
assert(pkgver);
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
pkgname = xbps_pkg_name(pkgver);
|
|
|
|
assert(pkgname);
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
bpkg = xbps_repository_pkg_path(xhp, pkg_repod);
|
2019-06-17 15:02:46 +05:30
|
|
|
if (bpkg == NULL) {
|
|
|
|
rv = errno;
|
|
|
|
goto out;
|
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
|
|
|
|
if ((ar = archive_read_new()) == NULL) {
|
2019-06-17 15:02:46 +05:30
|
|
|
rv = errno;
|
|
|
|
goto out;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Enable support for tar format and gzip/bzip2/lzma compression methods.
|
|
|
|
*/
|
2019-06-17 19:05:10 +05:30
|
|
|
archive_read_support_filter_gzip(ar);
|
|
|
|
archive_read_support_filter_bzip2(ar);
|
|
|
|
archive_read_support_filter_xz(ar);
|
|
|
|
archive_read_support_filter_lz4(ar);
|
|
|
|
archive_read_support_filter_zstd(ar);
|
2019-06-16 21:08:38 +05:30
|
|
|
archive_read_support_format_tar(ar);
|
|
|
|
|
|
|
|
pkg_fd = open(bpkg, O_RDONLY|O_CLOEXEC);
|
|
|
|
if (pkg_fd == -1) {
|
|
|
|
rv = errno;
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
|
|
|
rv, pkgver,
|
2019-06-18 16:30:06 +05:30
|
|
|
"%s: failed to open binary package `%s': %s",
|
2019-06-16 21:08:38 +05:30
|
|
|
pkgver, bpkg, strerror(rv));
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (fstat(pkg_fd, &st) == -1) {
|
|
|
|
rv = errno;
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
|
|
|
rv, pkgver,
|
2019-06-18 16:30:06 +05:30
|
|
|
"%s: failed to fstat binary package `%s': %s",
|
2019-06-16 21:08:38 +05:30
|
|
|
pkgver, bpkg, strerror(rv));
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (archive_read_open_fd(ar, pkg_fd, st.st_blksize) == ARCHIVE_FATAL) {
|
|
|
|
rv = archive_errno(ar);
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
|
|
|
|
rv, pkgver,
|
2019-06-18 16:30:06 +05:30
|
|
|
"%s: failed to read binary package `%s': %s",
|
2019-06-16 21:08:38 +05:30
|
|
|
pkgver, bpkg, strerror(rv));
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
|
|
const char *entry_pname;
|
|
|
|
int ar_rv = archive_read_next_header(ar, &entry);
|
|
|
|
if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
|
|
|
|
break;
|
|
|
|
else if (ar_rv == ARCHIVE_RETRY)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
entry_pname = archive_entry_pathname(entry);
|
|
|
|
if ((strcmp("./files.plist", entry_pname)) == 0) {
|
|
|
|
filesd = xbps_archive_get_dictionary(ar, entry);
|
|
|
|
if (filesd == NULL) {
|
|
|
|
rv = EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
2019-06-18 17:19:24 +05:30
|
|
|
rv = collect_files(xhp, filesd, pkgname, pkgver, idx,
|
2019-08-05 18:25:21 +05:30
|
|
|
update, false, false, false);
|
2019-06-18 16:30:06 +05:30
|
|
|
goto out;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
archive_read_data_skip(ar);
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (pkg_fd != -1)
|
|
|
|
close(pkg_fd);
|
|
|
|
if (ar)
|
|
|
|
archive_read_finish(ar);
|
2019-06-17 15:02:46 +05:30
|
|
|
free(bpkg);
|
|
|
|
free(pkgname);
|
2019-06-16 21:08:38 +05:30
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2019-06-17 15:02:46 +05:30
|
|
|
static int
|
|
|
|
pathcmp(const void *l1, const void *l2)
|
|
|
|
{
|
|
|
|
const struct item *a = *(const struct item * const*)l1;
|
|
|
|
const struct item *b = *(const struct item * const*)l2;
|
|
|
|
return (a->len < b->len) - (b->len < a->len);
|
|
|
|
}
|
2019-06-16 21:08:38 +05:30
|
|
|
|
|
|
|
int HIDDEN
|
|
|
|
xbps_transaction_files(struct xbps_handle *xhp, xbps_object_iterator_t iter)
|
|
|
|
{
|
|
|
|
xbps_dictionary_t pkgd, filesd;
|
|
|
|
xbps_object_t obj;
|
|
|
|
const char *trans, *pkgver;
|
2019-06-18 16:30:06 +05:30
|
|
|
char *pkgname = NULL;
|
2019-06-16 21:08:38 +05:30
|
|
|
int rv = 0;
|
2019-06-17 15:02:46 +05:30
|
|
|
unsigned int idx = 0;
|
2019-06-16 21:08:38 +05:30
|
|
|
|
|
|
|
iter = xbps_array_iter_from_dict(xhp->transd, "packages");
|
|
|
|
if (iter == NULL)
|
|
|
|
return EINVAL;
|
|
|
|
|
|
|
|
while ((obj = xbps_object_iterator_next(iter)) != NULL) {
|
2019-06-18 17:19:24 +05:30
|
|
|
bool update = false;
|
2019-06-17 15:02:46 +05:30
|
|
|
/*
|
|
|
|
* `idx` is used as package install index, to chose which
|
|
|
|
* choose the first package which owns or used to own the
|
|
|
|
* file deletes it.
|
|
|
|
*/
|
|
|
|
idx++;
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
xbps_dictionary_get_cstring_nocopy(obj, "transaction", &trans);
|
|
|
|
assert(trans);
|
|
|
|
|
|
|
|
if ((strcmp(trans, "hold") == 0) ||
|
|
|
|
(strcmp(trans, "configure") == 0))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver);
|
|
|
|
|
|
|
|
assert(pkgver);
|
|
|
|
pkgname = xbps_pkg_name(pkgver);
|
|
|
|
assert(pkgname);
|
|
|
|
|
2019-06-18 17:19:24 +05:30
|
|
|
update = strcmp(trans, "update") == 0;
|
2019-06-16 21:08:38 +05:30
|
|
|
|
2019-06-18 17:19:24 +05:30
|
|
|
if (update || (strcmp(trans, "install") == 0)) {
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES, 0, pkgver,
|
|
|
|
"%s: collecting files...", pkgver);
|
2019-06-18 17:19:24 +05:30
|
|
|
rv = collect_binpkg_files(xhp, obj, idx, update);
|
2019-06-18 16:30:06 +05:30
|
|
|
if (rv != 0)
|
|
|
|
goto out;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Always just try to get the package from the pkgdb:
|
|
|
|
* update and remove always have a previous package,
|
|
|
|
* `hold` and `configure` are skipped.
|
|
|
|
* And finally the reason to do is, `install` could be
|
|
|
|
* a reinstallation, in which case the files list could
|
|
|
|
* different between old and new "install".
|
|
|
|
*/
|
|
|
|
pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
|
|
|
|
if (pkgd) {
|
|
|
|
const char *oldpkgver;
|
2019-06-17 21:51:10 +05:30
|
|
|
bool preserve = false;
|
2019-08-05 18:25:21 +05:30
|
|
|
bool remove = strcmp(trans, "remove") == 0;
|
2019-06-18 16:30:06 +05:30
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &oldpkgver);
|
2019-06-18 16:30:06 +05:30
|
|
|
if (!xbps_dictionary_get_bool(obj, "preserve", &preserve))
|
|
|
|
preserve = false;
|
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
filesd = xbps_pkgdb_get_pkg_files(xhp, pkgname);
|
|
|
|
if (filesd == NULL) {
|
|
|
|
free(pkgname);
|
2019-06-18 16:30:06 +05:30
|
|
|
pkgname = NULL;
|
2019-06-16 21:08:38 +05:30
|
|
|
continue;
|
|
|
|
}
|
2019-06-18 16:30:06 +05:30
|
|
|
|
2019-06-16 21:08:38 +05:30
|
|
|
assert(oldpkgver);
|
2019-06-18 16:30:06 +05:30
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES, 0, oldpkgver,
|
|
|
|
"%s: collecting files...", oldpkgver);
|
2019-06-18 17:19:24 +05:30
|
|
|
rv = collect_files(xhp, filesd, pkgname, pkgver, idx,
|
2019-08-05 18:25:21 +05:30
|
|
|
update, remove, preserve, true);
|
2019-06-18 16:30:06 +05:30
|
|
|
if (rv != 0)
|
|
|
|
goto out;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
free(pkgname);
|
2019-06-18 16:30:06 +05:30
|
|
|
pkgname = NULL;
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|
|
|
|
xbps_object_iterator_reset(iter);
|
|
|
|
|
2019-06-18 16:30:06 +05:30
|
|
|
/*
|
|
|
|
* Sort items by path length, to make it easier to find files in
|
|
|
|
* directories.
|
|
|
|
*/
|
2019-06-17 15:02:46 +05:30
|
|
|
qsort(items, itemsidx, sizeof (struct item *), pathcmp);
|
|
|
|
|
|
|
|
if (chdir(xhp->rootdir) == -1) {
|
|
|
|
rv = errno;
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL, rv, xhp->rootdir,
|
2019-06-18 16:30:06 +05:30
|
|
|
"failed to chdir to rootdir `%s': %s",
|
2019-06-17 15:02:46 +05:30
|
|
|
xhp->rootdir, strerror(errno));
|
|
|
|
}
|
|
|
|
|
2019-06-18 16:30:06 +05:30
|
|
|
out:
|
|
|
|
free(pkgname);
|
2019-06-17 15:02:46 +05:30
|
|
|
if (rv != 0)
|
|
|
|
return rv;
|
|
|
|
return collect_obsoletes(xhp);
|
2019-06-16 21:08:38 +05:30
|
|
|
}
|