diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 96e5e643..0fda7ece 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -1438,6 +1438,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 */ @@ -2677,6 +2688,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 */ @@ -2698,6 +2747,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 */ /** @} */ diff --git a/lib/libalpm/be_local.c b/lib/libalpm/be_local.c index d5f97380..971380c1 100644 --- a/lib/libalpm/be_local.c +++ b/lib/libalpm/be_local.c @@ -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, diff --git a/lib/libalpm/db.c b/lib/libalpm/db.c index 92b22a6f..e12b4ee4 100644 --- a/lib/libalpm/db.c +++ b/lib/libalpm/db.c @@ -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(®, 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(®, 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(®); + } + for(i = needles; i; i = i->next) { char *targ; regex_t reg; diff --git a/lib/libalpm/db.h b/lib/libalpm/db.h index 88369581..80ba903e 100644 --- a/lib/libalpm/db.h +++ b/lib/libalpm/db.h @@ -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); diff --git a/lib/libalpm/package.c b/lib/libalpm/package.c index a7bccde3..e1f50f91 100644 --- a/lib/libalpm/package.c +++ b/lib/libalpm/package.c @@ -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) { diff --git a/lib/libalpm/package.h b/lib/libalpm/package.h index 0f8f9aa3..27d8a68f 100644 --- a/lib/libalpm/package.h +++ b/lib/libalpm/package.h @@ -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); diff --git a/lib/libalpm/sync.c b/lib/libalpm/sync.c index 087c48de..46d0c9e9 100644 --- a/lib/libalpm/sync.c +++ b/lib/libalpm/sync.c @@ -1175,6 +1175,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 */ } @@ -1209,6 +1224,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 */ diff --git a/src/pacman/conf.c b/src/pacman/conf.c index a0e0e96a..6beee93e 100644 --- a/src/pacman/conf.c +++ b/src/pacman/conf.c @@ -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); diff --git a/src/pacman/conf.h b/src/pacman/conf.h index 2c4fddf0..bf2fed0f 100644 --- a/src/pacman/conf.h +++ b/src/pacman/conf.h @@ -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 */ diff --git a/src/pacman/database.c b/src/pacman/database.c index b8f7a7fd..febbdb52 100644 --- a/src/pacman/database.c +++ b/src/pacman/database.c @@ -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; } diff --git a/src/pacman/package.c b/src/pacman/package.c index 6ac6c04e..2feaf32a 100644 --- a/src/pacman/package.c +++ b/src/pacman/package.c @@ -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,9 +569,15 @@ 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(alpm_db_search(db, targets, &searchlist) != 0) { - return -1; + 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 { @@ -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); } diff --git a/src/pacman/package.h b/src/pacman/package.h index 6b18d5a1..ca1d2859 100644 --- a/src/pacman/package.h +++ b/src/pacman/package.h @@ -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 */ diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c index 2866fc98..901e7485 100644 --- a/src/pacman/pacman.c +++ b/src/pacman/pacman.c @@ -147,6 +147,9 @@ static void usage(int op, const char * const myname) addlist(_(" -p, --file query a package file instead of the database\n")); addlist(_(" -q, --quiet show less information for query and search\n")); addlist(_(" -s, --search search locally-installed packages for matching strings\n")); + addlist(_(" --note-search \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 set user note for a package (default key: 'note')\n")); + addlist(_(" --note-extra =\n" + " set extra user note for a package\n")); + addlist(_(" --delete-note \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 set user note for a package (default key: 'note')\n")); + addlist(_(" --note-extra =\n" + " set extra user note for a package\n")); addlist(_(" --ignore ignore a package upgrade (can be used more than once)\n")); addlist(_(" --ignoregroup \n" " ignore a group upgrade (can be used more than once)\n")); @@ -521,6 +532,16 @@ static int parsearg_database(int opt) case OP_QUIET: 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}, diff --git a/src/pacman/sync.c b/src/pacman/sync.c index a3797e85..4b6742d9 100644 --- a/src/pacman/sync.c +++ b/src/pacman/sync.c @@ -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(¬es_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(¬es_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; diff --git a/test/pacman/meson.build b/test/pacman/meson.build index 4a73b600..22daf600 100644 --- a/test/pacman/meson.build +++ b/test/pacman/meson.build @@ -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 = { diff --git a/test/pacman/pmdb.py b/test/pacman/pmdb.py index e0005ded..3cbd2213 100644 --- a/test/pacman/pmdb.py +++ b/test/pacman/pmdb.py @@ -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) diff --git a/test/pacman/pmpkg.py b/test/pacman/pmpkg.py index 2be87908..d5c66b05 100644 --- a/test/pacman/pmpkg.py +++ b/test/pacman/pmpkg.py @@ -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 diff --git a/test/pacman/pmrule.py b/test/pacman/pmrule.py index 2dada027..5d70debc 100644 --- a/test/pacman/pmrule.py +++ b/test/pacman/pmrule.py @@ -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 diff --git a/test/pacman/tests/usernote-01-change-localdb.py b/test/pacman/tests/usernote-01-change-localdb.py new file mode 100644 index 00000000..0be4e1b2 --- /dev/null +++ b/test/pacman/tests/usernote-01-change-localdb.py @@ -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") diff --git a/test/pacman/tests/usernote-02-set-localdb.py b/test/pacman/tests/usernote-02-set-localdb.py new file mode 100644 index 00000000..b43dda16 --- /dev/null +++ b/test/pacman/tests/usernote-02-set-localdb.py @@ -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") diff --git a/test/pacman/tests/usernote-03-set-sync.py b/test/pacman/tests/usernote-03-set-sync.py new file mode 100644 index 00000000..be8a26eb --- /dev/null +++ b/test/pacman/tests/usernote-03-set-sync.py @@ -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") diff --git a/test/pacman/tests/usernote-04-prs-sync-reinstall.py b/test/pacman/tests/usernote-04-prs-sync-reinstall.py new file mode 100644 index 00000000..5960792e --- /dev/null +++ b/test/pacman/tests/usernote-04-prs-sync-reinstall.py @@ -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") diff --git a/test/pacman/tests/usernote-05-prs-sync-upgrade.py b/test/pacman/tests/usernote-05-prs-sync-upgrade.py new file mode 100644 index 00000000..d0b22e7f --- /dev/null +++ b/test/pacman/tests/usernote-05-prs-sync-upgrade.py @@ -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") diff --git a/test/pacman/tests/usernote-06-prs-upgrade-from-file.py b/test/pacman/tests/usernote-06-prs-upgrade-from-file.py new file mode 100644 index 00000000..1425c786 --- /dev/null +++ b/test/pacman/tests/usernote-06-prs-upgrade-from-file.py @@ -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") diff --git a/test/pacman/tests/usernote-07-prs-non-user-xdata.py b/test/pacman/tests/usernote-07-prs-non-user-xdata.py new file mode 100644 index 00000000..23cdff63 --- /dev/null +++ b/test/pacman/tests/usernote-07-prs-non-user-xdata.py @@ -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") diff --git a/test/pacman/tests/usernote-08-query-note-search.py b/test/pacman/tests/usernote-08-query-note-search.py new file mode 100644 index 00000000..48acb57b --- /dev/null +++ b/test/pacman/tests/usernote-08-query-note-search.py @@ -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") + diff --git a/test/pacman/tests/usernote-09-query-note-info.py b/test/pacman/tests/usernote-09-query-note-info.py new file mode 100644 index 00000000..fc13a294 --- /dev/null +++ b/test/pacman/tests/usernote-09-query-note-info.py @@ -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") +