user notes via xdata field

this patch adds user/libalpm interfaces to be able to add custom
user notes into local database
This commit is contained in:
vixfwis 2024-05-09 19:48:56 +05:00
parent 7bc5d55b56
commit 5705b600ca
27 changed files with 679 additions and 11 deletions

View file

@ -1446,6 +1446,17 @@ alpm_list_t *alpm_db_get_groupcache(alpm_db_t *db);
int alpm_db_search(alpm_db_t *db, const alpm_list_t *needles,
alpm_list_t **ret);
/** Searches a database with regular expressions.
* @param db pointer to the package database to search in
* @param needles a list of regular expressions to search for
* @param notes a list of regular expressions to search in extended data field
* @param ret pointer to list for storing packages matching all
* regular expressions - must point to an empty (NULL) alpm_list_t *.
* @return 0 on success, -1 on error (pm_errno is set accordingly)
*/
int alpm_db_search_usernote(alpm_db_t *db, const alpm_list_t *needles,
const alpm_list_t *notes, alpm_list_t **ret);
/** The usage level of a database. */
typedef enum _alpm_db_usage_t {
/** Enable refreshes for this database */
@ -2673,6 +2684,44 @@ int alpm_pkg_get_validation(alpm_pkg_t *pkg);
*/
alpm_list_t *alpm_pkg_get_xdata(alpm_pkg_t *pkg);
/** Gets user data prefix for extended data field keys
* @return a const reference to a key prefix
*/
const char *alpm_get_userdata_prefix(void);
/** Gets a list of user notes for a package. Caller is responsible
* for freeing the returned list
* @param pkg a pointer to package
* @return a reference to a list of alpm_pkg_xdata_t objects
*/
alpm_list_t *alpm_pkg_get_user_notes(alpm_pkg_t *pkg);
/** Updates user notes with passed list. Notes names will be prepended
* internally with the user prefix
* @param pkg a pointer to package
* @param notes a reference to a list of alpm_pkg_xdata_t objects
* @return 0 on success, -1 on error (pm_errno is set accordingly)
*/
int alpm_pkg_user_notes_update(alpm_pkg_t *pkg, const alpm_list_t *notes);
/** Deletes user notes matching passed keys list
* @param pkg a pointer to package
* @param keys a reference to a list of char*
* @return 0 on success, -1 on error (pm_errno is set accordingly)
*/
int alpm_pkg_user_notes_delete(alpm_pkg_t *pkg, const alpm_list_t *keys);
/** Parse string into an extended data structure
* @param string a pointer to string
* @return a reference to xdata on success, NULL on failure
*/
alpm_pkg_xdata_t *alpm_pkg_parse_xdata(const char *string);
/** Free an extended data structure.
* @param xdata a reference to an xdata to free
*/
void alpm_pkg_xdata_free(alpm_pkg_xdata_t *xdata);
/** Returns whether the package has an install scriptlet.
* @return 0 if FALSE, TRUE otherwise
*/
@ -2694,6 +2743,15 @@ off_t alpm_pkg_download_size(alpm_pkg_t *newpkg);
*/
int alpm_pkg_set_reason(alpm_pkg_t *pkg, alpm_pkgreason_t reason);
/** Set user notes for a package in the local database.
* The provided package object must be from the local database or this method
* will fail. The write to the local database is performed immediately.
* @param pkg the package to update
* @param notes list of \link alpm_pkg_xdata_t \endlink structures
* @return 0 on success, -1 on error (pm_errno is set accordingly)
*/
int alpm_pkg_set_user_notes(alpm_pkg_t *pkg, const alpm_list_t *notes);
/* End of libalpm_pkg_t accessors */
/** @} */

View file

@ -1205,6 +1205,38 @@ int SYMEXPORT alpm_pkg_set_reason(alpm_pkg_t *pkg, alpm_pkgreason_t reason)
return 0;
}
int SYMEXPORT alpm_pkg_set_user_notes(alpm_pkg_t *pkg, const alpm_list_t *notes)
{
ASSERT(pkg != NULL, return -1);
ASSERT(pkg->origin == ALPM_PKG_FROM_LOCALDB,
RET_ERR(pkg->handle, ALPM_ERR_WRONG_ARGS, -1));
ASSERT(pkg->origin_data.db == pkg->handle->db_local,
RET_ERR(pkg->handle, ALPM_ERR_WRONG_ARGS, -1));
pkg->handle->pm_errno = ALPM_ERR_OK;
const char *prefix = alpm_get_userdata_prefix();
alpm_list_t *pkg_xdata = alpm_pkg_get_xdata(pkg);
alpm_list_t *new_pkg_xdata = NULL;
for(alpm_list_t *i = pkg_xdata; i; i = alpm_list_next(i)) {
alpm_pkg_xdata_t *xdata = i->data;
if(strncmp(xdata->name, prefix, strlen(prefix)) != 0) {
alpm_pkg_xdata_t *tmp = _alpm_pkg_xdata_dup(xdata);
alpm_list_append(&new_pkg_xdata, tmp);
}
}
pkg->xdata = new_pkg_xdata;
alpm_list_free_inner(pkg_xdata, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(pkg_xdata);
alpm_pkg_user_notes_update(pkg, notes);
/* write DESC */
if(_alpm_local_db_write(pkg->handle->db_local, pkg, INFRQ_DESC)) {
RET_ERR(pkg->handle, ALPM_ERR_DB_WRITE, -1);
}
return 0;
}
static const struct db_operations local_db_ops = {
.validate = local_db_validate,
.populate = local_db_populate,

View file

@ -350,7 +350,17 @@ int SYMEXPORT alpm_db_search(alpm_db_t *db, const alpm_list_t *needles,
RET_ERR(db->handle, ALPM_ERR_WRONG_ARGS, -1));
db->handle->pm_errno = ALPM_ERR_OK;
return _alpm_db_search(db, needles, ret);
return _alpm_db_search(db, needles, NULL, ret);
}
int SYMEXPORT alpm_db_search_usernote(alpm_db_t *db, const alpm_list_t *needles,
const alpm_list_t *notes, alpm_list_t **ret)
{
ASSERT(db != NULL && ret != NULL && *ret == NULL,
RET_ERR(db->handle, ALPM_ERR_WRONG_ARGS, -1));
db->handle->pm_errno = ALPM_ERR_OK;
return _alpm_db_search(db, needles, notes, ret);
}
int SYMEXPORT alpm_db_set_usage(alpm_db_t *db, int usage)
@ -440,7 +450,7 @@ int _alpm_db_cmp(const void *d1, const void *d2)
}
int _alpm_db_search(alpm_db_t *db, const alpm_list_t *needles,
alpm_list_t **ret)
const alpm_list_t *notes, alpm_list_t **ret)
{
const alpm_list_t *i, *j, *k;
@ -451,6 +461,60 @@ int _alpm_db_search(alpm_db_t *db, const alpm_list_t *needles,
/* copy the pkgcache- we will free the list var after each needle */
alpm_list_t *list = alpm_list_copy(_alpm_db_get_pkgcache(db));
/* search in extended data field */
for(i = notes; i; i = i->next) {
char *targ;
regex_t reg;
if(i->data == NULL) {
continue;
}
*ret = NULL;
targ = i->data;
_alpm_log(db->handle, ALPM_LOG_DEBUG,
"searching for '%s' in extended data field\n", targ);
if(regcomp(&reg, targ, REG_EXTENDED | REG_NOSUB | REG_ICASE | REG_NEWLINE) != 0) {
db->handle->pm_errno = ALPM_ERR_INVALID_REGEX;
alpm_list_free(list);
alpm_list_free(*ret);
return -1;
}
for(j = list; j; j = j->next) {
alpm_pkg_t *pkg = j->data;
alpm_list_t *pkg_notes = alpm_pkg_get_user_notes(pkg);
for(k = pkg_notes; k; k = k->next) {
alpm_pkg_xdata_t *pkg_xdata = k->data;
/* pack xdata into flat cstring for searching */
char *xdata_str = NULL;
int maxlen = strlen(pkg_xdata->name) + strlen(pkg_xdata->value) + 2;
CALLOC(xdata_str, 1, maxlen,
db->handle->pm_errno = ALPM_ERR_MEMORY;
alpm_list_free(list);
alpm_list_free(*ret);
return -1
);
snprintf(xdata_str, maxlen, "%s=%s", pkg_xdata->name, pkg_xdata->value);
if(regexec(&reg, xdata_str, 0, 0, 0) == 0 || strstr(xdata_str, targ)) {
_alpm_log(db->handle, ALPM_LOG_DEBUG,
"search in notes for '%s' matched on package '%s'\n",
targ, pkg->name);
*ret = alpm_list_add(*ret, pkg);
}
free(xdata_str);
}
alpm_list_free_inner(pkg_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(pkg_notes);
}
/* Free the existing search list, and use the returned list for the
* next needle. This allows for AND-based package searching. */
alpm_list_free(list);
list = *ret;
regfree(&reg);
}
for(i = needles; i; i = i->next) {
char *targ;
regex_t reg;

View file

@ -89,7 +89,7 @@ void _alpm_db_free(alpm_db_t *db);
const char *_alpm_db_path(alpm_db_t *db);
int _alpm_db_cmp(const void *d1, const void *d2);
int _alpm_db_search(alpm_db_t *db, const alpm_list_t *needles,
alpm_list_t **ret);
const alpm_list_t *xdata, alpm_list_t **ret);
alpm_db_t *_alpm_db_register_local(alpm_handle_t *handle);
alpm_db_t *_alpm_db_register_sync(alpm_handle_t *handle, const char *treename,
int level);

View file

@ -35,6 +35,12 @@
#include "handle.h"
#include "deps.h"
static const char *XDATA_USERDATA_PREFIX = "user:";
const char SYMEXPORT *alpm_get_userdata_prefix(void) {
return XDATA_USERDATA_PREFIX;
}
int SYMEXPORT alpm_pkg_free(alpm_pkg_t *pkg)
{
ASSERT(pkg != NULL, return -1);
@ -47,6 +53,109 @@ int SYMEXPORT alpm_pkg_free(alpm_pkg_t *pkg)
return 0;
}
alpm_pkg_xdata_t SYMEXPORT *alpm_pkg_parse_xdata(const char *string) {
ASSERT(string != NULL, return NULL);
return _alpm_pkg_parse_xdata(string);
}
alpm_list_t SYMEXPORT *alpm_pkg_get_user_notes(alpm_pkg_t *pkg) {
ASSERT(pkg != NULL, return NULL);
pkg->handle->pm_errno = ALPM_ERR_OK;
alpm_list_t *user_notes = NULL;
const char *prefix = alpm_get_userdata_prefix();
for(alpm_list_t *i = alpm_pkg_get_xdata(pkg); i; i = alpm_list_next(i)) {
alpm_pkg_xdata_t *xdata = i->data;
if(strncmp(xdata->name, prefix, strlen(prefix)) == 0) {
alpm_pkg_xdata_t *user_note = _alpm_pkg_user_note_dup(xdata, NULL);
memmove(user_note->name,
user_note->name + strlen(prefix),
strlen(user_note->name) - strlen(prefix) + 1);
user_notes = alpm_list_add(user_notes, user_note);
}
}
return user_notes;
}
int SYMEXPORT alpm_pkg_user_notes_update(alpm_pkg_t *pkg, const alpm_list_t *notes) {
ASSERT(pkg != NULL, return -1);
pkg->handle->pm_errno = ALPM_ERR_OK;
if(notes == NULL) {
return 0;
}
const char *prefix = alpm_get_userdata_prefix();
alpm_list_t *pkg_xdata_lst = alpm_pkg_get_xdata(pkg);
for(const alpm_list_t *i = notes; i; i = alpm_list_next(i)) {
alpm_pkg_xdata_t *xdata = i->data;
int found_existing_key = 0;
for(alpm_list_t *j = pkg_xdata_lst; j; j = alpm_list_next(j)) {
alpm_pkg_xdata_t *pkg_xdata = j->data;
if(strncmp(pkg_xdata->name, prefix, strlen(prefix)) != 0) {
continue;
}
if(strcmp(pkg_xdata->name + strlen(prefix), xdata->name) == 0) {
found_existing_key = 1;
alpm_pkg_xdata_t *new_xdata = _alpm_pkg_user_note_dup(xdata, prefix);
if(new_xdata == NULL) {
pkg->handle->pm_errno = ALPM_ERR_MEMORY;
return -1;
}
j->data = new_xdata;
_alpm_pkg_xdata_free(pkg_xdata);
}
}
if(!found_existing_key) {
alpm_pkg_xdata_t *new_xdata = _alpm_pkg_user_note_dup(xdata, prefix);
if(new_xdata == NULL) {
pkg->handle->pm_errno = ALPM_ERR_MEMORY;
return -1;
}
pkg_xdata_lst = alpm_list_add(pkg_xdata_lst, new_xdata);
}
}
pkg->xdata = pkg_xdata_lst;
return 0;
}
int SYMEXPORT alpm_pkg_user_notes_delete(alpm_pkg_t *pkg, const alpm_list_t *keys) {
ASSERT(pkg != NULL, return -1);
pkg->handle->pm_errno = ALPM_ERR_OK;
if(keys == NULL) {
return 0;
}
const char *prefix = alpm_get_userdata_prefix();
alpm_list_t *pkg_xdata_lst = alpm_pkg_get_xdata(pkg);
alpm_list_t *new_pkg_xdata_lst = NULL;
for(alpm_list_t *j = pkg_xdata_lst; j; j = alpm_list_next(j)) {
alpm_pkg_xdata_t *pkg_xdata = j->data;
int skip_key = 0;
for(const alpm_list_t *i = keys; i; i = alpm_list_next(i)) {
const char *key = i->data;
if(strncmp(pkg_xdata->name, prefix, strlen(prefix)) == 0
&& strcmp(pkg_xdata->name + strlen(prefix), key) == 0) {
skip_key = 1;
break;
}
}
if(!skip_key) {
printf("dup %s\n", pkg_xdata->name);
alpm_pkg_xdata_t *tmp = _alpm_pkg_xdata_dup(pkg_xdata);
alpm_list_append(&new_pkg_xdata_lst, tmp);
}
}
pkg->xdata = new_pkg_xdata_lst;
alpm_list_free_inner(pkg_xdata_lst, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(pkg_xdata_lst);
return 0;
}
void SYMEXPORT alpm_pkg_xdata_free(alpm_pkg_xdata_t *xdata) {
ASSERT(xdata != NULL, return);
_alpm_pkg_xdata_free(xdata);
}
int SYMEXPORT alpm_pkg_checkmd5sum(alpm_pkg_t *pkg)
{
char *fpath;
@ -702,6 +811,36 @@ alpm_pkg_xdata_t *_alpm_pkg_parse_xdata(const char *string)
return pd;
}
alpm_pkg_xdata_t *_alpm_pkg_xdata_dup(const alpm_pkg_xdata_t *xdata) {
alpm_pkg_xdata_t *pd;
CALLOC(pd, 1, sizeof(alpm_pkg_xdata_t), return NULL);
STRDUP(pd->name, xdata->name, FREE(pd); return NULL);
STRDUP(pd->value, xdata->value, FREE(pd->name); FREE(pd); return NULL);
return pd;
}
alpm_pkg_xdata_t *_alpm_pkg_user_note_dup(const alpm_pkg_xdata_t *xdata, const char *prefix) {
ASSERT(xdata != NULL, return NULL);
alpm_pkg_xdata_t *new_xdata;
CALLOC(new_xdata, 1, sizeof(alpm_pkg_xdata_t), return NULL);
int key_alloc_size = strlen(xdata->name) + 1;
if(prefix) {
key_alloc_size += strlen(prefix);
}
CALLOC(new_xdata->name, 1, key_alloc_size, FREE(new_xdata); return NULL);
if(prefix) {
sprintf(new_xdata->name, "%s%s", prefix, xdata->name);
} else {
sprintf(new_xdata->name, "%s", xdata->name);
}
STRDUP(new_xdata->value, xdata->value, FREE(new_xdata->name); FREE(new_xdata);
return NULL);
return new_xdata;
}
void _alpm_pkg_xdata_free(alpm_pkg_xdata_t *pd)
{
if(pd) {

View file

@ -163,6 +163,9 @@ int _alpm_pkg_cmp(const void *p1, const void *p2);
int _alpm_pkg_compare_versions(alpm_pkg_t *local_pkg, alpm_pkg_t *pkg);
alpm_pkg_xdata_t *_alpm_pkg_parse_xdata(const char *string);
alpm_pkg_xdata_t *_alpm_pkg_xdata_dup(const alpm_pkg_xdata_t *xdata);
alpm_pkg_xdata_t *_alpm_pkg_user_note_dup(const alpm_pkg_xdata_t *xdata, const char *prefix);
void _alpm_pkg_set_xdata(alpm_pkg_t *pkg, alpm_list_t *xdata_lst);
void _alpm_pkg_xdata_free(alpm_pkg_xdata_t *pd);
int _alpm_pkg_check_meta(alpm_pkg_t *pkg);

View file

@ -1174,6 +1174,21 @@ static int load_packages(alpm_handle_t *handle, alpm_list_t **data,
PROGRESS(handle, ALPM_PROGRESS_LOAD_START, "", percent,
total, current);
/* check localdb for user notes and update new package */
alpm_db_t *localdb = alpm_get_localdb(handle);
alpm_pkg_t *localpkg = alpm_db_get_pkg(localdb, spkg->name);
if(localpkg != NULL) {
alpm_list_t *new_notes = alpm_pkg_get_user_notes(spkg);
alpm_list_t *old_notes = alpm_pkg_get_user_notes(localpkg);
alpm_pkg_user_notes_update(spkg, old_notes);
alpm_pkg_user_notes_update(spkg, new_notes);
alpm_list_free_inner(old_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(old_notes);
alpm_list_free_inner(new_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(new_notes);
}
if(spkg->origin == ALPM_PKG_FROM_FILE) {
continue; /* pkg_load() has been already called, this package is valid */
}
@ -1208,6 +1223,11 @@ static int load_packages(alpm_handle_t *handle, alpm_list_t **data,
continue;
}
free(filepath);
/* copy user notes */
alpm_list_t *user_notes = alpm_pkg_get_user_notes(spkg);
alpm_pkg_user_notes_update(pkgfile, user_notes);
alpm_list_free_inner(user_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(user_notes);
/* copy over the install reason */
pkgfile->reason = spkg->reason;
/* copy over validation method */

View file

@ -150,6 +150,9 @@ int config_free(config_t *oldconfig)
FREELIST(oldconfig->noupgrade);
FREELIST(oldconfig->noextract);
FREELIST(oldconfig->overwrite_files);
FREELIST(oldconfig->user_note);
FREELIST(oldconfig->user_note_extra);
FREELIST(oldconfig->user_note_delete);
free(oldconfig->configfile);
free(oldconfig->sysroot);
free(oldconfig->rootdir);

View file

@ -86,6 +86,7 @@ typedef struct __config_t {
unsigned short op_q_upgrade;
unsigned short op_q_check;
unsigned short op_q_locality;
unsigned short op_q_usernote_delete;
unsigned short op_s_clean;
unsigned short op_s_downloadonly;
@ -127,6 +128,9 @@ typedef struct __config_t {
alpm_list_t *noupgrade;
alpm_list_t *noextract;
alpm_list_t *overwrite_files;
alpm_list_t *user_note;
alpm_list_t *user_note_extra;
alpm_list_t *user_note_delete;
char *xfercommand;
char **xfercommand_argv;
size_t xfercommand_argc;
@ -214,7 +218,10 @@ enum {
OP_REFRESH,
OP_ASSUMEINSTALLED,
OP_DISABLEDLTIMEOUT,
OP_DISABLESANDBOX
OP_DISABLESANDBOX,
OP_USERNOTE,
OP_USERNOTE_EXTRA,
OP_USERNOTE_DELETE
};
/* clean method */

View file

@ -28,6 +28,7 @@
/* pacman */
#include "pacman.h"
#include "package.h"
#include "conf.h"
#include "util.h"
@ -91,6 +92,55 @@ static int change_install_reason(alpm_list_t *targets)
return ret;
}
/**
* @brief Modify the 'local' package database.
*
* @param targets a list of packages (as strings) to modify
* @param add list of alpm_pkg_xdata_t to add
* @param remove list of char* keys to remove
*
* @return 0 on success, 1 on failure
*/
static int change_user_notes(alpm_list_t *targets, const alpm_list_t *add, const alpm_list_t *delete) {
const alpm_list_t *i;
alpm_db_t *db_local;
int ret = 0;
if(targets == NULL) {
pm_printf(ALPM_LOG_ERROR, _("no targets specified (use -h for help)\n"));
return 1;
}
/* Lock database */
if(trans_init(0, 0) == -1) {
return 1;
}
db_local = alpm_get_localdb(config->handle);
for(i = targets; i; i = alpm_list_next(i)) {
char *pkgname = i->data;
alpm_pkg_t *pkg = alpm_db_get_pkg(db_local, pkgname);
if(pkg == NULL) {
pm_printf(ALPM_LOG_ERROR, _("could not get local package %s (%s)\n"),
pkgname, alpm_strerror(alpm_errno(config->handle)));
ret = 1;
continue;
}
alpm_pkg_user_notes_delete(pkg, delete);
alpm_pkg_user_notes_update(pkg, add);
alpm_list_t *pkg_notes = alpm_pkg_get_user_notes(pkg);
alpm_pkg_set_user_notes(pkg, pkg_notes);
alpm_list_free_inner(pkg_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(pkg_notes);
}
/* Unlock database */
if(trans_release() == -1) {
return 1;
}
return ret;
}
static int check_db_missing_deps(alpm_list_t *pkglist)
{
alpm_list_t *data, *i;
@ -301,5 +351,31 @@ int pacman_database(alpm_list_t *targets)
ret = change_install_reason(targets);
}
if(config->user_note || config->user_note_extra || config->user_note_delete) {
alpm_list_t *i, *usernote_add = NULL;
for(i = config->user_note; i; i = alpm_list_next(i)) {
const char *arg = i->data;
const char *key = get_default_user_note_key();
char *full_note = calloc(1, strlen(key) + 1 + strlen(arg) + 1);
sprintf(full_note, "%s=%s", key, arg);
alpm_pkg_xdata_t *user_note = alpm_pkg_parse_xdata(full_note);
free(full_note);
usernote_add = alpm_list_add(usernote_add, user_note);
}
for(i = config->user_note_extra; i; i = alpm_list_next(i)) {
const char *arg = i->data;
alpm_pkg_xdata_t *user_note = alpm_pkg_parse_xdata(arg);
if(user_note == NULL) {
pm_printf(ALPM_LOG_ERROR, "failed to parse user note '%s'\n", arg);
return -1;
}
usernote_add = alpm_list_add(usernote_add, user_note);
}
change_user_notes(targets, usernote_add, config->user_note_delete);
alpm_list_free_inner(usernote_add, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(usernote_add);
}
return ret;
}

View file

@ -82,6 +82,12 @@ enum {
static char titles[_T_MAX][TITLE_MAXLEN * sizeof(wchar_t)];
static const char *XDATA_DEFAULT_USER_KEY = "note";
const char *get_default_user_note_key(void) {
return XDATA_DEFAULT_USER_KEY;
}
/** Build the `titles` array of localized titles and pad them with spaces so
* that they align with the longest title. Storage for strings is stack
* allocated and naively truncated to TITLE_MAXLEN characters.
@ -351,6 +357,21 @@ void dump_pkg_full(alpm_pkg_t *pkg, int extra)
dump_pkg_backups(pkg, cols);
}
alpm_list_t *user_notes = alpm_pkg_get_user_notes(pkg);
if(user_notes) {
alpm_list_t *text = NULL;
for(alpm_list_t *i = user_notes; i; i = alpm_list_next(i)) {
alpm_pkg_xdata_t *note = i->data;
char *formatted = NULL;
pm_asprintf(&formatted, "%s=%s", note->name, note->value);
text = alpm_list_add(text, formatted);
}
list_display_linebreak("User Notes :", text, cols);
FREELIST(text);
}
alpm_list_free_inner(user_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(user_notes);
if(extra) {
alpm_list_t *text = NULL, *pdata = alpm_pkg_get_xdata(pkg);
while(pdata) {
@ -539,7 +560,7 @@ int dump_pkg_search(alpm_db_t *db, alpm_list_t *targets, int show_status)
{
int freelist = 0;
alpm_db_t *db_local;
alpm_list_t *i, *searchlist = NULL;
alpm_list_t *i, *j, *searchlist = NULL;
unsigned short cols;
const colstr_t *colstr = &config->colstr;
@ -548,10 +569,16 @@ int dump_pkg_search(alpm_db_t *db, alpm_list_t *targets, int show_status)
}
/* if we have a targets list, search for packages matching it */
if(targets) {
if(targets || config->user_note) {
if(config->user_note) {
if(alpm_db_search_usernote(db, targets, config->user_note, &searchlist) != 0) {
return -1;
}
}else {
if(alpm_db_search(db, targets, &searchlist) != 0) {
return -1;
}
}
freelist = 1;
} else {
searchlist = alpm_db_get_pkgcache(db);
@ -580,6 +607,20 @@ int dump_pkg_search(alpm_db_t *db, alpm_list_t *targets, int show_status)
/* we need a newline and initial indent first */
fputs("\n ", stdout);
indentprint(alpm_pkg_get_desc(pkg), 4, cols);
/* if we're searching for user note, print package notes */
if(config->user_note) {
alpm_list_t *pkg_notes = alpm_pkg_get_user_notes(pkg);
for(j = pkg_notes; j; j = alpm_list_next(j)) {
alpm_pkg_xdata_t *xdata = j->data;
char *user_note = calloc(1, strlen(xdata->name) + 1 + strlen(xdata->value) + 1);
fputs("\n ", stdout);
sprintf(user_note, "%s=%s", xdata->name, xdata->value);
indentprint(user_note, 4, cols);
free(user_note);
}
alpm_list_free_inner(pkg_notes, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(pkg_notes);
}
}
fputc('\n', stdout);
}

View file

@ -31,5 +31,6 @@ void dump_pkg_changelog(alpm_pkg_t *pkg);
void print_installed(alpm_db_t *db_local, alpm_pkg_t *pkg);
void print_groups(alpm_pkg_t *pkg);
int dump_pkg_search(alpm_db_t *db, alpm_list_t *targets, int show_status);
const char *get_default_user_note_key(void);
#endif /* PM_PACKAGE_H */

View file

@ -147,6 +147,9 @@ static void usage(int op, const char * const myname)
addlist(_(" -p, --file <package> query a package file instead of the database\n"));
addlist(_(" -q, --quiet show less information for query and search\n"));
addlist(_(" -s, --search <regex> search locally-installed packages for matching strings\n"));
addlist(_(" --note-search <regex>\n"
" search locally-installed packages for matching strings\n"
" inside user-added notes\n"));
addlist(_(" -t, --unrequired list packages not (optionally) required by any\n"
" package (-tt to ignore optdepends) [filter]\n"));
addlist(_(" -u, --upgrades list outdated packages [filter]\n"));
@ -171,6 +174,11 @@ static void usage(int op, const char * const myname)
addlist(_(" --asexplicit mark packages as explicitly installed\n"));
addlist(_(" -k, --check test local database for validity (-kk for sync databases)\n"));
addlist(_(" -q, --quiet suppress output of success messages\n"));
addlist(_(" --note <note> set user note for a package (default key: 'note')\n"));
addlist(_(" --note-extra <key>=<value>\n"
" set extra user note for a package\n"));
addlist(_(" --delete-note <key>\n"
" delete user note from package\n"));
} else if(op == PM_OP_DEPTEST) {
printf("%s: %s {-T --deptest} [%s] [%s]\n", str_usg, myname, str_opt, str_pkg);
printf("%s:\n", str_opt);
@ -193,6 +201,9 @@ static void usage(int op, const char * const myname)
" overwrite conflicting files (can be used more than once)\n"));
addlist(_(" --asdeps install packages as non-explicitly installed\n"));
addlist(_(" --asexplicit install packages as explicitly installed\n"));
addlist(_(" --note <note> set user note for a package (default key: 'note')\n"));
addlist(_(" --note-extra <key>=<value>\n"
" set extra user note for a package\n"));
addlist(_(" --ignore <pkg> ignore a package upgrade (can be used more than once)\n"));
addlist(_(" --ignoregroup <grp>\n"
" ignore a group upgrade (can be used more than once)\n"));
@ -522,6 +533,16 @@ static int parsearg_database(int opt)
case 'q':
config->quiet = 1;
break;
case OP_USERNOTE:
parsearg_util_addlist(&(config->user_note));
break;
case OP_USERNOTE_EXTRA:
parsearg_util_addlist(&(config->user_note_extra));
break;
case OP_USERNOTE_DELETE:
parsearg_util_addlist(&(config->user_note_delete));
break;
break;
default:
return 1;
}
@ -597,6 +618,10 @@ static int parsearg_query(int opt)
case 's':
config->op_q_search = 1;
break;
case OP_USERNOTE:
config->op_q_search = 1;
parsearg_util_addlist(&(config->user_note));
break;
case OP_UNREQUIRED:
case 't':
(config->op_q_unrequired)++;
@ -764,6 +789,12 @@ static int parsearg_upgrade(int opt)
case OP_NEEDED:
config->flags |= ALPM_TRANS_FLAG_NEEDED;
break;
case OP_USERNOTE:
parsearg_util_addlist(&(config->user_note));
break;
case OP_USERNOTE_EXTRA:
parsearg_util_addlist(&(config->user_note_extra));
break;
case OP_IGNORE:
parsearg_util_addlist(&(config->ignorepkg));
break;
@ -975,6 +1006,10 @@ static int parseargs(int argc, char *argv[])
{"ignoregroup", required_argument, 0, OP_IGNOREGROUP},
{"needed", no_argument, 0, OP_NEEDED},
{"asexplicit", no_argument, 0, OP_ASEXPLICIT},
{"note", required_argument, 0, OP_USERNOTE},
{"note-extra", required_argument, 0, OP_USERNOTE_EXTRA},
{"note-search", required_argument, 0, OP_USERNOTE},
{"delete-note", required_argument, 0, OP_USERNOTE_DELETE},
{"arch", required_argument, 0, OP_ARCH},
{"print-format", required_argument, 0, OP_PRINTFORMAT},
{"gpgdir", required_argument, 0, OP_GPGDIR},

View file

@ -761,8 +761,57 @@ static void print_broken_dep(alpm_depmissing_t *miss)
int sync_prepare_execute(void)
{
alpm_list_t *i, *packages, *data = NULL;
alpm_list_t *i, *packages, *data = NULL, *notes_lst = NULL;
int retval = 0;
const char *default_key = get_default_user_note_key();
/* Step 1.1: set user-added extended data fields */
for(i = config->user_note; i; i = alpm_list_next(i)) {
const char *xdata_arg = i->data;
/* extra 1 byte for '=' */
char *xdata_full_arg = calloc(1,
strlen(xdata_arg) + strlen(default_key) + 2
);
if(xdata_full_arg == NULL) {
pm_printf(ALPM_LOG_ERROR, _("memory allocation error\n"));
retval = 1;
goto cleanup;
}
sprintf(xdata_full_arg, "%s=%s", default_key, xdata_arg);
alpm_pkg_xdata_t *xdata = alpm_pkg_parse_xdata(xdata_full_arg);
if(xdata == NULL) {
pm_printf(ALPM_LOG_ERROR, _("failed to parse extended data field: %s\n"), xdata_arg);
free(xdata_full_arg);
retval = 1;
goto cleanup;
}
free(xdata_full_arg);
alpm_list_append(&notes_lst, xdata);
}
for(i = config->user_note_extra; i; i = alpm_list_next(i)) {
const char *xdata_arg = i->data;
alpm_pkg_xdata_t *xdata = alpm_pkg_parse_xdata(xdata_arg);
if(xdata == NULL) {
pm_printf(ALPM_LOG_ERROR, _("failed to parse extended data field: %s\n"), xdata_arg);
retval = 1;
goto cleanup;
}
alpm_list_append(&notes_lst, xdata);
}
if(notes_lst != NULL) {
packages = alpm_trans_get_add(config->handle);
for(i = packages; i; i = alpm_list_next(i)) {
alpm_pkg_t *pkg = i->data;
if(alpm_pkg_user_notes_update(pkg, notes_lst) != 0) {
pm_printf(ALPM_LOG_ERROR, _("failed to update user notes for package %s\n"),
alpm_pkg_get_name(pkg));
retval = 1;
goto cleanup;
}
}
}
/* Step 2: "compute" the transaction based on targets and flags */
if(alpm_trans_prepare(config->handle, &data) == -1) {
@ -889,6 +938,9 @@ int sync_prepare_execute(void)
/* Step 4: release transaction resources */
cleanup:
alpm_list_free_inner(notes_lst, (alpm_list_fn_free)alpm_pkg_xdata_free);
alpm_list_free(notes_lst);
alpm_list_free(data);
if(trans_release() == -1) {
retval = 1;

View file

@ -341,6 +341,15 @@ pacman_tests = [
'tests/upgrade-download-404.py',
'tests/upgrade-download-pkg-and-sig-with-filename.py',
'tests/upgrade-download-with-xfercommand.py',
'tests/usernote-01-change-localdb.py',
'tests/usernote-02-set-localdb.py',
'tests/usernote-03-set-sync.py',
'tests/usernote-04-prs-sync-reinstall.py',
'tests/usernote-05-prs-sync-upgrade.py',
'tests/usernote-06-prs-upgrade-from-file.py',
'tests/usernote-07-prs-non-user-xdata.py',
'tests/usernote-08-query-note-search.py',
'tests/usernote-09-query-note-info.py',
]
xfail_tests = {

View file

@ -157,6 +157,8 @@ class pmdb(object):
pkg.conflicts = _getsection(fd)
elif line == "%PROVIDES%":
pkg.provides = _getsection(fd)
elif line == "%XDATA%":
pkg.xdata = _getsection(fd)
fd.close()
# files
@ -203,6 +205,7 @@ class pmdb(object):
make_section(data, "OPTDEPENDS", pkg.optdepends)
make_section(data, "CONFLICTS", pkg.conflicts)
make_section(data, "PROVIDES", pkg.provides)
make_section(data, "XDATA", pkg.xdata)
make_section(data, "URL", pkg.url)
if self.is_local:
make_section(data, "INSTALLDATE", pkg.installdate)

View file

@ -64,6 +64,7 @@ class pmpkg(object):
}
self.path = None
self.finalized = False
self.xdata = []
def __str__(self):
s = ["%s" % self.fullname()]
@ -137,6 +138,8 @@ class pmpkg(object):
data.append("provides = %s" % i)
for i in self.backup:
data.append("backup = %s" % i)
for i in self.xdata:
data.append("xdata = %s" % i)
archive_files.append((".PKGINFO", "\n".join(data)))
# .INSTALL

View file

@ -33,7 +33,7 @@ class pmrule(object):
return self.rule
def snapshots_needed(self):
(testname, args) = self.rule.split("=")
(testname, args) = self.rule.split("=", 1)
if testname == "FILE_MODIFIED" or testname == "!FILE_MODIFIED":
return [args]
return []
@ -43,7 +43,7 @@ class pmrule(object):
"""
success = 1
[testname, args] = self.rule.split("=")
[testname, args] = self.rule.split("=", 1)
if testname[0] == "!":
self.false = 1
testname = testname[1:]
@ -108,6 +108,9 @@ class pmrule(object):
if f.startswith(value + "\t"):
success = 1
break;
elif case == "XDATA":
if not value in newpkg.xdata:
success = 0
else:
tap.diag("PKG rule '%s' not found" % case)
success = -1

View file

@ -0,0 +1,13 @@
self.description = "Change localdb notes"
sp = pmpkg("pkg")
sp.xdata = ["user:note=user note", 'user:foo=bar']
self.addpkg2db("local", sp);
self.args = "-D --delete-note note --delete-note foo --note-extra bar=foo pkg"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=pkg")
self.addrule("!PKG_XDATA=pkg|user:note=user note")
self.addrule("!PKG_XDATA=pkg|user:foo=bar")
self.addrule("PKG_XDATA=pkg|user:bar=foo")

View file

@ -0,0 +1,11 @@
self.description = "Set note in localdb"
sp = pmpkg("pkg")
self.addpkg2db("local", sp);
self.args = "-D --note 'user note' --note-extra foo=bar pkg"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=pkg")
self.addrule("PKG_XDATA=pkg|user:note=user note")
self.addrule("PKG_XDATA=pkg|user:foo=bar")

View file

@ -0,0 +1,11 @@
self.description = "Set note on install"
sp = pmpkg("pkg")
self.addpkg2db("sync", sp);
self.args = "-S --note 'user note' --note-extra foo=bar pkg"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=pkg")
self.addrule("PKG_XDATA=pkg|user:note=user note")
self.addrule("PKG_XDATA=pkg|user:foo=bar")

View file

@ -0,0 +1,14 @@
self.description = "Reinstall sync package, with user note in localdb"
sp = pmpkg("pkg")
self.addpkg2db("sync", sp);
sp = pmpkg("pkg")
sp.xdata = ['user:note=user note']
self.addpkg2db("local", sp);
self.args = "-S --note-extra foo=bar pkg"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=pkg")
self.addrule("PKG_XDATA=pkg|user:note=user note")
self.addrule("PKG_XDATA=pkg|user:foo=bar")

View file

@ -0,0 +1,14 @@
self.description = "Upgrade sync package, with user note in localdb"
sp = pmpkg("pkg", "1.1-1")
self.addpkg2db("sync", sp);
sp = pmpkg("pkg", "1.0-1")
sp.xdata = ['user:note=user note']
self.addpkg2db("local", sp);
self.args = "-Su --note-extra foo=bar pkg"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=pkg")
self.addrule("PKG_XDATA=pkg|user:note=user note")
self.addrule("PKG_XDATA=pkg|user:foo=bar")

View file

@ -0,0 +1,15 @@
self.description = "Upgrade from file, with user note in localdb"
lp = pmpkg("pkg")
lp.xdata=['user:note=user note']
self.addpkg2db("local", lp)
p = pmpkg("pkg", "1.0-2")
self.addpkg(p)
self.args = "-U --note-extra foo=bar %s" % p.filename()
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_VERSION=pkg|1.0-2")
self.addrule("PKG_XDATA=pkg|user:note=user note")
self.addrule("PKG_XDATA=pkg|user:foo=bar")

View file

@ -0,0 +1,18 @@
self.description = "Upgrade from file, with non-user xdata changed"
lp = pmpkg("pkg")
lp.xdata=['user:note=user note', 'pkgtype=pkg']
self.addpkg2db("local", lp)
p = pmpkg("pkg", "1.0-2")
p.xdata=['newfield=test']
self.addpkg(p)
self.args = "-U --note-extra foo=bar %s" % p.filename()
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_VERSION=pkg|1.0-2")
self.addrule("PKG_XDATA=pkg|user:note=user note")
self.addrule("PKG_XDATA=pkg|user:foo=bar")
self.addrule("PKG_XDATA=pkg|newfield=test")
self.addrule("!PKG_XDATA=pkg|pkgtype=pkg")

View file

@ -0,0 +1,12 @@
self.description = "Search for user note"
sp = pmpkg("searchpkg")
sp.xdata = ['user:foo=bar']
self.addpkg2db("local", sp);
self.args = "-Q --note-search bar"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PACMAN_OUTPUT=searchpkg")
self.addrule("PACMAN_OUTPUT=foo=bar")

View file

@ -0,0 +1,11 @@
self.description = "Print user notes in pkg info query"
sp = pmpkg("pkg")
sp.xdata = ['user:foo=bar']
self.addpkg2db("local", sp);
self.args = "-Qi pkg"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PACMAN_OUTPUT=foo=bar")