1
0
mirror of https://github.com/moparisthebest/pacman synced 2024-08-13 17:03:46 -04:00
pacman/lib/libalpm/hook.c
Andrew Gregory 2ee7a8d89a do not rely on localdb for hook matching
Relying on localdb to determine which trigger operations should match is
completely broken for PostTransaction hooks because the localdb has
already been updated.  Store a copy of the old version of any packages
being updated to use instead.

Fixes FS#47996

Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com>
Signed-off-by: Allan McRae <allan@archlinux.org>
2016-02-23 12:15:43 +10:00

773 lines
20 KiB
C

/*
* hook.c
*
* Copyright (c) 2015-2016 Pacman Development Team <pacman-dev@archlinux.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include "handle.h"
#include "hook.h"
#include "ini.h"
#include "log.h"
#include "trans.h"
#include "util.h"
enum _alpm_hook_op_t {
ALPM_HOOK_OP_INSTALL = (1 << 0),
ALPM_HOOK_OP_UPGRADE = (1 << 1),
ALPM_HOOK_OP_REMOVE = (1 << 2),
};
enum _alpm_trigger_type_t {
ALPM_HOOK_TYPE_PACKAGE = 1,
ALPM_HOOK_TYPE_FILE,
};
struct _alpm_trigger_t {
enum _alpm_hook_op_t op;
enum _alpm_trigger_type_t type;
alpm_list_t *targets;
};
struct _alpm_hook_t {
char *name;
char *desc;
alpm_list_t *triggers;
alpm_list_t *depends;
char **cmd;
alpm_list_t *matches;
alpm_hook_when_t when;
int abort_on_fail, needs_targets;
};
struct _alpm_hook_cb_ctx {
alpm_handle_t *handle;
struct _alpm_hook_t *hook;
};
static void _alpm_trigger_free(struct _alpm_trigger_t *trigger)
{
if(trigger) {
FREELIST(trigger->targets);
free(trigger);
}
}
static void _alpm_wordsplit_free(char **ws)
{
if(ws) {
char **c;
for(c = ws; *c; c++) {
free(*c);
}
free(ws);
}
}
static void _alpm_hook_free(struct _alpm_hook_t *hook)
{
if(hook) {
free(hook->name);
free(hook->desc);
_alpm_wordsplit_free(hook->cmd);
alpm_list_free_inner(hook->triggers, (alpm_list_fn_free) _alpm_trigger_free);
alpm_list_free(hook->triggers);
alpm_list_free(hook->matches);
FREELIST(hook->depends);
free(hook);
}
}
static int _alpm_trigger_validate(alpm_handle_t *handle,
struct _alpm_trigger_t *trigger, const char *file)
{
int ret = 0;
if(trigger->targets == NULL) {
ret = -1;
_alpm_log(handle, ALPM_LOG_ERROR,
_("Missing trigger targets in hook: %s\n"), file);
}
if(trigger->type == 0) {
ret = -1;
_alpm_log(handle, ALPM_LOG_ERROR,
_("Missing trigger type in hook: %s\n"), file);
}
if(trigger->op == 0) {
ret = -1;
_alpm_log(handle, ALPM_LOG_ERROR,
_("Missing trigger operation in hook: %s\n"), file);
}
return ret;
}
static int _alpm_hook_validate(alpm_handle_t *handle,
struct _alpm_hook_t *hook, const char *file)
{
alpm_list_t *i;
int ret = 0;
if(hook->triggers == NULL) {
/* special case: allow triggerless hooks as a way of creating dummy
* hooks that can be used to mask lower priority hooks */
return 0;
}
for(i = hook->triggers; i; i = i->next) {
if(_alpm_trigger_validate(handle, i->data, file) != 0) {
ret = -1;
}
}
if(hook->cmd == NULL) {
ret = -1;
_alpm_log(handle, ALPM_LOG_ERROR,
_("Missing Exec option in hook: %s\n"), file);
}
if(hook->when == 0) {
ret = -1;
_alpm_log(handle, ALPM_LOG_ERROR,
_("Missing When option in hook: %s\n"), file);
} else if(hook->when != ALPM_HOOK_PRE_TRANSACTION && hook->abort_on_fail) {
_alpm_log(handle, ALPM_LOG_WARNING,
_("AbortOnFail set for PostTransaction hook: %s\n"), file);
}
return ret;
}
static char **_alpm_wordsplit(char *str)
{
char *c = str, *end;
char **out = NULL, **outsave;
size_t count = 0;
if(str == NULL) {
errno = EINVAL;
return NULL;
}
for(c = str; isspace(*c); c++);
while(*c) {
size_t wordlen = 0;
/* extend our array */
outsave = out;
if((out = realloc(out, (count + 1) * sizeof(char*))) == NULL) {
out = outsave;
goto error;
}
/* calculate word length and check for unbalanced quotes */
for(end = c; *end && !isspace(*end); end++) {
if(*end == '\'' || *end == '"') {
char quote = *end;
while(*(++end) && *end != quote) {
if(*end == '\\' && *(end + 1) == quote) {
end++;
}
wordlen++;
}
if(*end != quote) {
errno = EINVAL;
goto error;
}
} else {
if(*end == '\\' && (end[1] == '\'' || end[1] == '"')) {
end++; /* skip the '\\' */
}
wordlen++;
}
}
if(wordlen == (size_t) (end - c)) {
/* no internal quotes or escapes, copy it the easy way */
if((out[count++] = strndup(c, wordlen)) == NULL) {
goto error;
}
} else {
/* manually copy to remove quotes and escapes */
char *dest = out[count++] = malloc(wordlen + 1);
if(dest == NULL) { goto error; }
while(c < end) {
if(*c == '\'' || *c == '"') {
char quote = *c;
/* we know there must be a matching end quote,
* no need to check for '\0' */
for(c++; *c != quote; c++) {
if(*c == '\\' && *(c + 1) == quote) {
c++;
}
*(dest++) = *c;
}
c++;
} else {
if(*c == '\\' && (c[1] == '\'' || c[1] == '"')) {
c++; /* skip the '\\' */
}
*(dest++) = *(c++);
}
}
*dest = '\0';
}
if(*end == '\0') {
break;
} else {
for(c = end + 1; isspace(*c); c++);
}
}
outsave = out;
if((out = realloc(out, (count + 1) * sizeof(char*))) == NULL) {
out = outsave;
goto error;
}
out[count++] = NULL;
return out;
error:
/* can't use wordsplit_free here because NULL has not been appended */
while(count) {
free(out[--count]);
}
free(out);
return NULL;
}
static int _alpm_hook_parse_cb(const char *file, int line,
const char *section, char *key, char *value, void *data)
{
struct _alpm_hook_cb_ctx *ctx = data;
alpm_handle_t *handle = ctx->handle;
struct _alpm_hook_t *hook = ctx->hook;
#define error(...) _alpm_log(handle, ALPM_LOG_ERROR, __VA_ARGS__); return 1;
if(!section && !key) {
error(_("error while reading hook %s: %s\n"), file, strerror(errno));
} else if(!section) {
error(_("hook %s line %d: invalid option %s\n"), file, line, key);
} else if(!key) {
/* beginning a new section */
if(strcmp(section, "Trigger") == 0) {
struct _alpm_trigger_t *t;
CALLOC(t, sizeof(struct _alpm_trigger_t), 1, return 1);
hook->triggers = alpm_list_add(hook->triggers, t);
} else if(strcmp(section, "Action") == 0) {
/* no special processing required */
} else {
error(_("hook %s line %d: invalid section %s\n"), file, line, section);
}
} else if(strcmp(section, "Trigger") == 0) {
struct _alpm_trigger_t *t = hook->triggers->prev->data;
if(strcmp(key, "Operation") == 0) {
if(strcmp(value, "Install") == 0) {
t->op |= ALPM_HOOK_OP_INSTALL;
} else if(strcmp(value, "Upgrade") == 0) {
t->op |= ALPM_HOOK_OP_UPGRADE;
} else if(strcmp(value, "Remove") == 0) {
t->op |= ALPM_HOOK_OP_REMOVE;
} else {
error(_("hook %s line %d: invalid value %s\n"), file, line, value);
}
} else if(strcmp(key, "Type") == 0) {
if(strcmp(value, "Package") == 0) {
t->type = ALPM_HOOK_TYPE_PACKAGE;
} else if(strcmp(value, "File") == 0) {
t->type = ALPM_HOOK_TYPE_FILE;
} else {
error(_("hook %s line %d: invalid value %s\n"), file, line, value);
}
} else if(strcmp(key, "Target") == 0) {
char *val;
STRDUP(val, value, return 1);
t->targets = alpm_list_add(t->targets, val);
} else {
error(_("hook %s line %d: invalid option %s\n"), file, line, key);
}
} else if(strcmp(section, "Action") == 0) {
if(strcmp(key, "When") == 0) {
if(strcmp(value, "PreTransaction") == 0) {
hook->when = ALPM_HOOK_PRE_TRANSACTION;
} else if(strcmp(value, "PostTransaction") == 0) {
hook->when = ALPM_HOOK_POST_TRANSACTION;
} else {
error(_("hook %s line %d: invalid value %s\n"), file, line, value);
}
} else if(strcmp(key, "Description") == 0) {
STRDUP(hook->desc, value, return 1);
} else if(strcmp(key, "Depends") == 0) {
char *val;
STRDUP(val, value, return 1);
hook->depends = alpm_list_add(hook->depends, val);
} else if(strcmp(key, "AbortOnFail") == 0) {
hook->abort_on_fail = 1;
} else if(strcmp(key, "NeedsTargets") == 0) {
hook->needs_targets = 1;
} else if(strcmp(key, "Exec") == 0) {
if((hook->cmd = _alpm_wordsplit(value)) == NULL) {
if(errno == EINVAL) {
error(_("hook %s line %d: invalid value %s\n"), file, line, value);
} else {
error(_("hook %s line %d: unable to set option (%s)\n"),
file, line, strerror(errno));
}
}
} else {
error(_("hook %s line %d: invalid option %s\n"), file, line, key);
}
}
#undef error
return 0;
}
static int _alpm_hook_trigger_match_file(alpm_handle_t *handle,
struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
{
alpm_list_t *i, *j, *install = NULL, *upgrade = NULL, *remove = NULL;
size_t isize = 0, rsize = 0;
int ret = 0;
/* check if file will be installed */
for(i = handle->trans->add; i; i = i->next) {
alpm_pkg_t *pkg = i->data;
alpm_filelist_t filelist = pkg->files;
size_t f;
for(f = 0; f < filelist.count; f++) {
if(alpm_option_match_noextract(handle, filelist.files[f].name) == 0) {
continue;
}
if(_alpm_fnmatch_patterns(t->targets, filelist.files[f].name) == 0) {
install = alpm_list_add(install, filelist.files[f].name);
isize++;
}
}
}
/* check if file will be removed due to package upgrade */
for(i = handle->trans->add; i; i = i->next) {
alpm_pkg_t *spkg = i->data;
alpm_pkg_t *pkg = spkg->oldpkg;
if(pkg) {
alpm_filelist_t filelist = pkg->files;
size_t f;
for(f = 0; f < filelist.count; f++) {
if(_alpm_fnmatch_patterns(t->targets, filelist.files[f].name) == 0) {
remove = alpm_list_add(remove, filelist.files[f].name);
rsize++;
}
}
}
}
/* check if file will be removed due to package removal */
for(i = handle->trans->remove; i; i = i->next) {
alpm_pkg_t *pkg = i->data;
alpm_filelist_t filelist = pkg->files;
size_t f;
for(f = 0; f < filelist.count; f++) {
if(_alpm_fnmatch_patterns(t->targets, filelist.files[f].name) == 0) {
remove = alpm_list_add(remove, filelist.files[f].name);
rsize++;
}
}
}
i = install = alpm_list_msort(install, isize, (alpm_list_fn_cmp)strcmp);
j = remove = alpm_list_msort(remove, rsize, (alpm_list_fn_cmp)strcmp);
while(i) {
while(j && strcmp(i->data, j->data) > 0) {
j = j->next;
}
if(j == NULL) {
break;
}
if(strcmp(i->data, j->data) == 0) {
char *path = i->data;
upgrade = alpm_list_add(upgrade, path);
while(i && strcmp(i->data, path) == 0) {
alpm_list_t *next = i->next;
install = alpm_list_remove_item(install, i);
free(i);
i = next;
}
while(j && strcmp(j->data, path) == 0) {
alpm_list_t *next = j->next;
remove = alpm_list_remove_item(remove, j);
free(j);
j = next;
}
} else {
i = i->next;
}
}
ret = (t->op & ALPM_HOOK_OP_INSTALL && install)
|| (t->op & ALPM_HOOK_OP_UPGRADE && upgrade)
|| (t->op & ALPM_HOOK_OP_REMOVE && remove);
if(hook->needs_targets) {
#define _save_matches(_op, _matches) \
if(t->op & _op && _matches) { \
hook->matches = alpm_list_join(hook->matches, _matches); \
} else { \
alpm_list_free(_matches); \
}
_save_matches(ALPM_HOOK_OP_INSTALL, install);
_save_matches(ALPM_HOOK_OP_UPGRADE, upgrade);
_save_matches(ALPM_HOOK_OP_REMOVE, remove);
#undef _save_matches
} else {
alpm_list_free(install);
alpm_list_free(upgrade);
alpm_list_free(remove);
}
return ret;
}
static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle,
struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
{
alpm_list_t *install = NULL, *upgrade = NULL, *remove = NULL;
if(t->op & ALPM_HOOK_OP_INSTALL || t->op & ALPM_HOOK_OP_UPGRADE) {
alpm_list_t *i;
for(i = handle->trans->add; i; i = i->next) {
alpm_pkg_t *pkg = i->data;
if(_alpm_fnmatch_patterns(t->targets, pkg->name) == 0) {
if(pkg->oldpkg) {
if(t->op & ALPM_HOOK_OP_UPGRADE) {
if(hook->needs_targets) {
upgrade = alpm_list_add(upgrade, pkg->name);
} else {
return 1;
}
}
} else {
if(t->op & ALPM_HOOK_OP_INSTALL) {
if(hook->needs_targets) {
install = alpm_list_add(install, pkg->name);
} else {
return 1;
}
}
}
}
}
}
if(t->op & ALPM_HOOK_OP_REMOVE) {
alpm_list_t *i;
for(i = handle->trans->remove; i; i = i->next) {
alpm_pkg_t *pkg = i->data;
if(pkg && _alpm_fnmatch_patterns(t->targets, pkg->name) == 0) {
if(!alpm_list_find(handle->trans->add, pkg, _alpm_pkg_cmp)) {
if(hook->needs_targets) {
remove = alpm_list_add(remove, pkg->name);
} else {
return 1;
}
}
}
}
}
/* if we reached this point we either need the target lists or we didn't
* match anything and the following calls will all be no-ops */
hook->matches = alpm_list_join(hook->matches, install);
hook->matches = alpm_list_join(hook->matches, upgrade);
hook->matches = alpm_list_join(hook->matches, remove);
return install || upgrade || remove;
}
static int _alpm_hook_trigger_match(alpm_handle_t *handle,
struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
{
return t->type == ALPM_HOOK_TYPE_PACKAGE
? _alpm_hook_trigger_match_pkg(handle, hook, t)
: _alpm_hook_trigger_match_file(handle, hook, t);
}
static int _alpm_hook_triggered(alpm_handle_t *handle, struct _alpm_hook_t *hook)
{
alpm_list_t *i;
int ret = 0;
for(i = hook->triggers; i; i = i->next) {
if(_alpm_hook_trigger_match(handle, hook, i->data)) {
if(!hook->needs_targets) {
return 1;
} else {
ret = 1;
}
}
}
return ret;
}
static int _alpm_hook_cmp(struct _alpm_hook_t *h1, struct _alpm_hook_t *h2)
{
return strcmp(h1->name, h2->name);
}
static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle)
{
while(haystack) {
struct _alpm_hook_t *h = haystack->data;
if(h && strcmp(h->name, needle) == 0) {
return haystack;
}
haystack = haystack->next;
}
return NULL;
}
static ssize_t _alpm_hook_feed_targets(char *buf, ssize_t needed, alpm_list_t **pos)
{
size_t remaining = needed, written = 0;;
size_t len;
while(*pos && (len = strlen((*pos)->data)) + 1 <= remaining) {
memcpy(buf, (*pos)->data, len);
buf[len++] = '\n';
*pos = (*pos)->next;
buf += len;
remaining -= len;
written += len;
}
if(*pos && remaining) {
memcpy(buf, (*pos)->data, remaining);
(*pos)->data = (char*) (*pos)->data + remaining;
written += remaining;
}
return written;
}
static alpm_list_t *_alpm_strlist_dedup(alpm_list_t *list)
{
alpm_list_t *i = list;
while(i) {
alpm_list_t *next = i->next;
while(next && strcmp(i->data, next->data) == 0) {
list = alpm_list_remove_item(list, next);
free(next);
next = i->next;
}
i = next;
}
return list;
}
static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook)
{
alpm_list_t *i, *pkgs = _alpm_db_get_pkgcache(handle->db_local);
for(i = hook->depends; i; i = i->next) {
if(!alpm_find_satisfier(pkgs, i->data)) {
_alpm_log(handle, ALPM_LOG_ERROR, _("unable to run hook %s: %s\n"),
hook->name, _("could not satisfy dependencies"));
return -1;
}
}
if(hook->needs_targets) {
alpm_list_t *ctx;
hook->matches = alpm_list_msort(hook->matches,
alpm_list_count(hook->matches), (alpm_list_fn_cmp)strcmp);
/* hooks with multiple triggers could have duplicate matches */
ctx = hook->matches = _alpm_strlist_dedup(hook->matches);
return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd,
(_alpm_cb_io) _alpm_hook_feed_targets, &ctx);
} else {
return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd, NULL, NULL);
}
}
int _alpm_hook_run(alpm_handle_t *handle, alpm_hook_when_t when)
{
alpm_event_hook_t event = { .when = when };
alpm_event_hook_run_t hook_event;
alpm_list_t *i, *hooks = NULL, *hooks_triggered = NULL;
const char *suffix = ".hook";
size_t suflen = strlen(suffix), triggered = 0;
int ret = 0;
for(i = alpm_list_last(handle->hookdirs); i; i = alpm_list_previous(i)) {
int err;
char path[PATH_MAX];
size_t dirlen;
struct dirent entry, *result;
DIR *d;
if((dirlen = strlen(i->data)) >= PATH_MAX) {
_alpm_log(handle, ALPM_LOG_ERROR, _("could not open directory: %s: %s\n"),
(char *)i->data, strerror(ENAMETOOLONG));
ret = -1;
continue;
}
memcpy(path, i->data, dirlen + 1);
if(!(d = opendir(path))) {
if(errno == ENOENT) {
continue;
} else {
_alpm_log(handle, ALPM_LOG_ERROR,
_("could not open directory: %s: %s\n"), path, strerror(errno));
ret = -1;
continue;
}
}
while((err = readdir_r(d, &entry, &result)) == 0 && result) {
struct _alpm_hook_cb_ctx ctx = { handle, NULL };
struct stat buf;
size_t name_len;
if(strcmp(entry.d_name, ".") == 0 || strcmp(entry.d_name, "..") == 0) {
continue;
}
if((name_len = strlen(entry.d_name)) >= PATH_MAX - dirlen) {
_alpm_log(handle, ALPM_LOG_ERROR, _("could not open file: %s%s: %s\n"),
path, entry.d_name, strerror(ENAMETOOLONG));
ret = -1;
continue;
}
memcpy(path + dirlen, entry.d_name, name_len + 1);
if(name_len < suflen
|| strcmp(entry.d_name + name_len - suflen, suffix) != 0) {
_alpm_log(handle, ALPM_LOG_DEBUG, "skipping non-hook file %s\n", path);
continue;
}
if(find_hook(hooks, entry.d_name)) {
_alpm_log(handle, ALPM_LOG_DEBUG, "skipping overridden hook %s\n", path);
continue;
}
if(fstatat(dirfd(d), entry.d_name, &buf, 0) != 0) {
_alpm_log(handle, ALPM_LOG_ERROR,
_("could not stat file %s: %s\n"), path, strerror(errno));
ret = -1;
continue;
}
if(S_ISDIR(buf.st_mode)) {
_alpm_log(handle, ALPM_LOG_DEBUG, "skipping directory %s\n", path);
continue;
}
CALLOC(ctx.hook, sizeof(struct _alpm_hook_t), 1,
ret = -1; closedir(d); goto cleanup);
_alpm_log(handle, ALPM_LOG_DEBUG, "parsing hook file %s\n", path);
if(parse_ini(path, _alpm_hook_parse_cb, &ctx) != 0
|| _alpm_hook_validate(handle, ctx.hook, path)) {
_alpm_log(handle, ALPM_LOG_DEBUG, "parsing hook file %s failed\n", path);
_alpm_hook_free(ctx.hook);
ret = -1;
continue;
}
STRDUP(ctx.hook->name, entry.d_name, ret = -1; closedir(d); goto cleanup);
hooks = alpm_list_add(hooks, ctx.hook);
}
if(err != 0) {
_alpm_log(handle, ALPM_LOG_ERROR, _("could not read directory: %s: %s\n"),
(char *) i->data, strerror(errno));
ret = -1;
}
closedir(d);
}
if(ret != 0 && when == ALPM_HOOK_PRE_TRANSACTION) {
goto cleanup;
}
hooks = alpm_list_msort(hooks, alpm_list_count(hooks),
(alpm_list_fn_cmp)_alpm_hook_cmp);
for(i = hooks; i; i = i->next) {
struct _alpm_hook_t *hook = i->data;
if(hook && hook->when == when && _alpm_hook_triggered(handle, hook)) {
hooks_triggered = alpm_list_add(hooks_triggered, hook);
triggered++;
}
}
if(hooks_triggered != NULL) {
event.type = ALPM_EVENT_HOOK_START;
EVENT(handle, (void *)&event);
hook_event.position = 1;
hook_event.total = triggered;
for(i = hooks_triggered; i; i = i->next, hook_event.position++) {
struct _alpm_hook_t *hook = i->data;
alpm_logaction(handle, ALPM_CALLER_PREFIX, "running '%s'...\n", hook->name);
hook_event.type = ALPM_EVENT_HOOK_RUN_START;
hook_event.name = hook->name;
hook_event.desc = hook->desc;
EVENT(handle, &hook_event);
if(_alpm_hook_run_hook(handle, hook) != 0 && hook->abort_on_fail) {
ret = -1;
}
hook_event.type = ALPM_EVENT_HOOK_RUN_DONE;
EVENT(handle, &hook_event);
if(ret != 0 && when == ALPM_HOOK_PRE_TRANSACTION) {
break;
}
}
alpm_list_free(hooks_triggered);
event.type = ALPM_EVENT_HOOK_DONE;
EVENT(handle, (void *)&event);
}
cleanup:
alpm_list_free_inner(hooks, (alpm_list_fn_free) _alpm_hook_free);
alpm_list_free(hooks);
return ret;
}
/* vim: set noet: */