Merged purge code in remove, configurable transaction regpkgdb flushing.
See the NEWS file for info.
This commit is contained in:
@@ -44,7 +44,7 @@ EXTOBJS += external/match.o external/mkpath.o
|
||||
OBJS = package_configure.o package_config_files.o package_orphans.o
|
||||
OBJS += package_remove.o package_remove_obsoletes.o package_state.o
|
||||
OBJS += package_unpack.o package_requiredby.o package_register.o
|
||||
OBJS += package_purge.o transaction_commit.o transaction_package_replace.o
|
||||
OBJS += transaction_commit.o transaction_package_replace.o
|
||||
OBJS += transaction_dictionary.o transaction_sortdeps.o transaction_ops.o
|
||||
OBJS += download.o initend.o
|
||||
OBJS += plist.o plist_archive_entry.o plist_find.o plist_match.o
|
||||
|
||||
@@ -88,12 +88,14 @@ xbps_init(struct xbps_handle *xh)
|
||||
CFG_STR(__UNCONST("rootdir"), __UNCONST("/"), CFGF_NONE),
|
||||
CFG_STR(__UNCONST("cachedir"),
|
||||
__UNCONST(XBPS_CACHE_PATH), CFGF_NONE),
|
||||
CFG_INT(__UNCONST("fetch-cache-connections"),
|
||||
CFG_INT(__UNCONST("FetchCacheConnections"),
|
||||
XBPS_FETCH_CACHECONN, CFGF_NONE),
|
||||
CFG_INT(__UNCONST("fetch-cache-connections-per-host"),
|
||||
CFG_INT(__UNCONST("FetchCacheConnectionsPerHost"),
|
||||
XBPS_FETCH_CACHECONN_HOST, CFGF_NONE),
|
||||
CFG_INT(__UNCONST("fetch-timeout-connection"),
|
||||
CFG_INT(__UNCONST("FetchTimeoutConnection"),
|
||||
XBPS_FETCH_TIMEOUT, CFGF_NONE),
|
||||
CFG_INT(__UNCONST("TransactionFrequencyFlush"),
|
||||
XBPS_TRANS_FLUSH, CFGF_NONE),
|
||||
CFG_BOOL(__UNCONST("syslog"), true, CFGF_NONE),
|
||||
CFG_STR_LIST(__UNCONST("repositories"), NULL, CFGF_MULTI),
|
||||
CFG_SEC(__UNCONST("virtual-package"),
|
||||
@@ -161,22 +163,27 @@ xbps_init(struct xbps_handle *xh)
|
||||
if (xhp->cfg == NULL) {
|
||||
xhp->syslog_enabled = true;
|
||||
xhp->fetch_timeout = XBPS_FETCH_TIMEOUT;
|
||||
xhp->transaction_frequency_flush = XBPS_TRANS_FLUSH;
|
||||
cc = XBPS_FETCH_CACHECONN;
|
||||
cch = XBPS_FETCH_CACHECONN_HOST;
|
||||
} else {
|
||||
xhp->syslog_enabled = cfg_getbool(xhp->cfg, "syslog");
|
||||
xhp->fetch_timeout = cfg_getint(xhp->cfg, "fetch-timeout-connection");
|
||||
cc = cfg_getint(xhp->cfg, "fetch-cache-connections");
|
||||
cch = cfg_getint(xhp->cfg, "fetch-cache-connections-per-host");
|
||||
xhp->fetch_timeout = cfg_getint(xhp->cfg, "FetchTimeoutConnection");
|
||||
cc = cfg_getint(xhp->cfg, "FetchCacheConnections");
|
||||
cch = cfg_getint(xhp->cfg, "FetchCacheConnectionsPerHost");
|
||||
xhp->transaction_frequency_flush =
|
||||
cfg_getint(xhp->cfg, "TransactionFrequencyFlush");
|
||||
}
|
||||
xbps_fetch_set_cache_connection(cc, cch);
|
||||
|
||||
xbps_dbg_printf("rootdir=%s\n", xhp->rootdir);
|
||||
xbps_dbg_printf("cachedir=%s\n", xhp->cachedir);
|
||||
xbps_dbg_printf("fetch-timeout=%u\n", xhp->fetch_timeout);
|
||||
xbps_dbg_printf("fetch-cacheconn=%u\n", cc);
|
||||
xbps_dbg_printf("fetch-cacheconn-host=%u\n", cch);
|
||||
xbps_dbg_printf("syslog=%u\n", xhp->syslog_enabled);
|
||||
xbps_dbg_printf("Rootdir=%s\n", xhp->rootdir);
|
||||
xbps_dbg_printf("Cachedir=%s\n", xhp->cachedir);
|
||||
xbps_dbg_printf("FetchTimeout=%u\n", xhp->fetch_timeout);
|
||||
xbps_dbg_printf("FetchCacheconn=%u\n", cc);
|
||||
xbps_dbg_printf("FetchCacheconnHost=%u\n", cch);
|
||||
xbps_dbg_printf("Syslog=%u\n", xhp->syslog_enabled);
|
||||
xbps_dbg_printf("TransactionFrequencyFlush=%u\n",
|
||||
xhp->transaction_frequency_flush);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -86,8 +86,7 @@ xbps_configure_pkg(const char *pkgname,
|
||||
rv = xbps_pkg_state_installed(pkgname, &state);
|
||||
if (rv == ENOENT) {
|
||||
/*
|
||||
* package not installed or has been removed
|
||||
* (must be purged) so ignore it.
|
||||
* package not installed or has been removed.
|
||||
*/
|
||||
return 0;
|
||||
} else if (rv != 0) {
|
||||
|
||||
@@ -90,9 +90,10 @@ find_orphan_pkg(prop_object_t obj, void *arg, bool *loop_done)
|
||||
if ((rv = xbps_pkg_state_dictionary(obj, &state)) != 0)
|
||||
return rv;
|
||||
/*
|
||||
* Skip packages that aren't fully installed.
|
||||
* Skip packages that aren't fully installed or half removed.
|
||||
*/
|
||||
if (state != XBPS_PKG_STATE_INSTALLED)
|
||||
if (state != XBPS_PKG_STATE_INSTALLED &&
|
||||
state != XBPS_PKG_STATE_HALF_REMOVED)
|
||||
return 0;
|
||||
|
||||
reqby = prop_dictionary_get(obj, "requiredby");
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2009-2011 Juan Romero Pardines.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "xbps_api_impl.h"
|
||||
|
||||
/**
|
||||
* @file lib/package_purge.c
|
||||
* @brief Package purging routines
|
||||
* @defgroup purge Package purging functions
|
||||
*
|
||||
* These functions will purge an specified package or all packages.
|
||||
* Only packages in XBPS_PKG_STATE_CONFIG_FILES state will be processed
|
||||
* (unless overriden). Package purging steps:
|
||||
*
|
||||
* - Unmodified configuration files will be removed.
|
||||
* - The purge action in the REMOVE script will be executed (if found).
|
||||
* - Metadata files will be removed and package will be unregistered
|
||||
* with xbps_unregister_pkg().
|
||||
*/
|
||||
|
||||
static int
|
||||
remove_pkg_metadata(const char *pkgname,
|
||||
const char *version,
|
||||
const char *pkgver,
|
||||
const char *rootdir)
|
||||
{
|
||||
struct dirent *dp;
|
||||
DIR *dirp;
|
||||
char *metadir, *path;
|
||||
int rv = 0;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
assert(rootdir != NULL);
|
||||
|
||||
metadir = xbps_xasprintf("%s/%s/metadata/%s", rootdir,
|
||||
XBPS_META_PATH, pkgname);
|
||||
if (metadir == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
dirp = opendir(metadir);
|
||||
if (dirp == NULL) {
|
||||
free(metadir);
|
||||
return errno;
|
||||
}
|
||||
|
||||
while ((dp = readdir(dirp)) != NULL) {
|
||||
if ((strcmp(dp->d_name, ".") == 0) ||
|
||||
(strcmp(dp->d_name, "..") == 0))
|
||||
continue;
|
||||
|
||||
path = xbps_xasprintf("%s/%s", metadir, dp->d_name);
|
||||
if (path == NULL) {
|
||||
(void)closedir(dirp);
|
||||
free(metadir);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
if (unlink(path) == -1) {
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
errno, pkgname, version,
|
||||
"%s: [purge] failed to remove metafile `%s': %s",
|
||||
pkgver, path, strerror(errno));
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
(void)closedir(dirp);
|
||||
rv = rmdir(metadir);
|
||||
free(metadir);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int
|
||||
purge_pkgs_cb(prop_object_t obj, void *arg, bool *done)
|
||||
{
|
||||
const char *pkgname;
|
||||
|
||||
(void)arg;
|
||||
(void)done;
|
||||
|
||||
prop_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname);
|
||||
return xbps_purge_pkg(pkgname, true);
|
||||
}
|
||||
int
|
||||
xbps_purge_packages(void)
|
||||
{
|
||||
return xbps_regpkgdb_foreach_pkg_cb(purge_pkgs_cb, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
xbps_purge_pkg(const char *pkgname, bool check_state)
|
||||
{
|
||||
struct xbps_handle *xhp;
|
||||
prop_dictionary_t dict, pkgd;
|
||||
const char *version, *pkgver;
|
||||
char *buf;
|
||||
int rv = 0;
|
||||
pkg_state_t state;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
xhp = xbps_handle_get();
|
||||
|
||||
/*
|
||||
* Firstly let's get the pkg dictionary from regpkgdb.
|
||||
*/
|
||||
pkgd = xbps_find_pkg_in_dict_by_name(xhp->regpkgdb_dictionary,
|
||||
"packages", pkgname);
|
||||
if (pkgd == NULL) {
|
||||
xbps_dbg_printf("[purge] %s: missing pkg dictionary (%s)\n",
|
||||
pkgname, strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
prop_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
|
||||
prop_dictionary_get_cstring_nocopy(pkgd, "version", &version);
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE, 0, pkgname, version, NULL);
|
||||
|
||||
if (check_state) {
|
||||
/*
|
||||
* Skip packages that aren't in "config-files" state.
|
||||
*/
|
||||
if ((rv = xbps_pkg_state_dictionary(pkgd, &state)) != 0)
|
||||
return rv;
|
||||
if (state != XBPS_PKG_STATE_CONFIG_FILES) {
|
||||
xbps_dbg_printf("[purge] %s not in config-files "
|
||||
"state.\n", pkgname);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Remove unmodified configuration files.
|
||||
*/
|
||||
dict = xbps_dictionary_from_metadata_plist(pkgname, XBPS_PKGFILES);
|
||||
if (dict == NULL) {
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
errno, pkgname, version,
|
||||
"%s: [purge] failed to read metafile `%s': %s",
|
||||
pkgver, XBPS_PKGFILES, strerror(errno));
|
||||
if (errno != ENOENT)
|
||||
return errno;
|
||||
} else {
|
||||
if (prop_dictionary_get(dict, "conf_files")) {
|
||||
rv = xbps_remove_pkg_files(dict, "conf_files", pkgver);
|
||||
if (rv != 0) {
|
||||
prop_object_release(dict);
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
rv, pkgname, version,
|
||||
"%s: [purge] failed to remove "
|
||||
"configuration files: %s",
|
||||
pkgver, strerror(rv));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
prop_object_release(dict);
|
||||
}
|
||||
/*
|
||||
* Execute the purge action in REMOVE script (if found).
|
||||
*/
|
||||
if (chdir(xhp->rootdir) == -1) {
|
||||
rv = errno;
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
rv, pkgname, version,
|
||||
"%s: [purge] failed to chdir to rootdir `%s': %s",
|
||||
pkgver, xhp->rootdir, strerror(rv));
|
||||
return rv;
|
||||
}
|
||||
buf = xbps_xasprintf("%s/metadata/%s/REMOVE", XBPS_META_PATH, pkgname);
|
||||
if (buf == NULL) {
|
||||
rv = ENOMEM;
|
||||
return rv;
|
||||
}
|
||||
if (access(buf, X_OK) == 0) {
|
||||
rv = xbps_file_exec(buf, "purge", pkgname, version,
|
||||
"no", xhp->conffile, NULL);
|
||||
if (rv != 0) {
|
||||
free(buf);
|
||||
if (errno && errno != ENOENT) {
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
errno, pkgname, version,
|
||||
"%s: [purge] REMOVE script failed to "
|
||||
"execute purge ACTION: %s",
|
||||
pkgver, strerror(errno));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
/*
|
||||
* Remove metadata dir and unregister package.
|
||||
*/
|
||||
if ((rv = remove_pkg_metadata(pkgname, version, pkgver,
|
||||
xhp->rootdir)) != 0) {
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
rv, pkgname, version,
|
||||
"%s: [purge] failed to remove metadata files: %s",
|
||||
pkgver, strerror(rv));
|
||||
if (rv != ENOENT)
|
||||
return rv;
|
||||
}
|
||||
if ((rv = xbps_unregister_pkg(pkgname, version)) != 0)
|
||||
return rv;
|
||||
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_DONE, 0, pkgname, version, NULL);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -43,20 +43,15 @@ int
|
||||
xbps_register_pkg(prop_dictionary_t pkgrd)
|
||||
{
|
||||
struct xbps_handle *xhp;
|
||||
prop_dictionary_t dict, pkgd;
|
||||
prop_array_t array, provides = NULL, reqby;
|
||||
prop_dictionary_t pkgd;
|
||||
prop_array_t provides, reqby, array;
|
||||
const char *pkgname, *version, *desc, *pkgver;
|
||||
char *plist;
|
||||
int rv = 0;
|
||||
bool autoinst = false;
|
||||
|
||||
assert(prop_object_type(pkgrd) == PROP_TYPE_DICTIONARY);
|
||||
|
||||
xhp = xbps_handle_get();
|
||||
plist = xbps_xasprintf("%s/%s/%s", xhp->rootdir,
|
||||
XBPS_META_PATH, XBPS_REGPKGDB);
|
||||
if (plist == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
prop_dictionary_get_cstring_nocopy(pkgrd, "pkgname", &pkgname);
|
||||
prop_dictionary_get_cstring_nocopy(pkgrd, "version", &version);
|
||||
@@ -72,80 +67,72 @@ xbps_register_pkg(prop_dictionary_t pkgrd)
|
||||
assert(version != NULL);
|
||||
assert(desc != NULL);
|
||||
assert(pkgver != NULL);
|
||||
assert(xhp->regpkgdb != NULL);
|
||||
|
||||
if ((dict = prop_dictionary_internalize_from_zfile(plist)) != NULL) {
|
||||
pkgd = xbps_find_pkg_in_dict_by_name(dict,
|
||||
"packages", pkgname);
|
||||
if (pkgd == NULL) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
if (!prop_dictionary_set_cstring_nocopy(pkgd,
|
||||
"version", version)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (!prop_dictionary_set_cstring_nocopy(pkgd,
|
||||
"pkgver", pkgver)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (!prop_dictionary_set_cstring_nocopy(pkgd,
|
||||
"short_desc", desc)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (reqby && !prop_dictionary_set(pkgd, "requiredby", reqby)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
prop_dictionary_get_bool(pkgd, "automatic-install", &autoinst);
|
||||
if (xhp->install_reason_auto)
|
||||
autoinst = true;
|
||||
else if (xhp->install_reason_manual)
|
||||
autoinst = false;
|
||||
|
||||
if (!prop_dictionary_set_bool(pkgd,
|
||||
"automatic-install", autoinst)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (provides) {
|
||||
if (!prop_dictionary_set(pkgd, "provides", provides)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Add the requiredby objects for dependent packages.
|
||||
*/
|
||||
if (pkgrd && xbps_pkg_has_rundeps(pkgrd)) {
|
||||
array = prop_dictionary_get(dict, "packages");
|
||||
if (array == NULL) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if ((rv = xbps_requiredby_pkg_add(array, pkgrd)) != 0)
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* Write plist file to storage.
|
||||
*/
|
||||
if (!prop_dictionary_externalize_to_zfile(dict, plist)) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
free(plist);
|
||||
return ENOENT;
|
||||
pkgd = xbps_regpkgdb_get_pkgd(pkgname, false);
|
||||
if (pkgd == NULL) {
|
||||
rv = ENOENT;
|
||||
goto out;
|
||||
}
|
||||
if (!prop_dictionary_set_cstring_nocopy(pkgd,
|
||||
"version", version)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (!prop_dictionary_set_cstring_nocopy(pkgd,
|
||||
"pkgver", pkgver)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (!prop_dictionary_set_cstring_nocopy(pkgd,
|
||||
"short_desc", desc)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (reqby && !prop_dictionary_set(pkgd, "requiredby", reqby)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
prop_dictionary_get_bool(pkgd, "automatic-install", &autoinst);
|
||||
if (xhp->install_reason_auto)
|
||||
autoinst = true;
|
||||
else if (xhp->install_reason_manual)
|
||||
autoinst = false;
|
||||
|
||||
if (!prop_dictionary_set_bool(pkgd,
|
||||
"automatic-install", autoinst)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (provides) {
|
||||
if (!prop_dictionary_set(pkgd, "provides", provides)) {
|
||||
prop_object_release(pkgd);
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
array = prop_dictionary_get(xhp->regpkgdb, "packages");
|
||||
rv = xbps_array_replace_dict_by_name(array, pkgd, pkgname);
|
||||
if (rv != 0) {
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* Add the requiredby objects for dependent packages.
|
||||
*/
|
||||
if (pkgrd && xbps_pkg_has_rundeps(pkgrd)) {
|
||||
if ((rv = xbps_requiredby_pkg_add(xhp, pkgrd)) != 0) {
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
prop_object_release(pkgd);
|
||||
|
||||
out:
|
||||
if (rv != 0) {
|
||||
xbps_set_cb_state(XBPS_STATE_REGISTER_FAIL,
|
||||
@@ -153,8 +140,6 @@ out:
|
||||
"%s: failed to register package: %s",
|
||||
pkgver, strerror(rv));
|
||||
}
|
||||
prop_object_release(dict);
|
||||
free(plist);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -163,31 +148,24 @@ int
|
||||
xbps_unregister_pkg(const char *pkgname, const char *version)
|
||||
{
|
||||
struct xbps_handle *xhp;
|
||||
char *plist;
|
||||
int rv = 0;
|
||||
int rv;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
|
||||
xhp = xbps_handle_get();
|
||||
if ((rv = xbps_regpkgdb_dictionary_init(xhp)) != 0)
|
||||
return rv;
|
||||
|
||||
xbps_set_cb_state(XBPS_STATE_UNREGISTER, 0, pkgname, version, NULL);
|
||||
|
||||
xhp = xbps_handle_get();
|
||||
plist = xbps_xasprintf("%s/%s/%s", xhp->rootdir,
|
||||
XBPS_META_PATH, XBPS_REGPKGDB);
|
||||
if (plist == NULL) {
|
||||
rv = ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
if (!xbps_remove_pkg_dict_from_plist_by_name(pkgname, plist)) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
if (rv != 0) {
|
||||
if (!xbps_remove_pkg_from_dict_by_name(xhp->regpkgdb,
|
||||
"packages", pkgname)) {
|
||||
xbps_set_cb_state(XBPS_STATE_UNREGISTER_FAIL,
|
||||
rv, pkgname, version,
|
||||
errno, pkgname, version,
|
||||
"%s: failed to unregister package: %s",
|
||||
pkgname, strerror(rv));
|
||||
pkgname, strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
free(plist);
|
||||
return rv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -42,14 +42,18 @@
|
||||
* files. Package removal steps:
|
||||
* -# Its <b>pre-remove</b> target specified in the REMOVE script
|
||||
* will be executed.
|
||||
* -# Its files, dirs and links will be removed. Modified files (not
|
||||
* matching its sha256 hash) are preserved, unless
|
||||
* -# Its links, files, conf_files and dirs will be removed.
|
||||
* Modified files (not matchings its sha256 hash) are preserved, unless
|
||||
* XBPS_FLAG_FORCE_REMOVE_FILES flag is set via xbps_init::flags member.
|
||||
* -# Its <b>post-remove</b> target specified in the REMOVE script
|
||||
* will be executed.
|
||||
* -# Its requiredby objects will be removed from the installed packages
|
||||
* database.
|
||||
* -# Its state will be changed to XBPS_PKG_STATE_CONFIG_FILES.
|
||||
* -# Its state will be changed to XBPS_PKG_STATE_HALF_UNPACKED.
|
||||
* -# Its <b>purge-remove</b> target specified in the REMOVE script
|
||||
* will be executed.
|
||||
* -# Its package metadata directory will be removed.
|
||||
* -# Package will be unregistered from package database.
|
||||
*
|
||||
* @note
|
||||
* -# If a package is going to be updated, only steps <b>1</b> and <b>4</b>
|
||||
@@ -69,6 +73,58 @@
|
||||
* Text inside of white boxes are the key associated with the object, its
|
||||
* data type is specified on its edge, i.e string, array, integer, dictionary.
|
||||
*/
|
||||
static int
|
||||
remove_pkg_metadata(const char *pkgname,
|
||||
const char *version,
|
||||
const char *pkgver,
|
||||
const char *rootdir)
|
||||
{
|
||||
struct dirent *dp;
|
||||
DIR *dirp;
|
||||
char *metadir, *path;
|
||||
int rv = 0;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
assert(rootdir != NULL);
|
||||
|
||||
metadir = xbps_xasprintf("%s/%s/metadata/%s", rootdir,
|
||||
XBPS_META_PATH, pkgname);
|
||||
if (metadir == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
dirp = opendir(metadir);
|
||||
if (dirp == NULL) {
|
||||
free(metadir);
|
||||
return errno;
|
||||
}
|
||||
|
||||
while ((dp = readdir(dirp)) != NULL) {
|
||||
if ((strcmp(dp->d_name, ".") == 0) ||
|
||||
(strcmp(dp->d_name, "..") == 0))
|
||||
continue;
|
||||
|
||||
path = xbps_xasprintf("%s/%s", metadir, dp->d_name);
|
||||
if (path == NULL) {
|
||||
(void)closedir(dirp);
|
||||
free(metadir);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
if (unlink(path) == -1) {
|
||||
xbps_set_cb_state(XBPS_STATE_PURGE_FAIL,
|
||||
errno, pkgname, version,
|
||||
"%s: [purge] failed to remove metafile `%s': %s",
|
||||
pkgver, path, strerror(errno));
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
(void)closedir(dirp);
|
||||
rv = rmdir(metadir);
|
||||
free(metadir);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
xbps_remove_pkg_files(prop_dictionary_t dict,
|
||||
const char *key,
|
||||
@@ -87,9 +143,8 @@ xbps_remove_pkg_files(prop_dictionary_t dict,
|
||||
xhp = xbps_handle_get();
|
||||
|
||||
array = prop_dictionary_get(dict, key);
|
||||
if (array == NULL)
|
||||
return EINVAL;
|
||||
else if (prop_array_count(array) == 0)
|
||||
if ((prop_object_type(array) != PROP_TYPE_ARRAY) ||
|
||||
prop_array_count(array) == 0)
|
||||
return 0;
|
||||
|
||||
iter = xbps_array_iter_from_dict(dict, key);
|
||||
@@ -174,6 +229,7 @@ xbps_remove_pkg_files(prop_dictionary_t dict,
|
||||
errno, pkgname, version,
|
||||
"%s: failed to remove %s `%s': %s", pkgver,
|
||||
curobj, file, strerror(errno));
|
||||
errno = 0;
|
||||
} else {
|
||||
/* success */
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_FILE,
|
||||
@@ -193,47 +249,47 @@ int
|
||||
xbps_remove_pkg(const char *pkgname, const char *version, bool update)
|
||||
{
|
||||
struct xbps_handle *xhp;
|
||||
prop_dictionary_t dict;
|
||||
char *buf, *pkgver;
|
||||
prop_dictionary_t pkgd = NULL;
|
||||
char *buf = NULL, *pkgver = NULL;
|
||||
int rv = 0;
|
||||
bool rmfile_exists = false;
|
||||
pkg_state_t state = 0;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
assert(version != NULL);
|
||||
|
||||
xhp = xbps_handle_get();
|
||||
/*
|
||||
* Check if pkg is installed before anything else.
|
||||
*/
|
||||
if (!xbps_check_is_installed_pkg_by_name(pkgname))
|
||||
return ENOENT;
|
||||
|
||||
pkgver = xbps_xasprintf("%s-%s", pkgname, version);
|
||||
if (pkgver == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
if (!update)
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE, 0, pkgname, version, NULL);
|
||||
|
||||
buf = xbps_xasprintf("%s/metadata/%s/REMOVE",
|
||||
XBPS_META_PATH, pkgname);
|
||||
if (buf == NULL) {
|
||||
rv = ENOMEM;
|
||||
free(pkgver);
|
||||
return rv;
|
||||
goto out;
|
||||
}
|
||||
|
||||
pkgver = xbps_xasprintf("%s-%s", pkgname, version);
|
||||
if (pkgver == NULL) {
|
||||
rv = ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((rv = xbps_pkg_state_installed(pkgname, &state)) != 0)
|
||||
goto out;
|
||||
|
||||
if (!update)
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE, 0, pkgname, version, NULL);
|
||||
|
||||
if (chdir(xhp->rootdir) == -1) {
|
||||
rv = errno;
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_FAIL,
|
||||
rv, pkgname, version,
|
||||
"%s: [remove] failed to chdir to rootdir `%s': %s",
|
||||
pkgver, xhp->rootdir, strerror(rv));
|
||||
free(buf);
|
||||
free(pkgver);
|
||||
return rv;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If package was "half-removed", remove it fully. */
|
||||
if (state == XBPS_PKG_STATE_HALF_REMOVED)
|
||||
goto purge;
|
||||
/*
|
||||
* Run the pre remove action.
|
||||
*/
|
||||
@@ -246,18 +302,15 @@ xbps_remove_pkg(const char *pkgname, const char *version, bool update)
|
||||
"%s: [remove] REMOVE script failed to "
|
||||
"execute pre ACTION: %s",
|
||||
pkgver, strerror(errno));
|
||||
free(pkgver);
|
||||
free(buf);
|
||||
return errno;
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
if (errno != ENOENT) {
|
||||
free(pkgver);
|
||||
free(buf);
|
||||
return errno;
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If updating a package, we just need to execute the current
|
||||
* pre-remove action target, unregister its requiredby entries and
|
||||
@@ -269,38 +322,26 @@ xbps_remove_pkg(const char *pkgname, const char *version, bool update)
|
||||
return xbps_requiredby_pkg_remove(pkgname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove links, files and dirs.
|
||||
*/
|
||||
dict = xbps_dictionary_from_metadata_plist(pkgname, XBPS_PKGFILES);
|
||||
if (dict == NULL) {
|
||||
free(pkgver);
|
||||
free(buf);
|
||||
return errno;
|
||||
pkgd = xbps_dictionary_from_metadata_plist(pkgname, XBPS_PKGFILES);
|
||||
if (pkgd == NULL) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Remove links */
|
||||
if ((rv = xbps_remove_pkg_files(dict, "links", pkgver)) != 0) {
|
||||
prop_object_release(dict);
|
||||
free(buf);
|
||||
free(pkgver);
|
||||
return rv;
|
||||
}
|
||||
if ((rv = xbps_remove_pkg_files(pkgd, "links", pkgver)) != 0)
|
||||
goto out;
|
||||
|
||||
/* Remove regular files */
|
||||
if ((rv = xbps_remove_pkg_files(dict, "files", pkgver)) != 0) {
|
||||
prop_object_release(dict);
|
||||
free(buf);
|
||||
free(pkgver);
|
||||
return rv;
|
||||
}
|
||||
if ((rv = xbps_remove_pkg_files(pkgd, "files", pkgver)) != 0)
|
||||
goto out;
|
||||
|
||||
/* Remove configuration files */
|
||||
if ((rv = xbps_remove_pkg_files(pkgd, "conf_files", pkgver)) != 0)
|
||||
goto out;
|
||||
|
||||
/* Remove dirs */
|
||||
if ((rv = xbps_remove_pkg_files(dict, "dirs", pkgver)) != 0) {
|
||||
prop_object_release(dict);
|
||||
free(buf);
|
||||
free(pkgver);
|
||||
return rv;
|
||||
}
|
||||
prop_object_release(dict);
|
||||
if ((rv = xbps_remove_pkg_files(pkgd, "dirs", pkgver)) != 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Execute the post REMOVE action if file exists and we aren't
|
||||
@@ -313,12 +354,9 @@ xbps_remove_pkg(const char *pkgname, const char *version, bool update)
|
||||
errno, pkgname, version,
|
||||
"%s: [remove] REMOVE script failed to execute "
|
||||
"post ACTION: %s", pkgver, strerror(errno));
|
||||
free(buf);
|
||||
free(pkgver);
|
||||
return errno;
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
free(buf);
|
||||
|
||||
/*
|
||||
* Update the requiredby array of all required dependencies.
|
||||
*/
|
||||
@@ -327,25 +365,64 @@ xbps_remove_pkg(const char *pkgname, const char *version, bool update)
|
||||
rv, pkgname, version,
|
||||
"%s: [remove] failed to remove requiredby entries: %s",
|
||||
pkgver, strerror(rv));
|
||||
free(pkgver);
|
||||
return rv;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set package state to "config-files".
|
||||
* Set package state to "half-removed".
|
||||
*/
|
||||
rv = xbps_set_pkg_state_installed(pkgname, version, pkgver,
|
||||
XBPS_PKG_STATE_CONFIG_FILES);
|
||||
XBPS_PKG_STATE_HALF_REMOVED);
|
||||
if (rv != 0) {
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_FAIL,
|
||||
rv, pkgname, version,
|
||||
"%s: [remove] failed to set state to config-files: %s",
|
||||
"%s: [remove] failed to set state to half-removed: %s",
|
||||
pkgver, strerror(rv));
|
||||
} else {
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_DONE,
|
||||
0, pkgname, version, NULL);
|
||||
goto out;
|
||||
}
|
||||
free(pkgver);
|
||||
|
||||
purge:
|
||||
/*
|
||||
* Execute the purge REMOVE action if file exists.
|
||||
*/
|
||||
if (access(buf, X_OK) == 0) {
|
||||
if (xbps_file_exec(buf, "purge", pkgname, version, "no",
|
||||
xhp->conffile, NULL) != 0) {
|
||||
rv = errno;
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_FAIL,
|
||||
errno, pkgname, version,
|
||||
"%s: REMOVE script failed to execute "
|
||||
"purge ACTION: %s", pkgver, strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Remove package metadata directory.
|
||||
*/
|
||||
rv = remove_pkg_metadata(pkgname, version, pkgver, xhp->rootdir);
|
||||
if (rv != 0) {
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_FAIL,
|
||||
rv, pkgname, version,
|
||||
"%s: failed to remove metadata files: %s",
|
||||
pkgver, strerror(rv));
|
||||
if (rv != ENOENT)
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* Unregister package from regpkgdb.
|
||||
*/
|
||||
if ((rv = xbps_unregister_pkg(pkgname, version)) != 0)
|
||||
goto out;
|
||||
|
||||
xbps_set_cb_state(XBPS_STATE_REMOVE_DONE,
|
||||
0, pkgname, version, NULL);
|
||||
|
||||
out:
|
||||
if (buf != NULL)
|
||||
free(buf);
|
||||
if (pkgver != NULL)
|
||||
free(pkgver);
|
||||
if (pkgd != NULL)
|
||||
prop_object_release(pkgd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -91,9 +91,10 @@ remove_pkg_from_reqby(prop_object_t obj, void *arg, bool *loop_done)
|
||||
if (reqby == NULL || prop_array_count(reqby) == 0)
|
||||
return 0;
|
||||
|
||||
if (xbps_match_pkgname_in_array(reqby, pkgname))
|
||||
if (xbps_match_pkgname_in_array(reqby, pkgname)) {
|
||||
if (!xbps_remove_pkgname_from_array(reqby, pkgname))
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -101,46 +102,12 @@ remove_pkg_from_reqby(prop_object_t obj, void *arg, bool *loop_done)
|
||||
int HIDDEN
|
||||
xbps_requiredby_pkg_remove(const char *pkgname)
|
||||
{
|
||||
struct xbps_handle *xhp;
|
||||
prop_dictionary_t dict;
|
||||
char *plist;
|
||||
int rv = 0;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
|
||||
xhp = xbps_handle_get();
|
||||
plist = xbps_xasprintf("%s/%s/%s", xhp->rootdir,
|
||||
XBPS_META_PATH, XBPS_REGPKGDB);
|
||||
if (plist == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
if ((dict = prop_dictionary_internalize_from_zfile(plist)) == NULL) {
|
||||
free(plist);
|
||||
xbps_dbg_printf("[reqby-rm] cannot internalize "
|
||||
"regpkgdb plist for '%s': %s\n", pkgname, strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
rv = xbps_callback_array_iter_in_dict(dict, "packages",
|
||||
remove_pkg_from_reqby, __UNCONST(pkgname));
|
||||
if (rv != 0)
|
||||
goto out;
|
||||
|
||||
if (!prop_dictionary_externalize_to_zfile(dict, plist)) {
|
||||
xbps_dbg_printf("[reqby-rm] cannot externalize plist for "
|
||||
"'%s': %s\n", pkgname, strerror(errno));
|
||||
rv = errno;
|
||||
}
|
||||
|
||||
out:
|
||||
prop_object_release(dict);
|
||||
free(plist);
|
||||
|
||||
return rv;
|
||||
return xbps_regpkgdb_foreach_pkg_cb(remove_pkg_from_reqby, __UNCONST(pkgname));
|
||||
}
|
||||
|
||||
int HIDDEN
|
||||
xbps_requiredby_pkg_add(prop_array_t pkgs_array, prop_dictionary_t pkgd)
|
||||
xbps_requiredby_pkg_add(struct xbps_handle *xhp, prop_dictionary_t pkgd)
|
||||
{
|
||||
prop_array_t pkg_rdeps;
|
||||
prop_object_t obj, pkgd_regpkgdb;
|
||||
@@ -148,7 +115,6 @@ xbps_requiredby_pkg_add(prop_array_t pkgs_array, prop_dictionary_t pkgd)
|
||||
const char *pkgver, *str;
|
||||
int rv = 0;
|
||||
|
||||
assert(prop_object_type(pkgs_array) == PROP_TYPE_ARRAY);
|
||||
assert(prop_object_type(pkgd) == PROP_TYPE_DICTIONARY);
|
||||
|
||||
prop_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
|
||||
@@ -166,8 +132,8 @@ xbps_requiredby_pkg_add(prop_array_t pkgs_array, prop_dictionary_t pkgd)
|
||||
rv = EINVAL;
|
||||
break;
|
||||
}
|
||||
pkgd_regpkgdb =
|
||||
xbps_find_virtualpkg_in_array_by_pattern(pkgs_array, str);
|
||||
pkgd_regpkgdb = xbps_find_virtualpkg_in_dict_by_pattern(
|
||||
xhp->regpkgdb, "packages", str);
|
||||
if (pkgd_regpkgdb == NULL)
|
||||
return EINVAL;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ static const struct state states[] = {
|
||||
{ "unpacked", XBPS_PKG_STATE_UNPACKED },
|
||||
{ "installed", XBPS_PKG_STATE_INSTALLED },
|
||||
{ "broken", XBPS_PKG_STATE_BROKEN },
|
||||
{ "config-files", XBPS_PKG_STATE_CONFIG_FILES },
|
||||
{ "half-removed", XBPS_PKG_STATE_HALF_REMOVED },
|
||||
{ "not-installed", XBPS_PKG_STATE_NOT_INSTALLED },
|
||||
{ "half-unpacked", XBPS_PKG_STATE_HALF_UNPACKED },
|
||||
{ NULL, 0 }
|
||||
@@ -68,7 +68,7 @@ set_new_state(prop_dictionary_t dict, pkg_state_t state)
|
||||
break;
|
||||
|
||||
if (stp->string == NULL)
|
||||
return -1;
|
||||
return EINVAL;
|
||||
|
||||
if (!prop_dictionary_set_cstring_nocopy(dict, "state", stp->string))
|
||||
return EINVAL;
|
||||
@@ -110,7 +110,7 @@ xbps_pkg_state_installed(const char *pkgname, pkg_state_t *state)
|
||||
assert(pkgname != NULL);
|
||||
assert(state != NULL);
|
||||
|
||||
pkgd = xbps_find_pkg_dict_installed(pkgname, false);
|
||||
pkgd = xbps_regpkgdb_get_pkgd(pkgname, false);
|
||||
if (pkgd == NULL)
|
||||
return ENOENT;
|
||||
|
||||
@@ -170,127 +170,80 @@ xbps_set_pkg_state_installed(const char *pkgname,
|
||||
pkg_state_t state)
|
||||
{
|
||||
struct xbps_handle *xhp;
|
||||
prop_dictionary_t dict = NULL, pkgd;
|
||||
prop_dictionary_t pkgd;
|
||||
prop_array_t array;
|
||||
char *metadir, *plist;
|
||||
int rv = 0;
|
||||
bool newpkg = false;
|
||||
int rv;
|
||||
|
||||
assert(pkgname != NULL);
|
||||
xhp = xbps_handle_get();
|
||||
|
||||
metadir = xbps_xasprintf("%s/%s", xhp->rootdir, XBPS_META_PATH);
|
||||
if (metadir == NULL)
|
||||
return ENOMEM;
|
||||
plist = xbps_xasprintf("%s/%s", metadir, XBPS_REGPKGDB);
|
||||
if (plist == NULL) {
|
||||
free(metadir);
|
||||
return ENOMEM;
|
||||
}
|
||||
if ((dict = prop_dictionary_internalize_from_zfile(plist)) == NULL) {
|
||||
dict = prop_dictionary_create();
|
||||
if (dict == NULL) {
|
||||
rv = ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
if (xhp->regpkgdb == NULL) {
|
||||
xhp->regpkgdb = prop_dictionary_create();
|
||||
if (xhp->regpkgdb == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
array = prop_array_create();
|
||||
if (array == NULL) {
|
||||
rv = ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
if (array == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
pkgd = prop_dictionary_create();
|
||||
if (pkgd == NULL) {
|
||||
rv = ENOMEM;
|
||||
prop_object_release(array);
|
||||
goto out;
|
||||
return ENOMEM;
|
||||
}
|
||||
if ((rv = set_pkg_objs(pkgd, pkgname, version, pkgver)) != 0) {
|
||||
prop_object_release(array);
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
return rv;
|
||||
}
|
||||
if ((rv = set_new_state(pkgd, state)) != 0) {
|
||||
prop_object_release(array);
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
return rv;
|
||||
}
|
||||
if (!xbps_add_obj_to_array(array, pkgd)) {
|
||||
rv = EINVAL;
|
||||
prop_object_release(array);
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
return EINVAL;
|
||||
}
|
||||
if (!xbps_add_obj_to_dict(dict, array, "packages")) {
|
||||
rv = EINVAL;
|
||||
if (!xbps_add_obj_to_dict(xhp->regpkgdb, array, "packages")) {
|
||||
prop_object_release(array);
|
||||
goto out;
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
} else {
|
||||
pkgd = xbps_find_pkg_in_dict_by_name(dict,
|
||||
"packages", pkgname);
|
||||
pkgd = xbps_regpkgdb_get_pkgd(pkgname, false);
|
||||
if (pkgd == NULL) {
|
||||
if (errno && errno != ENOENT) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
newpkg = true;
|
||||
pkgd = prop_dictionary_create();
|
||||
if ((rv = set_pkg_objs(pkgd, pkgname,
|
||||
version, pkgver)) != 0) {
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
array = prop_dictionary_get(dict, "packages");
|
||||
if (array == NULL) {
|
||||
array = prop_array_create();
|
||||
if (!prop_dictionary_set(dict, "packages", array)) {
|
||||
rv = EINVAL;
|
||||
if (newpkg)
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
if ((rv = set_new_state(pkgd, state)) != 0) {
|
||||
if (newpkg)
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
return rv;
|
||||
}
|
||||
if (newpkg && !xbps_add_obj_to_array(array, pkgd)) {
|
||||
rv = EINVAL;
|
||||
prop_object_release(pkgd);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create metadir if doesn't exist */
|
||||
if (access(metadir, X_OK) == -1) {
|
||||
if (errno == ENOENT) {
|
||||
if (xbps_mkpath(metadir, 0755) != 0) {
|
||||
xbps_dbg_printf("[pkgstate] failed to create "
|
||||
"metadir %s: %s\n", metadir,
|
||||
strerror(errno));
|
||||
rv = errno;
|
||||
goto out;
|
||||
array = prop_dictionary_get(xhp->regpkgdb, "packages");
|
||||
if (newpkg) {
|
||||
if (!xbps_add_obj_to_array(array, pkgd)) {
|
||||
prop_object_release(pkgd);
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Externalize regpkgdb plist file */
|
||||
if (!prop_dictionary_externalize_to_zfile(dict, plist)) {
|
||||
rv = errno;
|
||||
xbps_dbg_printf("[pkgstate] cannot write plist '%s': %s\n",
|
||||
plist, strerror(errno));
|
||||
}
|
||||
} else {
|
||||
if ((rv = xbps_array_replace_dict_by_name(array,
|
||||
pkgd, pkgname)) != 0)
|
||||
return rv;
|
||||
|
||||
out:
|
||||
if (prop_object_type(dict) == PROP_TYPE_DICTIONARY)
|
||||
prop_object_release(dict);
|
||||
if (metadir)
|
||||
free(metadir);
|
||||
if (plist)
|
||||
free(plist);
|
||||
prop_object_release(pkgd);
|
||||
}
|
||||
if (!prop_dictionary_set(xhp->regpkgdb, "packages", array))
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,16 @@ unpack_archive(prop_dictionary_t pkg_repod, struct archive *ar)
|
||||
xucd->entry_extract_count = 0;
|
||||
xucd->entry_total_count = 0;
|
||||
}
|
||||
|
||||
if (access(xhp->rootdir, R_OK) == -1) {
|
||||
if (errno != ENOENT) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
if (xbps_mkpath(xhp->rootdir, 0750) == -1) {
|
||||
rv = errno;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (chdir(xhp->rootdir) == -1) {
|
||||
xbps_set_cb_state(XBPS_STATE_UNPACK_FAIL,
|
||||
errno, pkgname, version,
|
||||
|
||||
@@ -231,7 +231,7 @@ xbps_dictionary_from_metadata_plist(const char *pkgname,
|
||||
|
||||
if (access(plistf, R_OK) == -1) {
|
||||
pkgd = xbps_find_virtualpkg_dict_installed(pkgname, false);
|
||||
if (pkgd) {
|
||||
if (prop_object_type(pkgd) == PROP_TYPE_DICTIONARY) {
|
||||
free(plistf);
|
||||
prop_dictionary_get_cstring_nocopy(pkgd,
|
||||
"pkgname", &savedpkgname);
|
||||
|
||||
@@ -336,15 +336,15 @@ find_pkgd_installed(const char *str, bool bypattern, bool virtual)
|
||||
|
||||
/* try normal pkg */
|
||||
if (virtual == false) {
|
||||
pkgd = find_pkg_in_dict(xhp->regpkgdb_dictionary,
|
||||
pkgd = find_pkg_in_dict(xhp->regpkgdb,
|
||||
"packages", str, bypattern, false);
|
||||
} else {
|
||||
/* virtual pkg set by user in conf */
|
||||
pkgd = find_virtualpkg_user_in_dict(xhp->regpkgdb_dictionary,
|
||||
pkgd = find_virtualpkg_user_in_dict(xhp->regpkgdb,
|
||||
"packages", str, bypattern);
|
||||
if (pkgd == NULL) {
|
||||
/* any virtual pkg in dictionary matching pattern */
|
||||
pkgd = find_pkg_in_dict(xhp->regpkgdb_dictionary,
|
||||
pkgd = find_pkg_in_dict(xhp->regpkgdb,
|
||||
"packages", str, bypattern, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,16 +42,20 @@
|
||||
static bool
|
||||
remove_string_from_array(prop_array_t array, const char *str, int mode)
|
||||
{
|
||||
prop_object_iterator_t iter;
|
||||
prop_object_t obj;
|
||||
const char *curname, *pkgdep;
|
||||
char *curpkgname;
|
||||
size_t i, idx = 0;
|
||||
size_t idx = 0;
|
||||
bool found = false;
|
||||
|
||||
assert(prop_object_type(array) == PROP_TYPE_ARRAY);
|
||||
|
||||
for (i = 0; i < prop_array_count(array); i++) {
|
||||
obj = prop_array_get(array, i);
|
||||
iter = prop_array_iterator(array);
|
||||
if (iter == NULL)
|
||||
return false;
|
||||
|
||||
while ((obj = prop_object_iterator_next(iter))) {
|
||||
if (mode == 0) {
|
||||
/* exact match, obj is a string */
|
||||
if (prop_string_equals_cstring(obj, str)) {
|
||||
@@ -81,8 +85,12 @@ remove_string_from_array(prop_array_t array, const char *str, int mode)
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
if (!found)
|
||||
prop_object_iterator_release(iter);
|
||||
|
||||
if (!found) {
|
||||
errno = ENOENT;
|
||||
return false;
|
||||
}
|
||||
|
||||
prop_array_remove(array, idx);
|
||||
return true;
|
||||
@@ -121,7 +129,10 @@ xbps_remove_pkg_from_dict_by_name(prop_dictionary_t dict,
|
||||
if (array == NULL)
|
||||
return false;
|
||||
|
||||
return xbps_remove_pkg_from_array_by_name(array, pkgname);
|
||||
if (!xbps_remove_pkg_from_array_by_name(array, pkgname))
|
||||
return false;
|
||||
|
||||
return prop_dictionary_set(dict, key, array);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -56,45 +56,94 @@
|
||||
* dictionary.
|
||||
*/
|
||||
|
||||
static bool regpkgdb_initialized;
|
||||
|
||||
int HIDDEN
|
||||
xbps_regpkgdb_dictionary_init(struct xbps_handle *xhp)
|
||||
{
|
||||
char *plist;
|
||||
int rv;
|
||||
|
||||
if (regpkgdb_initialized)
|
||||
assert(xhp != NULL);
|
||||
|
||||
if (xhp->regpkgdb != NULL)
|
||||
return 0;
|
||||
|
||||
rv = xbps_regpkgdb_update(xhp, false);
|
||||
if (rv != 0) {
|
||||
if (rv != ENOENT)
|
||||
xbps_dbg_printf("[regpkgdb] cannot internalize "
|
||||
"regpkgdb dictionary: %s\n", strerror(rv));
|
||||
|
||||
return rv;
|
||||
}
|
||||
xbps_dbg_printf("[regpkgdb] initialized ok.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
xbps_regpkgdb_update(struct xbps_handle *xhp, bool flush)
|
||||
{
|
||||
char *plist, *metadir;
|
||||
int rv = 0;
|
||||
|
||||
plist = xbps_xasprintf("%s/%s/%s", xhp->rootdir,
|
||||
XBPS_META_PATH, XBPS_REGPKGDB);
|
||||
if (plist == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
xhp->regpkgdb_dictionary =
|
||||
prop_dictionary_internalize_from_zfile(plist);
|
||||
if (xhp->regpkgdb_dictionary == NULL) {
|
||||
free(plist);
|
||||
if (errno != ENOENT)
|
||||
xbps_dbg_printf("[regpkgdb] cannot internalize "
|
||||
"regpkgdb dictionary: %s\n", strerror(errno));
|
||||
return errno;
|
||||
if (xhp->regpkgdb != NULL && flush) {
|
||||
metadir = xbps_xasprintf("%s/%s", xhp->rootdir,
|
||||
XBPS_META_PATH);
|
||||
if (metadir == NULL) {
|
||||
free(plist);
|
||||
return ENOMEM;
|
||||
}
|
||||
/* Create metadir if doesn't exist */
|
||||
if (access(metadir, X_OK) == -1) {
|
||||
if (errno == ENOENT) {
|
||||
if (xbps_mkpath(metadir, 0755) != 0) {
|
||||
xbps_dbg_printf("[regpkgdb] failed to "
|
||||
"create metadir %s: %s\n", metadir,
|
||||
strerror(errno));
|
||||
rv = errno;
|
||||
free(metadir);
|
||||
free(plist);
|
||||
return rv;
|
||||
}
|
||||
} else {
|
||||
free(plist);
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
free(metadir);
|
||||
/* flush dictionary to storage */
|
||||
if (!prop_dictionary_externalize_to_zfile(xhp->regpkgdb,
|
||||
plist)) {
|
||||
free(plist);
|
||||
return errno;
|
||||
}
|
||||
prop_object_release(xhp->regpkgdb);
|
||||
xhp->regpkgdb = NULL;
|
||||
}
|
||||
free(plist);
|
||||
regpkgdb_initialized = true;
|
||||
xbps_dbg_printf("[regpkgdb] initialized ok.\n");
|
||||
/* update copy in memory */
|
||||
xhp->regpkgdb = prop_dictionary_internalize_from_zfile(plist);
|
||||
if (xhp->regpkgdb == NULL)
|
||||
rv = errno;
|
||||
|
||||
return 0;
|
||||
free(plist);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void HIDDEN
|
||||
xbps_regpkgdb_dictionary_release(struct xbps_handle *xhp)
|
||||
{
|
||||
if (!regpkgdb_initialized)
|
||||
assert(xhp != NULL);
|
||||
|
||||
if (xhp->regpkgdb == NULL)
|
||||
return;
|
||||
|
||||
prop_object_release(xhp->regpkgdb_dictionary);
|
||||
regpkgdb_initialized = false;
|
||||
prop_object_release(xhp->regpkgdb);
|
||||
xhp->regpkgdb = NULL;
|
||||
xbps_dbg_printf("[regpkgdb] released ok.\n");
|
||||
}
|
||||
|
||||
@@ -103,36 +152,19 @@ foreach_pkg_cb(int (*fn)(prop_object_t, void *, bool *),
|
||||
void *arg,
|
||||
bool reverse)
|
||||
{
|
||||
prop_array_t array;
|
||||
prop_object_t obj;
|
||||
struct xbps_handle *xhp = xbps_handle_get();
|
||||
size_t i, cnt;
|
||||
int rv;
|
||||
bool done = false;
|
||||
|
||||
/* initialize regpkgdb */
|
||||
if ((rv = xbps_regpkgdb_dictionary_init(xhp)) != 0)
|
||||
return rv;
|
||||
|
||||
array = prop_dictionary_get(xhp->regpkgdb_dictionary, "packages");
|
||||
if (prop_object_type(array) != PROP_TYPE_ARRAY)
|
||||
return EINVAL;
|
||||
|
||||
cnt = prop_array_count(array);
|
||||
if (reverse) {
|
||||
while (cnt--) {
|
||||
obj = prop_array_get(array, cnt);
|
||||
rv = (*fn)(obj, arg, &done);
|
||||
if (rv != 0 || done)
|
||||
break;
|
||||
}
|
||||
rv = xbps_callback_array_iter_reverse_in_dict(
|
||||
xhp->regpkgdb, "packages", fn, arg);
|
||||
} else {
|
||||
for (i = 0; i < cnt; i++) {
|
||||
obj = prop_array_get(array, i);
|
||||
rv = (*fn)(obj, arg, &done);
|
||||
if (rv != 0 || done)
|
||||
break;
|
||||
}
|
||||
rv = xbps_callback_array_iter_in_dict(
|
||||
xhp->regpkgdb, "packages", fn, arg);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -150,3 +182,22 @@ xbps_regpkgdb_foreach_pkg_cb(int (*fn)(prop_object_t, void *, bool *),
|
||||
{
|
||||
return foreach_pkg_cb(fn, arg, false);
|
||||
}
|
||||
|
||||
prop_dictionary_t
|
||||
xbps_regpkgdb_get_pkgd(const char *pkg, bool bypattern)
|
||||
{
|
||||
struct xbps_handle *xhp = xbps_handle_get();
|
||||
prop_dictionary_t pkgd = NULL;
|
||||
|
||||
if (xbps_regpkgdb_dictionary_init(xhp) != 0)
|
||||
return NULL;
|
||||
|
||||
if (bypattern)
|
||||
pkgd = xbps_find_pkg_in_dict_by_pattern(xhp->regpkgdb,
|
||||
"packages", pkg);
|
||||
else
|
||||
pkgd = xbps_find_pkg_in_dict_by_name(xhp->regpkgdb,
|
||||
"packages", pkg);
|
||||
|
||||
return prop_dictionary_copy(pkgd);
|
||||
}
|
||||
|
||||
@@ -164,13 +164,14 @@ xbps_transaction_commit(prop_dictionary_t transd)
|
||||
struct xbps_handle *xhp;
|
||||
prop_object_t obj;
|
||||
prop_object_iterator_t iter;
|
||||
size_t i;
|
||||
const char *pkgname, *version, *pkgver, *tract;
|
||||
int rv = 0;
|
||||
bool update, install, purge;
|
||||
bool update, install;
|
||||
|
||||
assert(prop_object_type(transd) == PROP_TYPE_DICTIONARY);
|
||||
|
||||
update = install = purge = false;
|
||||
update = install = false;
|
||||
xhp = xbps_handle_get();
|
||||
iter = xbps_array_iter_from_dict(transd, "packages");
|
||||
if (iter == NULL)
|
||||
@@ -193,7 +194,16 @@ xbps_transaction_commit(prop_dictionary_t transd)
|
||||
*/
|
||||
xbps_set_cb_state(XBPS_STATE_TRANS_RUN, 0, NULL, NULL, NULL);
|
||||
|
||||
i = 0;
|
||||
while ((obj = prop_object_iterator_next(iter)) != NULL) {
|
||||
if ((xhp->transaction_frequency_flush > 0) &&
|
||||
(++i >= xhp->transaction_frequency_flush)) {
|
||||
rv = xbps_regpkgdb_update(xhp, true);
|
||||
if (rv != 0 && rv != ENOENT)
|
||||
goto out;
|
||||
|
||||
i = 0;
|
||||
}
|
||||
update = false;
|
||||
prop_dictionary_get_cstring_nocopy(obj, "transaction", &tract);
|
||||
prop_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname);
|
||||
@@ -201,22 +211,15 @@ xbps_transaction_commit(prop_dictionary_t transd)
|
||||
prop_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver);
|
||||
|
||||
if (strcmp(tract, "remove") == 0) {
|
||||
purge = update = false;
|
||||
update = false;
|
||||
/*
|
||||
* Remove and optionally also purge package.
|
||||
* Remove package.
|
||||
*/
|
||||
prop_dictionary_get_bool(obj, "remove-and-update",
|
||||
&update);
|
||||
prop_dictionary_get_bool(obj, "remove-and-purge",
|
||||
&purge);
|
||||
rv = xbps_remove_pkg(pkgname, version, update);
|
||||
if (rv != 0)
|
||||
goto out;
|
||||
if (update || !purge)
|
||||
continue;
|
||||
|
||||
if ((rv = xbps_purge_pkg(pkgname, false)) != 0)
|
||||
goto out;
|
||||
} else if (strcmp(tract, "configure") == 0) {
|
||||
/*
|
||||
* Reconfigure pending package.
|
||||
@@ -269,6 +272,10 @@ xbps_transaction_commit(prop_dictionary_t transd)
|
||||
}
|
||||
prop_object_iterator_reset(iter);
|
||||
|
||||
/* force a flush now packages were removed/unpacked */
|
||||
if ((rv = xbps_regpkgdb_update(xhp, true)) != 0)
|
||||
goto out;
|
||||
|
||||
/* if there are no packages to install or update we are done */
|
||||
if (!update && !install)
|
||||
goto out;
|
||||
@@ -277,11 +284,21 @@ xbps_transaction_commit(prop_dictionary_t transd)
|
||||
*/
|
||||
xbps_set_cb_state(XBPS_STATE_TRANS_CONFIGURE, 0, NULL, NULL, NULL);
|
||||
|
||||
i = 0;
|
||||
while ((obj = prop_object_iterator_next(iter)) != NULL) {
|
||||
if (xhp->transaction_frequency_flush > 0 &&
|
||||
++i >= xhp->transaction_frequency_flush) {
|
||||
if ((rv = xbps_regpkgdb_update(xhp, true)) != 0)
|
||||
goto out;
|
||||
|
||||
i = 0;
|
||||
}
|
||||
|
||||
prop_dictionary_get_cstring_nocopy(obj, "transaction", &tract);
|
||||
if ((strcmp(tract, "remove") == 0) ||
|
||||
(strcmp(tract, "configure") == 0))
|
||||
continue;
|
||||
|
||||
prop_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname);
|
||||
prop_dictionary_get_cstring_nocopy(obj, "version", &version);
|
||||
update = false;
|
||||
@@ -304,6 +321,8 @@ xbps_transaction_commit(prop_dictionary_t transd)
|
||||
}
|
||||
}
|
||||
|
||||
/* Force a flush now that packages are configured */
|
||||
rv = xbps_regpkgdb_update(xhp, true);
|
||||
out:
|
||||
prop_object_iterator_release(iter);
|
||||
|
||||
|
||||
@@ -178,6 +178,8 @@ compute_transaction_stats(void)
|
||||
pkg_metad =
|
||||
xbps_dictionary_from_metadata_plist(pkgname,
|
||||
XBPS_PKGPROPS);
|
||||
if (pkg_metad == NULL)
|
||||
continue;
|
||||
prop_dictionary_get_uint64(pkg_metad,
|
||||
"installed_size", &tsize);
|
||||
prop_object_release(pkg_metad);
|
||||
|
||||
@@ -227,7 +227,7 @@ xbps_transaction_install_pkg(const char *pkgpattern)
|
||||
}
|
||||
|
||||
int
|
||||
xbps_transaction_remove_pkg(const char *pkgname, bool purge, bool recursive)
|
||||
xbps_transaction_remove_pkg(const char *pkgname, bool recursive)
|
||||
{
|
||||
prop_dictionary_t transd, pkgd;
|
||||
prop_array_t mdeps, orphans, orphans_pkg, unsorted, reqby;
|
||||
@@ -238,11 +238,9 @@ xbps_transaction_remove_pkg(const char *pkgname, bool purge, bool recursive)
|
||||
|
||||
assert(pkgname != NULL);
|
||||
|
||||
pkgd = xbps_find_pkg_dict_installed(pkgname, false);
|
||||
if (prop_object_type(pkgd) != PROP_TYPE_DICTIONARY) {
|
||||
if ((pkgd = xbps_regpkgdb_get_pkgd(pkgname, false)) == NULL) {
|
||||
/* pkg not installed */
|
||||
rv = ENOENT;
|
||||
goto out;
|
||||
return ENOENT;
|
||||
}
|
||||
/*
|
||||
* Prepare transaction dictionary and missing deps array.
|
||||
@@ -267,6 +265,7 @@ xbps_transaction_remove_pkg(const char *pkgname, bool purge, bool recursive)
|
||||
rv = ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
prop_array_set_cstring_nocopy(orphans_pkg, 0, pkgname);
|
||||
orphans = xbps_find_pkg_orphans(orphans_pkg);
|
||||
prop_object_release(orphans_pkg);
|
||||
@@ -274,13 +273,12 @@ xbps_transaction_remove_pkg(const char *pkgname, bool purge, bool recursive)
|
||||
rv = EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
count = prop_array_count(orphans);
|
||||
while (count--) {
|
||||
obj = prop_array_get(orphans, count);
|
||||
prop_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver);
|
||||
prop_dictionary_set_cstring_nocopy(obj, "transaction", "remove");
|
||||
if (purge)
|
||||
prop_dictionary_set_bool(obj, "remove-and-purge", true);
|
||||
prop_array_add(unsorted, obj);
|
||||
xbps_dbg_printf("%s: added into transaction (remove).\n", pkgver);
|
||||
}
|
||||
@@ -291,8 +289,6 @@ rmpkg:
|
||||
*/
|
||||
prop_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
|
||||
prop_dictionary_set_cstring_nocopy(pkgd, "transaction", "remove");
|
||||
if (purge)
|
||||
prop_dictionary_set_bool(pkgd, "remove-and-purge", true);
|
||||
prop_array_add(unsorted, pkgd);
|
||||
xbps_dbg_printf("%s: added into transaction (remove).\n", pkgver);
|
||||
reqby = prop_dictionary_get(pkgd, "requiredby");
|
||||
@@ -303,15 +299,15 @@ rmpkg:
|
||||
if ((prop_object_type(reqby) == PROP_TYPE_ARRAY) &&
|
||||
(prop_array_count(reqby) > 0))
|
||||
rv = EEXIST;
|
||||
|
||||
out:
|
||||
if (prop_object_type(pkgd) == PROP_TYPE_DICTIONARY)
|
||||
prop_object_release(pkgd);
|
||||
prop_object_release(pkgd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
xbps_transaction_autoremove_pkgs(bool purge)
|
||||
xbps_transaction_autoremove_pkgs(void)
|
||||
{
|
||||
prop_dictionary_t transd;
|
||||
prop_array_t orphans, mdeps, unsorted;
|
||||
@@ -350,8 +346,6 @@ xbps_transaction_autoremove_pkgs(bool purge)
|
||||
prop_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver);
|
||||
prop_dictionary_set_cstring_nocopy(obj,
|
||||
"transaction", "remove");
|
||||
if (purge)
|
||||
prop_dictionary_set_bool(obj, "remove-and-purge", true);
|
||||
prop_array_add(unsorted, obj);
|
||||
xbps_dbg_printf("%s: added into transaction (remove).\n",
|
||||
pkgver);
|
||||
|
||||
@@ -121,8 +121,7 @@ xbps_transaction_package_replace(prop_dictionary_t transd)
|
||||
* If new package is providing a virtual package to the
|
||||
* package that we want to replace we should respect
|
||||
* its requiredby and automatic-install objects, so copy
|
||||
* them to the pkg's dictionary in transaction. Also
|
||||
* make sure that replaced package is also purged.
|
||||
* them to the pkg's dictionary in transaction.
|
||||
*/
|
||||
if (xbps_match_virtual_pkg_in_dict(pkg_repod,
|
||||
pattern, true) ||
|
||||
@@ -135,8 +134,6 @@ xbps_transaction_package_replace(prop_dictionary_t transd)
|
||||
}
|
||||
prop_dictionary_set_bool(pkg_repod,
|
||||
"automatic-install", instd_auto);
|
||||
prop_dictionary_set_bool(instd,
|
||||
"remove-and-purge", true);
|
||||
}
|
||||
/*
|
||||
* Add package dictionary into the transaction and mark
|
||||
|
||||
Reference in New Issue
Block a user