/* * hook.c * * Copyright (c) 2015 Pacman Development Team * * 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 . */ #include #include #include #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; alpm_list_t *triggers; alpm_list_t *depends; char *cmd; enum _alpm_hook_when_t when; int abort_on_fail; }; 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_hook_free(struct _alpm_hook_t *hook) { if(hook) { free(hook->name); free(hook->cmd); alpm_list_free_inner(hook->triggers, (alpm_list_fn_free) _alpm_trigger_free); alpm_list_free(hook->triggers); FREELIST(hook->depends); free(hook); } } 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, "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, "Exec") == 0) { STRDUP(hook->cmd, value, return 1); } else { error(_("hook %s line %d: invalid option %s\n"), file, line, value); } } #undef error return 0; } static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, 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 = alpm_db_get_pkg(handle->db_local, spkg->name); 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); 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_trigger_t *t) { 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(alpm_db_get_pkg(handle->db_local, pkg->name)) { if(t->op & ALPM_HOOK_OP_UPGRADE) { return 1; } } else { if(t->op & ALPM_HOOK_OP_INSTALL) { 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)) { return 1; } } } } return 0; } static int _alpm_hook_trigger_match(alpm_handle_t *handle, struct _alpm_trigger_t *t) { return t->type == ALPM_HOOK_TYPE_PACKAGE ? _alpm_hook_trigger_match_pkg(handle, t) : _alpm_hook_trigger_match_file(handle, t); } static int _alpm_hook_triggered(alpm_handle_t *handle, struct _alpm_hook_t *hook) { alpm_list_t *i; for(i = hook->triggers; i; i = i->next) { if(_alpm_hook_trigger_match(handle, i->data)) { return 1; } } return 0; } 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 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); char *const argv[] = { hook->cmd, NULL }; 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; } } return _alpm_run_chroot(handle, hook->cmd, argv); } int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when) { alpm_list_t *i, *hooks = NULL; const char *suffix = ".hook"; size_t suflen = strlen(suffix); 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(!(d = opendir(i->data))) { if(errno == ENOENT) { continue; } else { _alpm_log(handle, ALPM_LOG_ERROR, _("could not open directory: %s: %s\n"), (char *)i->data, strerror(errno)); ret = -1; continue; } } strncpy(path, i->data, PATH_MAX); dirlen = strlen(i->data); while((err = readdir_r(d, &entry, &result)) == 0 && result) { struct _alpm_hook_cb_ctx ctx = { handle, NULL }; struct stat buf; size_t name_len = strlen(entry.d_name); if(strcmp(entry.d_name, ".") == 0 || strcmp(entry.d_name, "..") == 0) { continue; } strncpy(path + dirlen, entry.d_name, PATH_MAX - dirlen); 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_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); } for(i = hooks; i; i = i->next) { struct _alpm_hook_t *hook = i->data; if(hook && hook->when == when && _alpm_hook_triggered(handle, hook)) { _alpm_log(handle, ALPM_LOG_DEBUG, "running hook %s\n", hook->name); if(_alpm_hook_run_hook(handle, hook) != 0 && hook->abort_on_fail) { ret = -1; } } } cleanup: alpm_list_free_inner(hooks, (alpm_list_fn_free) _alpm_hook_free); alpm_list_free(hooks); return ret; } /* vim: set noet: */