[pacman-dev] [PATCH 0/4] hooks
= TODO = * documentation * run PreTransaction hooks *after* conflict checks * hook/trigger validation * masking hooks with an empty file or symlink to /dev/null * tests = Hook Directories = ALPM handles keep a list of hook directories to scan. Initially, this list is populated with a single directory: /usr/share/alpm/hooks/. This directory is intended for packages to be able to place hooks in a front-end agnostic manner. Front-ends may specify any number of additional directories to search, with later directories overriding earlier ones. Front-ends can disable hooks altogether by setting an empty list. = Parsing Hooks = Configured directories are scanned for files matching '*.hook'. Hook files must be in pacman-style INI format with the following available options: [Trigger] (multiple Trigger sections allowed) Operation = Sync|Remove (Required) Type = File|Package (Required) Target = <Path|PkgName> (Required, multiple allowed) [Action] (only one Action section allowed) When = PreTransaction|PostTransaction (Required) Exec = <Command> (Required) Depends = <PkgName> (Optional) AbortOnFail (Optional, PreTransaction only) Unlike pacman.conf multiple values are *not* allowed on a single line. Exec is currently limited to the path of the executable to run, arguments are not allowed. This is intended to be a temporary limitation until I decide the best way to go about splitting the arguments, which depends in part on whether we want to try to provide hooks with the packages/files that triggered them. Targets are matched the same as IgnorePkg in pacman.conf, meaning multiple targets can be specified and targets can be negated with a leading '!'. If multiple Trigger sections are specified the hook will run if any of them match the transaction. = Running Hooks = Hooks are not cached after running. Directories are rescanned and hooks reloaded each time. This is to make sure that any hooks added/removed during a transaction are appropriately accounted for. Hooks are run exactly the same as install scriptlets (chroot, output passed to the front-end, etc.). They do *not* have access to which package/file triggered the hook. For the time being, run order is officially undefined. apg Andrew Gregory (4): move strtim to util-common move ini parser into common wip add hooks add example hooks hooks/checkboot.hook | 17 +++ hooks/checkmount | 13 ++ hooks/checkmount.conf | 1 + hooks/checkmount.hook | 17 +++ hooks/sync.hook | 16 +++ lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.c | 1 + lib/libalpm/alpm.h | 10 ++ lib/libalpm/handle.c | 59 ++++++++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 34 +++++ lib/libalpm/trans.c | 6 + src/common/ini.c | 116 ++++++++++++++++ src/common/ini.h | 30 ++++ src/common/util-common.c | 39 ++++++ src/common/util-common.h | 2 + src/pacman/ini.c | 117 +--------------- src/pacman/ini.h | 31 +---- src/pacman/util.c | 39 ------ src/pacman/util.h | 1 - src/util/pactree.c | 36 ----- 22 files changed, 720 insertions(+), 222 deletions(-) create mode 100644 hooks/checkboot.hook create mode 100644 hooks/checkmount create mode 100644 hooks/checkmount.conf create mode 100644 hooks/checkmount.hook create mode 100644 hooks/sync.hook create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h create mode 100644 src/common/ini.c create mode 100644 src/common/ini.h mode change 100644 => 120000 src/pacman/ini.c mode change 100644 => 120000 src/pacman/ini.h -- 2.4.5
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/util-common.c | 39 +++++++++++++++++++++++++++++++++++++++ src/common/util-common.h | 2 ++ src/pacman/ini.c | 2 +- src/pacman/util.c | 39 --------------------------------------- src/pacman/util.h | 1 - src/util/pactree.c | 36 ------------------------------------ 6 files changed, 42 insertions(+), 77 deletions(-) diff --git a/src/common/util-common.c b/src/common/util-common.c index e834168..542dcfd 100644 --- a/src/common/util-common.c +++ b/src/common/util-common.c @@ -17,6 +17,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <ctype.h> #include <errno.h> #include <stdlib.h> #include <string.h> @@ -127,6 +128,44 @@ char *safe_fgets(char *s, int size, FILE *stream) return ret; } +/* Trim whitespace and newlines from a string + */ +size_t strtrim(char *str) +{ + char *end, *pch = str; + + if(str == NULL || *str == '\0') { + /* string is empty, so we're done. */ + return 0; + } + + while(isspace((unsigned char)*pch)) { + pch++; + } + if(pch != str) { + size_t len = strlen(pch); + if(len) { + memmove(str, pch, len + 1); + pch = str; + } else { + *str = '\0'; + } + } + + /* check if there wasn't anything but whitespace in the string. */ + if(*str == '\0') { + return 0; + } + + end = (str + strlen(str) - 1); + while(isspace((unsigned char)*end)) { + end--; + } + *++end = '\0'; + + return end - pch; +} + #ifndef HAVE_STRNLEN /* A quick and dirty implementation derived from glibc */ /** Determines the length of a fixed-size string. diff --git a/src/common/util-common.h b/src/common/util-common.h index a2093be..af2ebda 100644 --- a/src/common/util-common.h +++ b/src/common/util-common.h @@ -30,6 +30,8 @@ int llstat(char *path, struct stat *buf); char *safe_fgets(char *s, int size, FILE *stream); +size_t strtrim(char *str); + #ifndef HAVE_STRNDUP char *strndup(const char *s, size_t n); #endif diff --git a/src/pacman/ini.c b/src/pacman/ini.c index fed0b18..da30af1 100644 --- a/src/pacman/ini.c +++ b/src/pacman/ini.c @@ -24,7 +24,7 @@ #include <alpm.h> #include "ini.h" -#include "util.h" +#include "util-common.h" /** * @brief Parse a pacman-style INI config file. diff --git a/src/pacman/util.c b/src/pacman/util.c index 73bcb15..d3bbf98 100644 --- a/src/pacman/util.c +++ b/src/pacman/util.c @@ -29,7 +29,6 @@ #include <stdint.h> /* intmax_t */ #include <string.h> #include <errno.h> -#include <ctype.h> #include <dirent.h> #include <unistd.h> #include <limits.h> @@ -320,44 +319,6 @@ void indentprint(const char *str, unsigned short indent, unsigned short cols) free(wcstr); } -/* Trim whitespace and newlines from a string - */ -size_t strtrim(char *str) -{ - char *end, *pch = str; - - if(str == NULL || *str == '\0') { - /* string is empty, so we're done. */ - return 0; - } - - while(isspace((unsigned char)*pch)) { - pch++; - } - if(pch != str) { - size_t len = strlen(pch); - if(len) { - memmove(str, pch, len + 1); - pch = str; - } else { - *str = '\0'; - } - } - - /* check if there wasn't anything but whitespace in the string. */ - if(*str == '\0') { - return 0; - } - - end = (str + strlen(str) - 1); - while(isspace((unsigned char)*end)) { - end--; - } - *++end = '\0'; - - return end - pch; -} - /* Replace all occurrences of 'needle' with 'replace' in 'str', returning * a new string (must be free'd) */ char *strreplace(const char *str, const char *needle, const char *replace) diff --git a/src/pacman/util.h b/src/pacman/util.h index fda9b51..af5cf01 100644 --- a/src/pacman/util.h +++ b/src/pacman/util.h @@ -53,7 +53,6 @@ unsigned short getcols(void); void columns_cache_reset(void); int rmrf(const char *path); void indentprint(const char *str, unsigned short indent, unsigned short cols); -size_t strtrim(char *str); char *strreplace(const char *str, const char *needle, const char *replace); void string_display(const char *title, const char *string, unsigned short cols); double humanize_size(off_t bytes, const char target_unit, int precision, diff --git a/src/util/pactree.c b/src/util/pactree.c index 11ad7ca..6969995 100644 --- a/src/util/pactree.c +++ b/src/util/pactree.c @@ -123,42 +123,6 @@ int searchsyncs = 0; const char *dbpath = DBPATH; const char *configfile = CONFFILE; -static size_t strtrim(char *str) -{ - char *end, *pch = str; - - if(str == NULL || *str == '\0') { - /* string is empty, so we're done. */ - return 0; - } - - while(isspace((unsigned char)*pch)) { - pch++; - } - if(pch != str) { - size_t len = strlen(pch); - if(len) { - memmove(str, pch, len + 1); - pch = str; - } else { - *str = '\0'; - } - } - - /* check if there wasn't anything but whitespace in the string. */ - if(*str == '\0') { - return 0; - } - - end = (str + strlen(str) - 1); - while(isspace((unsigned char)*end)) { - end--; - } - *++end = '\0'; - - return end - pch; -} - static int register_syncs(void) { FILE *fp; -- 2.4.5
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/ini.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/common/ini.h | 30 ++++++++++++++ src/pacman/ini.c | 117 +------------------------------------------------------ src/pacman/ini.h | 31 +-------------- 4 files changed, 148 insertions(+), 146 deletions(-) create mode 100644 src/common/ini.c create mode 100644 src/common/ini.h mode change 100644 => 120000 src/pacman/ini.c mode change 100644 => 120000 src/pacman/ini.h diff --git a/src/common/ini.c b/src/common/ini.c new file mode 100644 index 0000000..fed0b18 --- /dev/null +++ b/src/common/ini.c @@ -0,0 +1,116 @@ +/* + * ini.c + * + * Copyright (c) 2013-2015 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 <errno.h> +#include <limits.h> +#include <string.h> /* strdup */ + +#include <alpm.h> + +#include "ini.h" +#include "util.h" + +/** + * @brief Parse a pacman-style INI config file. + * + * @param file path to the config file + * @param cb callback for key/value pairs + * @param data caller defined data to be passed to the callback + * + * @return the callback return value + * + * @note The callback will be called at the beginning of each section with an + * empty key and value and for each key/value pair. + * + * @note If the parser encounters an error the callback will be called with + * section, key, and value set to NULL and errno set by fopen, fgets, or + * strdup. + * + * @note The @a key and @a value passed to @ cb will be overwritten between + * calls. The section name will remain valid until after @a cb is called to + * begin a new section. + * + * @note Parsing will immediately stop if the callback returns non-zero. + */ +int parse_ini(const char *file, ini_parser_fn cb, void *data) +{ + char line[PATH_MAX], *section_name = NULL; + FILE *fp = NULL; + int linenum = 0; + int ret = 0; + + fp = fopen(file, "r"); + if(fp == NULL) { + return cb(file, 0, NULL, NULL, NULL, data); + } + + while(safe_fgets(line, PATH_MAX, fp)) { + char *key, *value, *ptr; + size_t line_len; + + linenum++; + + /* ignore whole line and end of line comments */ + if((ptr = strchr(line, '#'))) { + *ptr = '\0'; + } + + line_len = strtrim(line); + + if(line_len == 0) { + continue; + } + + if(line[0] == '[' && line[line_len - 1] == ']') { + char *name; + /* new config section, skip the '[' */ + name = strdup(line + 1); + name[line_len - 2] = '\0'; + + ret = cb(file, linenum, name, NULL, NULL, data); + free(section_name); + section_name = name; + + /* we're at a new section; perform any post-actions for the prior */ + if(ret) { + goto cleanup; + } + continue; + } + + /* directive */ + /* strsep modifies the 'line' string: 'key \0 value' */ + key = line; + value = line; + strsep(&value, "="); + strtrim(key); + strtrim(value); + + if((ret = cb(file, linenum, section_name, key, value, data)) != 0) { + goto cleanup; + } + } + +cleanup: + fclose(fp); + free(section_name); + return ret; +} + +/* vim: set noet: */ diff --git a/src/common/ini.h b/src/common/ini.h new file mode 100644 index 0000000..e85a505 --- /dev/null +++ b/src/common/ini.h @@ -0,0 +1,30 @@ +/* + * ini.h + * + * Copyright (c) 2013-2015 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/>. + */ + +#ifndef _PM_INI_H +#define _PM_INI_H + +typedef int (ini_parser_fn)(const char *file, int line, const char *section, + char *key, char *value, void *data); + +int parse_ini(const char *file, ini_parser_fn cb, void *data); + +#endif /* _PM_CONF_H */ + +/* vim: set noet: */ diff --git a/src/pacman/ini.c b/src/pacman/ini.c deleted file mode 100644 index da30af1..0000000 --- a/src/pacman/ini.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * ini.c - * - * Copyright (c) 2013-2015 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 <errno.h> -#include <limits.h> -#include <string.h> /* strdup */ - -#include <alpm.h> - -#include "ini.h" -#include "util-common.h" - -/** - * @brief Parse a pacman-style INI config file. - * - * @param file path to the config file - * @param cb callback for key/value pairs - * @param data caller defined data to be passed to the callback - * - * @return the callback return value - * - * @note The callback will be called at the beginning of each section with an - * empty key and value and for each key/value pair. - * - * @note If the parser encounters an error the callback will be called with - * section, key, and value set to NULL and errno set by fopen, fgets, or - * strdup. - * - * @note The @a key and @a value passed to @ cb will be overwritten between - * calls. The section name will remain valid until after @a cb is called to - * begin a new section. - * - * @note Parsing will immediately stop if the callback returns non-zero. - */ -int parse_ini(const char *file, ini_parser_fn cb, void *data) -{ - char line[PATH_MAX], *section_name = NULL; - FILE *fp = NULL; - int linenum = 0; - int ret = 0; - - fp = fopen(file, "r"); - if(fp == NULL) { - return cb(file, 0, NULL, NULL, NULL, data); - } - - while(safe_fgets(line, PATH_MAX, fp)) { - char *key, *value, *ptr; - size_t line_len; - - linenum++; - - /* ignore whole line and end of line comments */ - if((ptr = strchr(line, '#'))) { - *ptr = '\0'; - } - - line_len = strtrim(line); - - if(line_len == 0) { - continue; - } - - if(line[0] == '[' && line[line_len - 1] == ']') { - char *name; - /* new config section, skip the '[' */ - name = strdup(line + 1); - name[line_len - 2] = '\0'; - - ret = cb(file, linenum, name, NULL, NULL, data); - free(section_name); - section_name = name; - - /* we're at a new section; perform any post-actions for the prior */ - if(ret) { - goto cleanup; - } - continue; - } - - /* directive */ - /* strsep modifies the 'line' string: 'key \0 value' */ - key = line; - value = line; - strsep(&value, "="); - strtrim(key); - strtrim(value); - - if((ret = cb(file, linenum, section_name, key, value, data)) != 0) { - goto cleanup; - } - } - -cleanup: - fclose(fp); - free(section_name); - return ret; -} - -/* vim: set noet: */ diff --git a/src/pacman/ini.c b/src/pacman/ini.c new file mode 120000 index 0000000..ba0e024 --- /dev/null +++ b/src/pacman/ini.c @@ -0,0 +1 @@ +../common/ini.c \ No newline at end of file diff --git a/src/pacman/ini.h b/src/pacman/ini.h deleted file mode 100644 index e85a505..0000000 --- a/src/pacman/ini.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * ini.h - * - * Copyright (c) 2013-2015 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/>. - */ - -#ifndef _PM_INI_H -#define _PM_INI_H - -typedef int (ini_parser_fn)(const char *file, int line, const char *section, - char *key, char *value, void *data); - -int parse_ini(const char *file, ini_parser_fn cb, void *data); - -#endif /* _PM_CONF_H */ - -/* vim: set noet: */ diff --git a/src/pacman/ini.h b/src/pacman/ini.h new file mode 120000 index 0000000..a0640e5 --- /dev/null +++ b/src/pacman/ini.h @@ -0,0 +1 @@ +../common/ini.h \ No newline at end of file -- 2.4.5
--- lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.c | 1 + lib/libalpm/alpm.h | 10 ++ lib/libalpm/handle.c | 59 ++++++++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 354 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 34 +++++ lib/libalpm/trans.c | 6 + 8 files changed, 467 insertions(+) create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h diff --git a/lib/libalpm/Makefile.am b/lib/libalpm/Makefile.am index f66daed..77e68a4 100644 --- a/lib/libalpm/Makefile.am +++ b/lib/libalpm/Makefile.am @@ -42,6 +42,8 @@ libalpm_la_SOURCES = \ graph.h graph.c \ group.h group.c \ handle.h handle.c \ + hook.h hook.c \ + ini.h ini.c \ libarchive-compat.h \ log.h log.c \ package.h package.c \ diff --git a/lib/libalpm/alpm.c b/lib/libalpm/alpm.c index 2110286..f2713d4 100644 --- a/lib/libalpm/alpm.c +++ b/lib/libalpm/alpm.c @@ -63,6 +63,7 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, if((myerr = _alpm_set_directory_option(dbpath, &(myhandle->dbpath), 1))) { goto cleanup; } + myhandle->hookdirs = alpm_list_add(NULL, strdup("/usr/share/alpm/hooks/")); lockfilelen = strlen(myhandle->dbpath) + strlen(lf) + 1; myhandle->lockfile = calloc(lockfilelen, sizeof(char)); diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 06e080b..446005a 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -87,6 +87,7 @@ typedef enum _alpm_errno_t { ALPM_ERR_TRANS_ABORT, ALPM_ERR_TRANS_TYPE, ALPM_ERR_TRANS_NOT_LOCKED, + ALPM_ERR_TRANS_HOOK_FAILED, /* Packages */ ALPM_ERR_PKG_NOT_FOUND, ALPM_ERR_PKG_IGNORED, @@ -775,6 +776,15 @@ int alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir); int alpm_option_remove_cachedir(alpm_handle_t *handle, const char *cachedir); /** @} */ +/** @name Accessors to the list of package hook directories. + * @{ + */ +alpm_list_t *alpm_option_get_hookdirs(alpm_handle_t *handle); +int alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs); +int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir); +int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); +/** @} */ + /** Returns the logfile name. */ const char *alpm_option_get_logfile(alpm_handle_t *handle); /** Sets the logfile name. */ diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index 4915d0b..5a6c298 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -82,6 +82,7 @@ void _alpm_handle_free(alpm_handle_t *handle) FREE(handle->root); FREE(handle->dbpath); FREELIST(handle->cachedirs); + FREELIST(handle->hookdirs); FREE(handle->logfile); FREE(handle->lockfile); FREE(handle->arch); @@ -206,6 +207,12 @@ const char SYMEXPORT *alpm_option_get_dbpath(alpm_handle_t *handle) return handle->dbpath; } +alpm_list_t SYMEXPORT *alpm_option_get_hookdirs(alpm_handle_t *handle) +{ + CHECK_HANDLE(handle, return NULL); + return handle->hookdirs; +} + alpm_list_t SYMEXPORT *alpm_option_get_cachedirs(alpm_handle_t *handle) { CHECK_HANDLE(handle, return NULL); @@ -380,6 +387,58 @@ alpm_errno_t _alpm_set_directory_option(const char *value, return 0; } +int SYMEXPORT alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *newhookdir; + + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_add(handle->hookdirs, newhookdir); + _alpm_log(handle, ALPM_LOG_DEBUG, "option 'hookdir' = %s\n", newhookdir); + return 0; +} + +int SYMEXPORT alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs) +{ + alpm_list_t *i; + CHECK_HANDLE(handle, return -1); + if(handle->hookdirs) { + FREELIST(handle->hookdirs); + } + for(i = hookdirs; i; i = i->next) { + int ret = alpm_option_add_hookdir(handle, i->data); + if(ret) { + return ret; + } + } + return 0; +} + +int SYMEXPORT alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *vdata = NULL; + char *newhookdir; + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_remove_str(handle->hookdirs, newhookdir, &vdata); + FREE(newhookdir); + if(vdata != NULL) { + FREE(vdata); + return 1; + } + return 0; +} + int SYMEXPORT alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir) { char *newcachedir; diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h index 5893139..171473f 100644 --- a/lib/libalpm/handle.h +++ b/lib/libalpm/handle.h @@ -82,6 +82,7 @@ struct __alpm_handle_t { char *lockfile; /* Name of the lock file */ char *gpgdir; /* Directory where GnuPG files are stored */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */ + alpm_list_t *hookdirs; /* Paths to hook directories */ /* package lists */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */ diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c new file mode 100644 index 0000000..49a76b4 --- /dev/null +++ b/lib/libalpm/hook.c @@ -0,0 +1,354 @@ +/* + * hook.c + * + * Copyright (c) 2015 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 <string.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> + +#include "alpm.h" +#include "handle.h" +#include "hook.h" +#include "ini.h" +#include "util.h" +#include "trans.h" +#include "log.h" + +enum alpm_hook_op_t { + ALPM_HOOK_OP_SYNC, + ALPM_HOOK_OP_REMOVE, +}; + +enum alpm_hook_type_t { + ALPM_HOOK_TYPE_PACKAGE, + ALPM_HOOK_TYPE_FILE, +}; + +struct alpm_trigger_t { + enum alpm_hook_op_t op; + enum alpm_hook_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, UNUSED int line, + const char *section, char *key, char *value, void *data) { + alpm_handle_t *handle = ((struct _alpm_hook_cb_ctx *) data)->handle; + struct alpm_hook_t *hook = ((struct _alpm_hook_cb_ctx *) data)->hook; + + if(!section && !key) { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error while reading file %s: %s\n"), file, strerror(errno)); + return 1; + } else if(!section) { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid option %s\n"), file, key); + return 1; + } else if(!key) { + /* beginning a new section */ + if(strcmp(section, "Trigger") == 0) { + hook->triggers = alpm_list_add(hook->triggers, + calloc(sizeof(struct alpm_trigger_t), 1)); + } else if(strcmp(section, "Action") == 0) { + /* no special processing required */ + } else { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid section %s\n"), file, section); + return 1; + } + } else if(strcmp(section, "Trigger") == 0) { + struct alpm_trigger_t *t = hook->triggers->prev->data; + if(strcmp(key, "Operation") == 0) { + if(strcmp(value, "Sync") == 0) { + t->op = ALPM_HOOK_OP_SYNC; + } else if(strcmp(value, "Remove") == 0) { + t->op = ALPM_HOOK_OP_SYNC; + } else { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid value %s\n"), file, value); + return 1; + } + } 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 { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid value %s\n"), file, value); + return 1; + } + } else if(strcmp(key, "Target") == 0) { + t->targets = alpm_list_add(t->targets, strdup(value)); + } else { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid option %s\n"), file, value); + return 1; + } + } 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 { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid value %s\n"), file, value); + return 1; + } + } else if(strcmp(key, "Depends") == 0) { + hook->depends = alpm_list_add(hook->depends, strdup(value)); + } else if(strcmp(key, "AbortOnFail") == 0) { + hook->abort_on_fail = 1; + } else if(strcmp(key, "Exec") == 0) { + hook->cmd = strdup(value); + } else { + _alpm_log(handle, ALPM_LOG_ERROR, + _("error parsing hook file %s: invalid option %s\n"), file, value); + return 1; + } + } + + return 0; +} + +static int _alpm_hook_trigger_match(alpm_handle_t *handle, struct alpm_trigger_t *t) +{ + alpm_list_t *pkgs + = t->op == ALPM_HOOK_OP_SYNC + ? handle->trans->add + : handle->trans->remove; + if(t->type == ALPM_HOOK_TYPE_PACKAGE) { + while(pkgs) { + alpm_pkg_t *pkg = pkgs->data; + if(_alpm_fnmatch_patterns(t->targets, pkg->name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + return 1; + } + pkgs = pkgs->next; + } + } else { + /* TODO: optimize this */ + while(pkgs) { + alpm_pkg_t *pkg = pkgs->data; + alpm_filelist_t f = pkg->files; + size_t i; + for(i = 0; i < f.count; i++) { + if(_alpm_fnmatch_patterns(t->targets, f.files[i].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", f.files[i].name); + return 1; + } + } + pkgs = pkgs->next; + } + } + + return 0; +} + +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 int _alpm_hook_deps_satisfied(alpm_handle_t *handle, + struct alpm_hook_t *hook) +{ + alpm_list_t *i; + alpm_list_t *pkgs = _alpm_db_get_pkgcache(handle->db_local); + for(i = hook->depends; i; i = i->next) { + if(!alpm_find_satisfier(pkgs, i->data)) { + return 0; + } + } + return 1; +} + +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) +{ + char *const argv[] = { hook->cmd, NULL }; + 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_t *hook; + struct _alpm_hook_cb_ctx ctx = { handle, NULL }; + struct stat buf; + size_t name_len = strlen(entry.d_name); + + 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; + } + + if(!(hook = calloc(sizeof(struct alpm_hook_t), 1))) { + _alpm_log(handle, ALPM_LOG_ERROR, + _("could not stat file %s: %s\n"), path, strerror(errno)); + ret = -1; + closedir(d); + goto cleanup; + } + + ctx.hook = hook; + _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(hook); + ret = -1; + continue; + } + + hook->name = strdup(entry.d_name); + hooks = alpm_list_add(hooks, 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_deps_satisfied(handle, hook) && hook->abort_on_fail) { + _alpm_log(handle, ALPM_LOG_ERROR, _("unable to run hook %s: %s\n"), + hook->name, _("could not satisfy dependencies")); + ret = -1; + continue; + } + 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: */ diff --git a/lib/libalpm/hook.h b/lib/libalpm/hook.h new file mode 100644 index 0000000..01de9c1 --- /dev/null +++ b/lib/libalpm/hook.h @@ -0,0 +1,34 @@ +/* + * hook.h + * + * Copyright (c) 2015 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/>. + */ + +#ifndef _ALPM_HOOK_H +#define _ALPM_HOOK_H + +#include "alpm.h" + +enum _alpm_hook_when_t { + ALPM_HOOK_PRE_TRANSACTION, + ALPM_HOOK_POST_TRANSACTION +}; + +int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when); + +#endif /* _ALPM_HOOK_H */ + +/* vim: set noet: */ diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index 6a26e75..0b225e5 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -40,6 +40,7 @@ #include "sync.h" #include "alpm.h" #include "deps.h" +#include "hook.h" /** \addtogroup alpm_trans Transaction Functions * @brief Functions to manipulate libalpm transactions @@ -185,6 +186,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) } } + if(_alpm_hook_run(handle, ALPM_HOOK_PRE_TRANSACTION) != 0) { + RET_ERR(handle, ALPM_ERR_TRANS_HOOK_FAILED, -1); + } + trans->state = STATE_COMMITING; alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction started\n"); @@ -211,6 +216,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction interrupted\n"); } else { alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction completed\n"); + _alpm_hook_run(handle, ALPM_HOOK_POST_TRANSACTION); } trans->state = STATE_COMMITED; -- 2.4.5
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- hooks/checkboot.hook | 17 +++++++++++++++++ hooks/checkmount | 13 +++++++++++++ hooks/checkmount.conf | 1 + hooks/checkmount.hook | 17 +++++++++++++++++ hooks/sync.hook | 16 ++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 hooks/checkboot.hook create mode 100644 hooks/checkmount create mode 100644 hooks/checkmount.conf create mode 100644 hooks/checkmount.hook create mode 100644 hooks/sync.hook diff --git a/hooks/checkboot.hook b/hooks/checkboot.hook new file mode 100644 index 0000000..d1736de --- /dev/null +++ b/hooks/checkboot.hook @@ -0,0 +1,17 @@ +# Make sure /boot is mounted before we modify it + +[Trigger] +Operation = Sync +Type = File +Target = boot/* + +[Trigger] +Operation = Remove +Type = File +Target = boot/* + +[Action] +AbortOnFail +Depends = util-linux +When = PreTransaction +Exec = /usr/lib/alpm/hooks/checkmount diff --git a/hooks/checkmount b/hooks/checkmount new file mode 100644 index 0000000..00fff7f --- /dev/null +++ b/hooks/checkmount @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +[[ -f /etc/checkmount ]] || exit 0 + +source /etc/checkmount +ret=0 +for mp in "${mountpoints[@]}"; do + if ! findmnt "$mp" &>/dev/null; then + printf "error: $mp not mounted\n" >&2 + ret=1 + fi +done +exit $ret diff --git a/hooks/checkmount.conf b/hooks/checkmount.conf new file mode 100644 index 0000000..7ac515b --- /dev/null +++ b/hooks/checkmount.conf @@ -0,0 +1 @@ +mountpoints=( /boot ) diff --git a/hooks/checkmount.hook b/hooks/checkmount.hook new file mode 100644 index 0000000..21c4127 --- /dev/null +++ b/hooks/checkmount.hook @@ -0,0 +1,17 @@ +# Make sure all our filesystems are mounted + +[Trigger] +Operation = Sync +Type = File +Target = * + +[Trigger] +Operation = Remove +Type = File +Target = * + +[Action] +AbortOnFail +Depends = util-linux +When = PreTransaction +Exec = /usr/lib/alpm/hooks/checkmount diff --git a/hooks/sync.hook b/hooks/sync.hook new file mode 100644 index 0000000..43c2869 --- /dev/null +++ b/hooks/sync.hook @@ -0,0 +1,16 @@ +# Force disks to sync to prevent data corruption + +[Trigger] +Operation = Sync +Type = Package +Target = * + +[Trigger] +Operation = Remove +Type = Package +Target = * + +[Action] +Depends = coreutils +When = PostTransaction +Exec = /usr/bin/sync -- 2.4.5
Firstly, WOO! On 04/07/15 19:59, Andrew Gregory wrote:
= TODO = * documentation * run PreTransaction hooks *after* conflict checks * hook/trigger validation * masking hooks with an empty file or symlink to /dev/null * tests
= Hook Directories =
ALPM handles keep a list of hook directories to scan. Initially, this list is populated with a single directory: /usr/share/alpm/hooks/. This directory is intended for packages to be able to place hooks in a front-end agnostic manner. Front-ends may specify any number of additional directories to search, with later directories overriding earlier ones. Front-ends can disable hooks altogether by setting an empty list.
= Parsing Hooks =
Configured directories are scanned for files matching '*.hook'. Hook files must be in pacman-style INI format with the following available options:
[Trigger] (multiple Trigger sections allowed) Operation = Sync|Remove (Required) Type = File|Package (Required) Target = <Path|PkgName> (Required, multiple allowed)
[Action] (only one Action section allowed) When = PreTransaction|PostTransaction (Required) Exec = <Command> (Required) Depends = <PkgName> (Optional) AbortOnFail (Optional, PreTransaction only)
Looks good to me. Idea: Do we want to add a field that provides suggest text to display while it is running. I assume pacman will state what hooks it is running and something better than "Running foo.hook..." would be useful.
Unlike pacman.conf multiple values are *not* allowed on a single line.
Is there a reason for this? Are they allowed for Target?
Exec is currently limited to the path of the executable to run, arguments are not allowed. This is intended to be a temporary limitation until I decide the best way to go about splitting the arguments, which depends in part on whether we want to try to provide hooks with the packages/files that triggered them.
Fair enough - updating caches is the big slowdown during Arch updates. This will cover that. But I'd really like at least the filename to be used. e.g. for info files. Is there a current usage case for package name?
Targets are matched the same as IgnorePkg in pacman.conf, meaning multiple targets can be specified and targets can be negated with a leading '!'.
Great
If multiple Trigger sections are specified the hook will run if any of them match the transaction.
= Running Hooks =
Hooks are not cached after running. Directories are rescanned and hooks reloaded each time. This is to make sure that any hooks added/removed during a transaction are appropriately accounted for.
So load hooks -> PreTransaction, do stuff, load hooks -> PostTransaction? (aside: do we currently save a list of all files changed? I have a feeling we do during conflict checking)
Hooks are run exactly the same as install scriptlets (chroot, output passed to the front-end, etc.).
They do *not* have access to which package/file triggered the hook.
For the time being, run order is officially undefined.
Fine. I'm happy to commit the first two patches now if that would help. I'll take a decent look that the second two later. WOO! Allan
On 07/05/15 at 12:00am, Allan McRae wrote:
Firstly, WOO!
On 04/07/15 19:59, Andrew Gregory wrote:
= TODO = * documentation * run PreTransaction hooks *after* conflict checks * hook/trigger validation * masking hooks with an empty file or symlink to /dev/null * tests
= Hook Directories =
ALPM handles keep a list of hook directories to scan. Initially, this list is populated with a single directory: /usr/share/alpm/hooks/. This directory is intended for packages to be able to place hooks in a front-end agnostic manner. Front-ends may specify any number of additional directories to search, with later directories overriding earlier ones. Front-ends can disable hooks altogether by setting an empty list.
= Parsing Hooks =
Configured directories are scanned for files matching '*.hook'. Hook files must be in pacman-style INI format with the following available options:
[Trigger] (multiple Trigger sections allowed) Operation = Sync|Remove (Required) Type = File|Package (Required) Target = <Path|PkgName> (Required, multiple allowed)
[Action] (only one Action section allowed) When = PreTransaction|PostTransaction (Required) Exec = <Command> (Required) Depends = <PkgName> (Optional) AbortOnFail (Optional, PreTransaction only)
Looks good to me.
Idea: Do we want to add a field that provides suggest text to display while it is running. I assume pacman will state what hooks it is running and something better than "Running foo.hook..." would be useful.
As of now, hooks are treated similarly to scriptlets; no output is shown unless the hook itself generates it. I was considering adding some sort of message, but hadn't decided on it yet. If we add one, I agree allowing hooks to specify a name/description would be useful.
Unlike pacman.conf multiple values are *not* allowed on a single line.
Is there a reason for this? Are they allowed for Target?
For simplicity and because the values can technically contain whitespace. Target does not allow multiple values on a single line.
Exec is currently limited to the path of the executable to run, arguments are not allowed. This is intended to be a temporary limitation until I decide the best way to go about splitting the arguments, which depends in part on whether we want to try to provide hooks with the packages/files that triggered them.
Fair enough - updating caches is the big slowdown during Arch updates. This will cover that.
But I'd really like at least the filename to be used. e.g. for info files. Is there a current usage case for package name?
I have no idea. Since hooks are run outside the transaction it should be safe for them to use the package name to query the database, but I don't know of a use case where they would want to. My tentative plan for providing hooks with the triggering files in the future is to allow hooks to specify that file lists should be provided to them on stdin. Having them explicitly request the file list would make it easy for us to avoid a significant amount of unnecessary overhead if they don't need it. Providing it to them on stdin would avoid the problem of potentially exceeding the maximum command line length and prevent the need for complicated substitutions in the Exec command. If that plan is agreeable to everybody, it can be implemented later without disrupting any of the existing implementation so I would like to go ahead and get an initial hook implementation merged before worrying about file lists.
Targets are matched the same as IgnorePkg in pacman.conf, meaning multiple targets can be specified and targets can be negated with a leading '!'.
Great
If multiple Trigger sections are specified the hook will run if any of them match the transaction.
= Running Hooks =
Hooks are not cached after running. Directories are rescanned and hooks reloaded each time. This is to make sure that any hooks added/removed during a transaction are appropriately accounted for.
So load hooks -> PreTransaction, do stuff, load hooks -> PostTransaction? (aside: do we currently save a list of all files changed? I have a feeling we do during conflict checking)
Correct. During file conflict checking we determine which files in a package are *new* but I think that's about it and we don't save them anywhere. Part of the problem with trying to track changes during the transaction to avoid rescanning is that multiple hook directories are allowed. We would have to scan all packages for files in all of those directories and keep track of where previously loaded hooks came from to maintain proper order.
Hooks are run exactly the same as install scriptlets (chroot, output passed to the front-end, etc.).
They do *not* have access to which package/file triggered the hook.
For the time being, run order is officially undefined.
Fine.
I'm happy to commit the first two patches now if that would help. I'll take a decent look that the second two later.
WOO! Allan
On 05/07/15 03:11, Andrew Gregory wrote:
On 07/05/15 at 12:00am, Allan McRae wrote:
Idea: Do we want to add a field that provides suggest text to display while it is running. I assume pacman will state what hooks it is running and something better than "Running foo.hook..." would be useful.
As of now, hooks are treated similarly to scriptlets; no output is shown unless the hook itself generates it. I was considering adding some sort of message, but hadn't decided on it yet. If we add one, I agree allowing hooks to specify a name/description would be useful.
I definitely think we should have one. Some of the standard things hooks will do take time, and from a pacman poin of view I would like a display message instead of looking like it is hanging.
Unlike pacman.conf multiple values are *not* allowed on a single line.
Is there a reason for this? Are they allowed for Target?
For simplicity and because the values can technically contain whitespace. Target does not allow multiple values on a single line.
OK. <snip>
My tentative plan for providing hooks with the triggering files in the future is to allow hooks to specify that file lists should be provided to them on stdin. Having them explicitly request the file list would make it easy for us to avoid a significant amount of unnecessary overhead if they don't need it. Providing it to them on stdin would avoid the problem of potentially exceeding the maximum command line length and prevent the need for complicated substitutions in the Exec command.
If that plan is agreeable to everybody, it can be implemented later without disrupting any of the existing implementation so I would like to go ahead and get an initial hook implementation merged before worrying about file lists.
That is fine to me. As far as I am concerned, handling info file database updates is the primary target. That should be readly done by passing the file list on stdin. Allan
= Changes since v1 = * file trigger matching has been improved * sync triggers take noextract into account * remove triggers will match if a package update removes a file * remove triggers will not match if the file just moves to another package * documentation started * hooks are run after file conflict and diskspace checks * hooks can be overridden or masked * hooks are validated after parsing * system hook directory is relative to the root * user hook directories can be specified in pacman.conf = TODO = * improve documentation * tests * compile-time configuration of system directory? = TODO (deferred) = * status output * run order * allow arguments in Exec * pass triggering package(s)/file(s) to hook = RFC = * Should the system directory be configurable at compile-time? Our other defaults are configurable, but perhaps this shouldn't be because we expect packages to be putting files in it. = Caveats = * See alpm-hooks.5.txt in the documentation patch for some remaining caveats regarding file matching. Aside from the possible configuration of the system hook directory, I consider this feature-complete for our initial implementation. The remaining TODOs marked as deferred are non-essential and can be added later with minimal disruption. Andrew Gregory (7): move strtim to util-common move ini parser into common check fileconflicts and diskspace outside commit wip add hooks add example hooks start hook documentation pacman: add user hook directories doc/.gitignore | 1 + doc/Makefile.am | 4 + doc/alpm-hooks.5.txt | 107 ++++++++++++ doc/pacman.conf.5.txt | 8 + hooks/checkboot.hook | 17 ++ hooks/checkmount | 13 ++ hooks/checkmount.conf | 1 + hooks/checkmount.hook | 17 ++ hooks/sync.hook | 16 ++ lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.c | 7 +- lib/libalpm/alpm.h | 10 ++ lib/libalpm/handle.c | 59 +++++++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 443 +++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 34 ++++ lib/libalpm/sync.c | 18 +- lib/libalpm/sync.h | 3 +- lib/libalpm/trans.c | 12 +- src/common/ini.c | 116 +++++++++++++ src/common/ini.h | 30 ++++ src/common/util-common.c | 39 +++++ src/common/util-common.h | 2 + src/pacman/Makefile.am | 2 + src/pacman/conf.c | 22 +++ src/pacman/conf.h | 1 + src/pacman/ini.c | 117 +------------ src/pacman/ini.h | 31 +--- src/pacman/util.c | 39 ----- src/pacman/util.h | 1 - src/util/pactree.c | 36 ---- 31 files changed, 980 insertions(+), 229 deletions(-) create mode 100644 doc/alpm-hooks.5.txt create mode 100644 hooks/checkboot.hook create mode 100644 hooks/checkmount create mode 100644 hooks/checkmount.conf create mode 100644 hooks/checkmount.hook create mode 100644 hooks/sync.hook create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h create mode 100644 src/common/ini.c create mode 100644 src/common/ini.h mode change 100644 => 120000 src/pacman/ini.c mode change 100644 => 120000 src/pacman/ini.h -- 2.5.2
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/util-common.c | 39 +++++++++++++++++++++++++++++++++++++++ src/common/util-common.h | 2 ++ src/pacman/ini.c | 2 +- src/pacman/util.c | 39 --------------------------------------- src/pacman/util.h | 1 - src/util/pactree.c | 36 ------------------------------------ 6 files changed, 42 insertions(+), 77 deletions(-) diff --git a/src/common/util-common.c b/src/common/util-common.c index e834168..542dcfd 100644 --- a/src/common/util-common.c +++ b/src/common/util-common.c @@ -17,6 +17,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <ctype.h> #include <errno.h> #include <stdlib.h> #include <string.h> @@ -127,6 +128,44 @@ char *safe_fgets(char *s, int size, FILE *stream) return ret; } +/* Trim whitespace and newlines from a string + */ +size_t strtrim(char *str) +{ + char *end, *pch = str; + + if(str == NULL || *str == '\0') { + /* string is empty, so we're done. */ + return 0; + } + + while(isspace((unsigned char)*pch)) { + pch++; + } + if(pch != str) { + size_t len = strlen(pch); + if(len) { + memmove(str, pch, len + 1); + pch = str; + } else { + *str = '\0'; + } + } + + /* check if there wasn't anything but whitespace in the string. */ + if(*str == '\0') { + return 0; + } + + end = (str + strlen(str) - 1); + while(isspace((unsigned char)*end)) { + end--; + } + *++end = '\0'; + + return end - pch; +} + #ifndef HAVE_STRNLEN /* A quick and dirty implementation derived from glibc */ /** Determines the length of a fixed-size string. diff --git a/src/common/util-common.h b/src/common/util-common.h index a2093be..af2ebda 100644 --- a/src/common/util-common.h +++ b/src/common/util-common.h @@ -30,6 +30,8 @@ int llstat(char *path, struct stat *buf); char *safe_fgets(char *s, int size, FILE *stream); +size_t strtrim(char *str); + #ifndef HAVE_STRNDUP char *strndup(const char *s, size_t n); #endif diff --git a/src/pacman/ini.c b/src/pacman/ini.c index fed0b18..da30af1 100644 --- a/src/pacman/ini.c +++ b/src/pacman/ini.c @@ -24,7 +24,7 @@ #include <alpm.h> #include "ini.h" -#include "util.h" +#include "util-common.h" /** * @brief Parse a pacman-style INI config file. diff --git a/src/pacman/util.c b/src/pacman/util.c index 3d71d8b..1542f8a 100644 --- a/src/pacman/util.c +++ b/src/pacman/util.c @@ -29,7 +29,6 @@ #include <stdint.h> /* intmax_t */ #include <string.h> #include <errno.h> -#include <ctype.h> #include <dirent.h> #include <unistd.h> #include <limits.h> @@ -353,44 +352,6 @@ void indentprint(const char *str, unsigned short indent, unsigned short cols) free(wcstr); } -/* Trim whitespace and newlines from a string - */ -size_t strtrim(char *str) -{ - char *end, *pch = str; - - if(str == NULL || *str == '\0') { - /* string is empty, so we're done. */ - return 0; - } - - while(isspace((unsigned char)*pch)) { - pch++; - } - if(pch != str) { - size_t len = strlen(pch); - if(len) { - memmove(str, pch, len + 1); - pch = str; - } else { - *str = '\0'; - } - } - - /* check if there wasn't anything but whitespace in the string. */ - if(*str == '\0') { - return 0; - } - - end = (str + strlen(str) - 1); - while(isspace((unsigned char)*end)) { - end--; - } - *++end = '\0'; - - return end - pch; -} - /* Replace all occurrences of 'needle' with 'replace' in 'str', returning * a new string (must be free'd) */ char *strreplace(const char *str, const char *needle, const char *replace) diff --git a/src/pacman/util.h b/src/pacman/util.h index c82d816..744c13c 100644 --- a/src/pacman/util.h +++ b/src/pacman/util.h @@ -54,7 +54,6 @@ unsigned short getcols(void); void columns_cache_reset(void); int rmrf(const char *path); void indentprint(const char *str, unsigned short indent, unsigned short cols); -size_t strtrim(char *str); char *strreplace(const char *str, const char *needle, const char *replace); void string_display(const char *title, const char *string, unsigned short cols); double humanize_size(off_t bytes, const char target_unit, int precision, diff --git a/src/util/pactree.c b/src/util/pactree.c index 11ad7ca..6969995 100644 --- a/src/util/pactree.c +++ b/src/util/pactree.c @@ -123,42 +123,6 @@ int searchsyncs = 0; const char *dbpath = DBPATH; const char *configfile = CONFFILE; -static size_t strtrim(char *str) -{ - char *end, *pch = str; - - if(str == NULL || *str == '\0') { - /* string is empty, so we're done. */ - return 0; - } - - while(isspace((unsigned char)*pch)) { - pch++; - } - if(pch != str) { - size_t len = strlen(pch); - if(len) { - memmove(str, pch, len + 1); - pch = str; - } else { - *str = '\0'; - } - } - - /* check if there wasn't anything but whitespace in the string. */ - if(*str == '\0') { - return 0; - } - - end = (str + strlen(str) - 1); - while(isspace((unsigned char)*end)) { - end--; - } - *++end = '\0'; - - return end - pch; -} - static int register_syncs(void) { FILE *fp; -- 2.5.2
Hi There is a typo in the commit message. [pacman-dev] [PATCH v2 1/7] move strtim to util-common vs. [pacman-dev] [PATCH v2 1/7] move strt*r*im to util-common one other thing below. On Mon, Sep 14, 2015 at 06:37:38PM -0400, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/util-common.c | 39 +++++++++++++++++++++++++++++++++++++++ src/common/util-common.h | 2 ++ src/pacman/ini.c | 2 +- src/pacman/util.c | 39 --------------------------------------- src/pacman/util.h | 1 - src/util/pactree.c | 36 ------------------------------------ 6 files changed, 42 insertions(+), 77 deletions(-)
diff --git a/src/common/util-common.c b/src/common/util-common.c index e834168..542dcfd 100644 --- a/src/common/util-common.c +++ b/src/common/util-common.c @@ -17,6 +17,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+#include <ctype.h> #include <errno.h> #include <stdlib.h> #include <string.h> @@ -127,6 +128,44 @@ char *safe_fgets(char *s, int size, FILE *stream) return ret; }
+/* Trim whitespace and newlines from a string + */ +size_t strtrim(char *str) +{ + char *end, *pch = str; + + if(str == NULL || *str == '\0') { + /* string is empty, so we're done. */ + return 0; + } + + while(isspace((unsigned char)*pch)) { + pch++; + } + if(pch != str) { + size_t len = strlen(pch); + if(len) { + memmove(str, pch, len + 1); + pch = str; + } else { + *str = '\0'; + } + }
The last part could be simplified as follows. [...] if(pch != str) { size_t len = strlen(pch); if(!len) { /* check if there wasn't anything but whitespace in the string. */ *str = '\0'; return 0; } memmove(str, pch, len + 1); pch = str; } and these three lines could then be removed...
+ if(*str == '\0') { + return 0; + } [...]
...because we already checked for (*str == '\0') in the empty string case further up in the function. Cheers, Silvan
On 19/09/15 03:17, Silvan Jegen wrote:
Hi
There is a typo in the commit message.
[pacman-dev] [PATCH v2 1/7] move strtim to util-common
vs.
[pacman-dev] [PATCH v2 1/7] move strt*r*im to util-common
one other thing below.
Thanks.
On Mon, Sep 14, 2015 at 06:37:38PM -0400, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/util-common.c | 39 +++++++++++++++++++++++++++++++++++++++ src/common/util-common.h | 2 ++ src/pacman/ini.c | 2 +- src/pacman/util.c | 39 --------------------------------------- src/pacman/util.h | 1 - src/util/pactree.c | 36 ------------------------------------ 6 files changed, 42 insertions(+), 77 deletions(-)
diff --git a/src/common/util-common.c b/src/common/util-common.c index e834168..542dcfd 100644 --- a/src/common/util-common.c +++ b/src/common/util-common.c @@ -17,6 +17,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+#include <ctype.h> #include <errno.h> #include <stdlib.h> #include <string.h> @@ -127,6 +128,44 @@ char *safe_fgets(char *s, int size, FILE *stream) return ret; }
+/* Trim whitespace and newlines from a string + */ +size_t strtrim(char *str) +{ + char *end, *pch = str; + + if(str == NULL || *str == '\0') { + /* string is empty, so we're done. */ + return 0; + } + + while(isspace((unsigned char)*pch)) { + pch++; + } + if(pch != str) { + size_t len = strlen(pch); + if(len) { + memmove(str, pch, len + 1); + pch = str; + } else { + *str = '\0'; + } + }
The last part could be simplified as follows.
[...] if(pch != str) { size_t len = strlen(pch); if(!len) { /* check if there wasn't anything but whitespace in the string. */ *str = '\0'; return 0; } memmove(str, pch, len + 1); pch = str; }
and these three lines could then be removed...
+ if(*str == '\0') { + return 0; + } [...]
...because we already checked for (*str == '\0') in the empty string case further up in the function.
This patch is a pure cut and paste. Any improvements should go in a other patches. A
Hi On Sat, Sep 19, 2015 at 03:10:50PM +1000, Allan McRae wrote:
On 19/09/15 03:17, Silvan Jegen wrote:
On Mon, Sep 14, 2015 at 06:37:38PM -0400, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> +/* Trim whitespace and newlines from a string + */ +size_t strtrim(char *str) +{ + char *end, *pch = str; + + if(str == NULL || *str == '\0') { + /* string is empty, so we're done. */ + return 0; + } + + while(isspace((unsigned char)*pch)) { + pch++; + } + if(pch != str) { + size_t len = strlen(pch); + if(len) { + memmove(str, pch, len + 1); + pch = str; + } else { + *str = '\0'; + } + }
The last part could be simplified as follows.
[...] if(pch != str) { size_t len = strlen(pch); if(!len) { /* check if there wasn't anything but whitespace in the string. */ *str = '\0'; return 0; } memmove(str, pch, len + 1); pch = str; }
and these three lines could then be removed...
+ if(*str == '\0') { + return 0; + } [...]
...because we already checked for (*str == '\0') in the empty string case further up in the function.
This patch is a pure cut and paste. Any improvements should go in a other patches.
I will send a patch after this code move has been applied then. Cheers, Silvan
On 15/09/15 08:37, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/util-common.c | 39 +++++++++++++++++++++++++++++++++++++++ src/common/util-common.h | 2 ++ src/pacman/ini.c | 2 +- src/pacman/util.c | 39 --------------------------------------- src/pacman/util.h | 1 - src/util/pactree.c | 36 ------------------------------------ 6 files changed, 42 insertions(+), 77 deletions(-)
Ack. A
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/ini.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/common/ini.h | 30 ++++++++++++++ src/pacman/ini.c | 117 +------------------------------------------------------ src/pacman/ini.h | 31 +-------------- 4 files changed, 148 insertions(+), 146 deletions(-) create mode 100644 src/common/ini.c create mode 100644 src/common/ini.h mode change 100644 => 120000 src/pacman/ini.c mode change 100644 => 120000 src/pacman/ini.h diff --git a/src/common/ini.c b/src/common/ini.c new file mode 100644 index 0000000..fed0b18 --- /dev/null +++ b/src/common/ini.c @@ -0,0 +1,116 @@ +/* + * ini.c + * + * Copyright (c) 2013-2015 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 <errno.h> +#include <limits.h> +#include <string.h> /* strdup */ + +#include <alpm.h> + +#include "ini.h" +#include "util.h" + +/** + * @brief Parse a pacman-style INI config file. + * + * @param file path to the config file + * @param cb callback for key/value pairs + * @param data caller defined data to be passed to the callback + * + * @return the callback return value + * + * @note The callback will be called at the beginning of each section with an + * empty key and value and for each key/value pair. + * + * @note If the parser encounters an error the callback will be called with + * section, key, and value set to NULL and errno set by fopen, fgets, or + * strdup. + * + * @note The @a key and @a value passed to @ cb will be overwritten between + * calls. The section name will remain valid until after @a cb is called to + * begin a new section. + * + * @note Parsing will immediately stop if the callback returns non-zero. + */ +int parse_ini(const char *file, ini_parser_fn cb, void *data) +{ + char line[PATH_MAX], *section_name = NULL; + FILE *fp = NULL; + int linenum = 0; + int ret = 0; + + fp = fopen(file, "r"); + if(fp == NULL) { + return cb(file, 0, NULL, NULL, NULL, data); + } + + while(safe_fgets(line, PATH_MAX, fp)) { + char *key, *value, *ptr; + size_t line_len; + + linenum++; + + /* ignore whole line and end of line comments */ + if((ptr = strchr(line, '#'))) { + *ptr = '\0'; + } + + line_len = strtrim(line); + + if(line_len == 0) { + continue; + } + + if(line[0] == '[' && line[line_len - 1] == ']') { + char *name; + /* new config section, skip the '[' */ + name = strdup(line + 1); + name[line_len - 2] = '\0'; + + ret = cb(file, linenum, name, NULL, NULL, data); + free(section_name); + section_name = name; + + /* we're at a new section; perform any post-actions for the prior */ + if(ret) { + goto cleanup; + } + continue; + } + + /* directive */ + /* strsep modifies the 'line' string: 'key \0 value' */ + key = line; + value = line; + strsep(&value, "="); + strtrim(key); + strtrim(value); + + if((ret = cb(file, linenum, section_name, key, value, data)) != 0) { + goto cleanup; + } + } + +cleanup: + fclose(fp); + free(section_name); + return ret; +} + +/* vim: set noet: */ diff --git a/src/common/ini.h b/src/common/ini.h new file mode 100644 index 0000000..e85a505 --- /dev/null +++ b/src/common/ini.h @@ -0,0 +1,30 @@ +/* + * ini.h + * + * Copyright (c) 2013-2015 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/>. + */ + +#ifndef _PM_INI_H +#define _PM_INI_H + +typedef int (ini_parser_fn)(const char *file, int line, const char *section, + char *key, char *value, void *data); + +int parse_ini(const char *file, ini_parser_fn cb, void *data); + +#endif /* _PM_CONF_H */ + +/* vim: set noet: */ diff --git a/src/pacman/ini.c b/src/pacman/ini.c deleted file mode 100644 index da30af1..0000000 --- a/src/pacman/ini.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * ini.c - * - * Copyright (c) 2013-2015 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 <errno.h> -#include <limits.h> -#include <string.h> /* strdup */ - -#include <alpm.h> - -#include "ini.h" -#include "util-common.h" - -/** - * @brief Parse a pacman-style INI config file. - * - * @param file path to the config file - * @param cb callback for key/value pairs - * @param data caller defined data to be passed to the callback - * - * @return the callback return value - * - * @note The callback will be called at the beginning of each section with an - * empty key and value and for each key/value pair. - * - * @note If the parser encounters an error the callback will be called with - * section, key, and value set to NULL and errno set by fopen, fgets, or - * strdup. - * - * @note The @a key and @a value passed to @ cb will be overwritten between - * calls. The section name will remain valid until after @a cb is called to - * begin a new section. - * - * @note Parsing will immediately stop if the callback returns non-zero. - */ -int parse_ini(const char *file, ini_parser_fn cb, void *data) -{ - char line[PATH_MAX], *section_name = NULL; - FILE *fp = NULL; - int linenum = 0; - int ret = 0; - - fp = fopen(file, "r"); - if(fp == NULL) { - return cb(file, 0, NULL, NULL, NULL, data); - } - - while(safe_fgets(line, PATH_MAX, fp)) { - char *key, *value, *ptr; - size_t line_len; - - linenum++; - - /* ignore whole line and end of line comments */ - if((ptr = strchr(line, '#'))) { - *ptr = '\0'; - } - - line_len = strtrim(line); - - if(line_len == 0) { - continue; - } - - if(line[0] == '[' && line[line_len - 1] == ']') { - char *name; - /* new config section, skip the '[' */ - name = strdup(line + 1); - name[line_len - 2] = '\0'; - - ret = cb(file, linenum, name, NULL, NULL, data); - free(section_name); - section_name = name; - - /* we're at a new section; perform any post-actions for the prior */ - if(ret) { - goto cleanup; - } - continue; - } - - /* directive */ - /* strsep modifies the 'line' string: 'key \0 value' */ - key = line; - value = line; - strsep(&value, "="); - strtrim(key); - strtrim(value); - - if((ret = cb(file, linenum, section_name, key, value, data)) != 0) { - goto cleanup; - } - } - -cleanup: - fclose(fp); - free(section_name); - return ret; -} - -/* vim: set noet: */ diff --git a/src/pacman/ini.c b/src/pacman/ini.c new file mode 120000 index 0000000..ba0e024 --- /dev/null +++ b/src/pacman/ini.c @@ -0,0 +1 @@ +../common/ini.c \ No newline at end of file diff --git a/src/pacman/ini.h b/src/pacman/ini.h deleted file mode 100644 index e85a505..0000000 --- a/src/pacman/ini.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * ini.h - * - * Copyright (c) 2013-2015 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/>. - */ - -#ifndef _PM_INI_H -#define _PM_INI_H - -typedef int (ini_parser_fn)(const char *file, int line, const char *section, - char *key, char *value, void *data); - -int parse_ini(const char *file, ini_parser_fn cb, void *data); - -#endif /* _PM_CONF_H */ - -/* vim: set noet: */ diff --git a/src/pacman/ini.h b/src/pacman/ini.h new file mode 120000 index 0000000..a0640e5 --- /dev/null +++ b/src/pacman/ini.h @@ -0,0 +1 @@ +../common/ini.h \ No newline at end of file -- 2.5.2
On 15/09/15 08:37, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/common/ini.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/common/ini.h | 30 ++++++++++++++ src/pacman/ini.c | 117 +------------------------------------------------------ src/pacman/ini.h | 31 +-------------- 4 files changed, 148 insertions(+), 146 deletions(-) create mode 100644 src/common/ini.c create mode 100644 src/common/ini.h mode change 100644 => 120000 src/pacman/ini.c mode change 100644 => 120000 src/pacman/ini.h
Looks good. A
This is necessary in order to be able to run PreTransaction hooks as close to the actual commit as possible so that we don't prematurely run hooks for a transaction that ultimately never happens. Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/sync.c | 18 ++++++++++++++---- lib/libalpm/sync.h | 3 ++- lib/libalpm/trans.c | 6 +++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/libalpm/sync.c b/lib/libalpm/sync.c index e843b07..c5607bc 100644 --- a/lib/libalpm/sync.c +++ b/lib/libalpm/sync.c @@ -1338,7 +1338,7 @@ int _alpm_sync_load(alpm_handle_t *handle, alpm_list_t **data) return 0; } -int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data) +int _alpm_sync_check(alpm_handle_t *handle, alpm_list_t **data) { alpm_trans_t *trans = handle->trans; alpm_event_t event; @@ -1355,7 +1355,8 @@ int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data) if(data) { *data = conflict; } else { - alpm_list_free_inner(conflict, (alpm_list_fn_free)alpm_fileconflict_free); + alpm_list_free_inner(conflict, + (alpm_list_fn_free)alpm_fileconflict_free); alpm_list_free(conflict); } RET_ERR(handle, ALPM_ERR_FILE_CONFLICTS, -1); @@ -1380,12 +1381,21 @@ int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data) EVENT(handle, &event); } + return 0; +} + +int _alpm_sync_commit(alpm_handle_t *handle) +{ + alpm_trans_t *trans = handle->trans; + /* remove conflicting and to-be-replaced packages */ if(trans->remove) { - _alpm_log(handle, ALPM_LOG_DEBUG, "removing conflicting and to-be-replaced packages\n"); + _alpm_log(handle, ALPM_LOG_DEBUG, + "removing conflicting and to-be-replaced packages\n"); /* we want the frontend to be aware of commit details */ if(_alpm_remove_packages(handle, 0) == -1) { - _alpm_log(handle, ALPM_LOG_ERROR, _("could not commit removal transaction\n")); + _alpm_log(handle, ALPM_LOG_ERROR, + _("could not commit removal transaction\n")); return -1; } } diff --git a/lib/libalpm/sync.h b/lib/libalpm/sync.h index 6281550..60ebb75 100644 --- a/lib/libalpm/sync.h +++ b/lib/libalpm/sync.h @@ -26,7 +26,8 @@ int _alpm_sync_prepare(alpm_handle_t *handle, alpm_list_t **data); int _alpm_sync_load(alpm_handle_t *handle, alpm_list_t **data); -int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data); +int _alpm_sync_check(alpm_handle_t *handle, alpm_list_t **data); +int _alpm_sync_commit(alpm_handle_t *handle); #endif /* _ALPM_SYNC_H */ diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index 6a26e75..ed073c0 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -183,6 +183,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) if(trans->flags & ALPM_TRANS_FLAG_DOWNLOADONLY) { return 0; } + if(_alpm_sync_check(handle, data) != 0) { + /* pm_errno is set by _alpm_sync_check() */ + return -1; + } } trans->state = STATE_COMMITING; @@ -198,7 +202,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) return -1; } } else { - if(_alpm_sync_commit(handle, data) == -1) { + if(_alpm_sync_commit(handle) == -1) { /* pm_errno is set by _alpm_sync_commit() */ alpm_errno_t save = handle->pm_errno; alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction failed\n"); -- 2.5.2
On 15/09/15 08:37, Andrew Gregory wrote:
This is necessary in order to be able to run PreTransaction hooks as close to the actual commit as possible so that we don't prematurely run hooks for a transaction that ultimately never happens.
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/sync.c | 18 ++++++++++++++---- lib/libalpm/sync.h | 3 ++- lib/libalpm/trans.c | 6 +++++- 3 files changed, 21 insertions(+), 6 deletions(-)
Ack. A
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- hooks/checkboot.hook | 17 +++++++++++++++++ hooks/checkmount | 13 +++++++++++++ hooks/checkmount.conf | 1 + hooks/checkmount.hook | 17 +++++++++++++++++ hooks/sync.hook | 16 ++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 hooks/checkboot.hook create mode 100644 hooks/checkmount create mode 100644 hooks/checkmount.conf create mode 100644 hooks/checkmount.hook create mode 100644 hooks/sync.hook diff --git a/hooks/checkboot.hook b/hooks/checkboot.hook new file mode 100644 index 0000000..74a2dcf --- /dev/null +++ b/hooks/checkboot.hook @@ -0,0 +1,17 @@ +# Make sure /boot is mounted before we modify it + +[Trigger] +Operation = Sync +Type = File +Target = boot/* + +[Trigger] +Operation = Remove +Type = File +Target = boot/* + +[Action] +AbortOnFail +Depends = util-linux +When = PreTransaction +Exec = /usr/share/alpm/hooks/checkmount diff --git a/hooks/checkmount b/hooks/checkmount new file mode 100644 index 0000000..00fff7f --- /dev/null +++ b/hooks/checkmount @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +[[ -f /etc/checkmount ]] || exit 0 + +source /etc/checkmount +ret=0 +for mp in "${mountpoints[@]}"; do + if ! findmnt "$mp" &>/dev/null; then + printf "error: $mp not mounted\n" >&2 + ret=1 + fi +done +exit $ret diff --git a/hooks/checkmount.conf b/hooks/checkmount.conf new file mode 100644 index 0000000..7ac515b --- /dev/null +++ b/hooks/checkmount.conf @@ -0,0 +1 @@ +mountpoints=( /boot ) diff --git a/hooks/checkmount.hook b/hooks/checkmount.hook new file mode 100644 index 0000000..7b9d9d7 --- /dev/null +++ b/hooks/checkmount.hook @@ -0,0 +1,17 @@ +# Make sure all our filesystems are mounted + +[Trigger] +Operation = Sync +Type = File +Target = * + +[Trigger] +Operation = Remove +Type = File +Target = * + +[Action] +AbortOnFail +Depends = util-linux +When = PreTransaction +Exec = /usr/share/alpm/hooks/checkmount diff --git a/hooks/sync.hook b/hooks/sync.hook new file mode 100644 index 0000000..43c2869 --- /dev/null +++ b/hooks/sync.hook @@ -0,0 +1,16 @@ +# Force disks to sync to prevent data corruption + +[Trigger] +Operation = Sync +Type = Package +Target = * + +[Trigger] +Operation = Remove +Type = Package +Target = * + +[Action] +Depends = coreutils +When = PostTransaction +Exec = /usr/bin/sync -- 2.5.2
--- doc/.gitignore | 1 + doc/Makefile.am | 4 ++ doc/alpm-hooks.5.txt | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 doc/alpm-hooks.5.txt diff --git a/doc/.gitignore b/doc/.gitignore index ad496ce..2eae9e4 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,3 +1,4 @@ +alpm-hooks.5 PKGBUILD.5 libalpm.3 makepkg.8 diff --git a/doc/Makefile.am b/doc/Makefile.am index 60a70b3..7e83dd2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -4,6 +4,7 @@ # man_MANS if --enable-asciidoc and/or --enable-doxygen are used. ASCIIDOC_MANS = \ + alpm-hooks.5 \ pacman.8 \ makepkg.8 \ makepkg-template.1 \ @@ -20,6 +21,7 @@ ASCIIDOC_MANS = \ DOXYGEN_MANS = $(wildcard man3/*.3) HTML_MANPAGES = \ + alpm-hooks.5 \ pacman.8.html \ makepkg.8.html \ makepkg-template.1.html \ @@ -46,6 +48,7 @@ HTML_DOCS = \ EXTRA_DIST = \ asciidoc.conf \ asciidoc-override.css \ + alpm-hooks.5.txt \ pacman.8.txt \ makepkg.8.txt \ makepkg-template.1.txt \ @@ -147,6 +150,7 @@ $(HTML_OTHER): asciidoc.conf Makefile.am %.3.html: ASCIIDOC_OPTS += -d manpage # Dependency rules +alpm-hooks.5 alpm-hooks.5.html: alpm-hooks.5.txt pacman.8 pacman.8.html: pacman.8.txt makepkg.8 makepkg.8.html: makepkg.8.txt makepkg-template.1 makepkg-template.1.html: makepkg-template.1.txt diff --git a/doc/alpm-hooks.5.txt b/doc/alpm-hooks.5.txt new file mode 100644 index 0000000..36e69c4 --- /dev/null +++ b/doc/alpm-hooks.5.txt @@ -0,0 +1,107 @@ +///// +vim:set ts=4 sw=4 syntax=asciidoc noet spell spelllang=en_us: +///// +alpm-hooks(5) +============= + +NAME +---- + +alpm-hooks - alpm hook file format + +SYNOPSIS +-------- + + [Trigger] (Required, Repeatable) + Operation = Sync|Remove (Required) + Type = File|Package (Required) + Target = <Path|PkgName> (Required, Repeatable) + + [Action] (Required) + When = PreTransaction|PostTransaction (Required) + Exec = <Command> (Required) + Depends = <PkgName> (Optional) + AbortOnFail (Optional, PreTransaction only) + +DESCRIPTION +----------- + +libalpm provides the ability to specify hooks to run before or after +transactions based on the packages and/or files being modified. Hooks consist +of a single "[Action]" section describing the action to be run and one or more +"[Trigger]" section describing which transactions it should be run for. + +TRIGGERS +-------- + +Hooks must contain at least one "[Trigger]" section that determines which +transactions will cause the hook to run. If multiple trigger sections are +defined the hook will run if the transaction matches *any* of the triggers. + +*Operation =* Sync|Remove:: + Select the type of operation to match targets against. + +*Type =* File|Package:: + Select whether targets are matched against transaction packages or files. + See CAVEATS for special notes regarding File triggers. + +*Target =* path|package:: + The file path or package name to match against the active transaction. + File paths refer to the files in the package archive; the installation root + should *not* be included in the path. Shell-style glob patterns are + allowed. It is possible to invert matches by prepending a file with an + exclamation mark. May be specified multiple times. Required. + +ACTIONS +------- + +*Exec =* /path/to/executable:: + Executable to run. Required. + +*When =* PreTransaction|PostTransaction:: + When to run the hook. Required. + +*Depends =* package:: + Packages that must be installed for the hook to run. May be specified + multiple times. + +*AbortOnFail*:: + Causes the transaction to be aborted if the hook exits non-zero. Only + applies to PreTransaction hooks. + +OVERRIDING HOOKS +---------------- + +Hooks may be overridden by placing a file with the same name in a higher +priority hook directory. Hooks may be disabled by overriding them with +a symlink to /dev/null. + +EXAMPLES +-------- + + # Force disks to sync to prevent data corruption + + [Trigger] + Operation = Sync + Type = Package + Target = * + + [Trigger] + Operation = Remove + Type = Package + Target = * + + [Action] + Depends = coreutils + When = PostTransaction + Exec = /usr/bin/sync + +CAVEATS +------- + +There is no way to guarantee that the trigger operation was actually performed +for file triggers. Removal triggers will match even if the file did not +actually exist on the file system when the transaction begin. Sync triggers +may be extracted as a .pacnew file, leaving the trigger path unmodified. + +include::footer.txt[] -- 2.5.2
On 15/09/15 08:42, Andrew Gregory wrote:
--- doc/.gitignore | 1 + doc/Makefile.am | 4 ++ doc/alpm-hooks.5.txt | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 doc/alpm-hooks.5.txt
libalpm-hooks vs alpm-hooks... I lean towards using libalpm-hooks. Especially as we already have libalpm(3).
diff --git a/doc/.gitignore b/doc/.gitignore index ad496ce..2eae9e4 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,3 +1,4 @@ +alpm-hooks.5 PKGBUILD.5 libalpm.3 makepkg.8 diff --git a/doc/Makefile.am b/doc/Makefile.am index 60a70b3..7e83dd2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -4,6 +4,7 @@ # man_MANS if --enable-asciidoc and/or --enable-doxygen are used.
ASCIIDOC_MANS = \ + alpm-hooks.5 \ pacman.8 \ makepkg.8 \ makepkg-template.1 \ @@ -20,6 +21,7 @@ ASCIIDOC_MANS = \ DOXYGEN_MANS = $(wildcard man3/*.3)
HTML_MANPAGES = \ + alpm-hooks.5 \ pacman.8.html \ makepkg.8.html \ makepkg-template.1.html \ @@ -46,6 +48,7 @@ HTML_DOCS = \ EXTRA_DIST = \ asciidoc.conf \ asciidoc-override.css \ + alpm-hooks.5.txt \ pacman.8.txt \ makepkg.8.txt \ makepkg-template.1.txt \ @@ -147,6 +150,7 @@ $(HTML_OTHER): asciidoc.conf Makefile.am %.3.html: ASCIIDOC_OPTS += -d manpage
# Dependency rules +alpm-hooks.5 alpm-hooks.5.html: alpm-hooks.5.txt pacman.8 pacman.8.html: pacman.8.txt makepkg.8 makepkg.8.html: makepkg.8.txt makepkg-template.1 makepkg-template.1.html: makepkg-template.1.txt diff --git a/doc/alpm-hooks.5.txt b/doc/alpm-hooks.5.txt new file mode 100644 index 0000000..36e69c4 --- /dev/null +++ b/doc/alpm-hooks.5.txt @@ -0,0 +1,107 @@ +///// +vim:set ts=4 sw=4 syntax=asciidoc noet spell spelllang=en_us: +///// +alpm-hooks(5) +============= + +NAME +---- + +alpm-hooks - alpm hook file format + +SYNOPSIS +-------- + + [Trigger] (Required, Repeatable) + Operation = Sync|Remove (Required) + Type = File|Package (Required) + Target = <Path|PkgName> (Required, Repeatable) + + [Action] (Required) + When = PreTransaction|PostTransaction (Required) + Exec = <Command> (Required) + Depends = <PkgName> (Optional) + AbortOnFail (Optional, PreTransaction only) + +DESCRIPTION +----------- + +libalpm provides the ability to specify hooks to run before or after +transactions based on the packages and/or files being modified. Hooks consist +of a single "[Action]" section describing the action to be run and one or more +"[Trigger]" section describing which transactions it should be run for. + +TRIGGERS +-------- + +Hooks must contain at least one "[Trigger]" section that determines which +transactions will cause the hook to run. If multiple trigger sections are +defined the hook will run if the transaction matches *any* of the triggers. + +*Operation =* Sync|Remove:: + Select the type of operation to match targets against. +
There should be a description of what an upgrade is.
+*Type =* File|Package:: + Select whether targets are matched against transaction packages or files. + See CAVEATS for special notes regarding File triggers. + +*Target =* path|package:: + The file path or package name to match against the active transaction. + File paths refer to the files in the package archive; the installation root + should *not* be included in the path. Shell-style glob patterns are + allowed. It is possible to invert matches by prepending a file with an + exclamation mark. May be specified multiple times. Required. + +ACTIONS +------- + +*Exec =* /path/to/executable:: + Executable to run. Required. + +*When =* PreTransaction|PostTransaction:: + When to run the hook. Required. + +*Depends =* package:: + Packages that must be installed for the hook to run. May be specified + multiple times. + +*AbortOnFail*:: + Causes the transaction to be aborted if the hook exits non-zero. Only + applies to PreTransaction hooks. + +OVERRIDING HOOKS +---------------- + +Hooks may be overridden by placing a file with the same name in a higher +priority hook directory. Hooks may be disabled by overriding them with +a symlink to /dev/null. + +EXAMPLES +-------- + + # Force disks to sync to prevent data corruption + + [Trigger] + Operation = Sync + Type = Package + Target = * + + [Trigger] + Operation = Remove + Type = Package + Target = * + + [Action] + Depends = coreutils + When = PostTransaction + Exec = /usr/bin/sync + +CAVEATS +------- + +There is no way to guarantee that the trigger operation was actually performed +for file triggers. Removal triggers will match even if the file did not +actually exist on the file system when the transaction begin.
began
Sync triggers +may be extracted as a .pacnew file, leaving the trigger path unmodified. +
Does that mean /etc/foo.pacnew will not match a Trigger /etc/foo? Or will it match despite the file not being modified? That section needs to be clearer.
+include::footer.txt[]
On 09/19/15 at 06:25pm, Allan McRae wrote:
On 15/09/15 08:42, Andrew Gregory wrote:
--- doc/.gitignore | 1 + doc/Makefile.am | 4 ++ doc/alpm-hooks.5.txt | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 doc/alpm-hooks.5.txt
libalpm-hooks vs alpm-hooks... I lean towards using libalpm-hooks. Especially as we already have libalpm(3).
<snip>
+CAVEATS +------- + +There is no way to guarantee that the trigger operation was actually performed +for file triggers. Removal triggers will match even if the file did not +actually exist on the file system when the transaction begin.
began
Sync triggers +may be extracted as a .pacnew file, leaving the trigger path unmodified. +
Does that mean /etc/foo.pacnew will not match a Trigger /etc/foo? Or will it match despite the file not being modified?
Hooks are completely unaware of backup handling so anything .pacnew will not match. What this is trying to say is that if you have a trigger for '/etc/foo', pacman may ultimately extract it as '/etc/foo.pacnew' leaving '/etc/foo' untouched but a SYNC hook for '/etc/foo' would still be triggered.
That section needs to be clearer.
+include::footer.txt[]
--- doc/pacman.conf.5.txt | 8 ++++++++ src/pacman/Makefile.am | 2 ++ src/pacman/conf.c | 22 ++++++++++++++++++++++ src/pacman/conf.h | 1 + 4 files changed, 33 insertions(+) diff --git a/doc/pacman.conf.5.txt b/doc/pacman.conf.5.txt index 383e072..b78d1fe 100644 --- a/doc/pacman.conf.5.txt +++ b/doc/pacman.conf.5.txt @@ -70,6 +70,14 @@ Options to the first cache directory with write access. *NOTE*: this is an absolute path, the root path is not automatically prepended. +*HookDir =* path/to/hook/dir:: + Add directories to search for alpm hooks. A typical default is + +{sysconfdir}/pacman.d/hooks+. Multiple directories can be specified with + hooks in later directories taking precedence over hooks in earlier + directories. *NOTE*: this is an absolute path, the root path is not + automatically prepended. For more information on the alpm hooks, see + linkman:alpm-hooks[5]. + *GPGDir =* path/to/gpg/dir:: Overrides the default location of the directory containing configuration files for GnuPG. A typical default is +{sysconfdir}/pacman.d/gnupg/+. diff --git a/src/pacman/Makefile.am b/src/pacman/Makefile.am index d3ae071..b07c670 100644 --- a/src/pacman/Makefile.am +++ b/src/pacman/Makefile.am @@ -4,6 +4,7 @@ SUBDIRS = po conffile = ${sysconfdir}/pacman.conf dbpath = ${localstatedir}/lib/pacman/ gpgdir = ${sysconfdir}/pacman.d/gnupg/ +hookdir = ${sysconfdir}/pacman.d/hooks/ cachedir = ${localstatedir}/cache/pacman/pkg/ logfile = ${localstatedir}/log/pacman.log @@ -16,6 +17,7 @@ AM_CPPFLAGS = \ -DCONFFILE=\"$(conffile)\" \ -DDBPATH=\"$(dbpath)\" \ -DGPGDIR=\"$(gpgdir)\" \ + -DHOOKDIR=\"$(hookdir)\" \ -DCACHEDIR=\"$(cachedir)\" \ -DLOGFILE=\"$(logfile)\" diff --git a/src/pacman/conf.c b/src/pacman/conf.c index 738b026..552ebd3 100644 --- a/src/pacman/conf.c +++ b/src/pacman/conf.c @@ -146,6 +146,7 @@ int config_free(config_t *oldconfig) free(oldconfig->dbpath); free(oldconfig->logfile); free(oldconfig->gpgdir); + FREELIST(oldconfig->hookdirs); FREELIST(oldconfig->cachedirs); free(oldconfig->xfercommand); free(oldconfig->print_format); @@ -515,6 +516,8 @@ static int _parse_options(const char *key, char *value, setrepeatingoption(value, "HoldPkg", &(config->holdpkg)); } else if(strcmp(key, "CacheDir") == 0) { setrepeatingoption(value, "CacheDir", &(config->cachedirs)); + } else if(strcmp(key, "HookDir") == 0) { + setrepeatingoption(value, "HookDir", &(config->hookdirs)); } else if(strcmp(key, "Architecture") == 0) { if(!config->arch) { config_set_arch(value); @@ -751,6 +754,25 @@ static int setup_libalpm(void) return ret; } + /* Set user hook directory. This is not relative to rootdir, even if + * rootdir is defined. Reasoning: hookdir contains configuration data. */ + if(config->hookdirs == NULL) { + if((ret = alpm_option_add_hookdir(handle, HOOKDIR)) != 0) { + pm_printf(ALPM_LOG_ERROR, _("problem adding hookdir '%s' (%s)\n"), + HOOKDIR, alpm_strerror(alpm_errno(handle))); + return ret; + } + } else { + /* add hook directories 1-by-1 to avoid overwriting the system directory */ + for(i = config->hookdirs; i; i = alpm_list_next(i)) { + if((ret = alpm_option_add_hookdir(handle, i->data)) != 0) { + pm_printf(ALPM_LOG_ERROR, _("problem adding hookdir '%s' (%s)\n"), + (char *) i->data, alpm_strerror(alpm_errno(handle))); + return ret; + } + } + } + /* add a default cachedir if one wasn't specified */ if(config->cachedirs == NULL) { alpm_option_add_cachedir(handle, CACHEDIR); diff --git a/src/pacman/conf.h b/src/pacman/conf.h index 3fff900..7ccc4dc 100644 --- a/src/pacman/conf.h +++ b/src/pacman/conf.h @@ -66,6 +66,7 @@ typedef struct __config_t { char *dbpath; char *logfile; char *gpgdir; + alpm_list_t *hookdirs; alpm_list_t *cachedirs; unsigned short op_q_isfile; -- 2.5.2
On 15/09/15 08:42, Andrew Gregory wrote:
--- doc/pacman.conf.5.txt | 8 ++++++++ src/pacman/Makefile.am | 2 ++ src/pacman/conf.c | 22 ++++++++++++++++++++++ src/pacman/conf.h | 1 + 4 files changed, 33 insertions(+)
Ack.
On 15/09/15 08:42, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- hooks/checkboot.hook | 17 +++++++++++++++++ hooks/checkmount | 13 +++++++++++++ hooks/checkmount.conf | 1 + hooks/checkmount.hook | 17 +++++++++++++++++
checkmount and checkboot seem a bit redundant. But patch is fine. A
hooks/sync.hook | 16 ++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 hooks/checkboot.hook create mode 100644 hooks/checkmount create mode 100644 hooks/checkmount.conf create mode 100644 hooks/checkmount.hook create mode 100644 hooks/sync.hook
diff --git a/hooks/checkboot.hook b/hooks/checkboot.hook new file mode 100644 index 0000000..74a2dcf --- /dev/null +++ b/hooks/checkboot.hook @@ -0,0 +1,17 @@ +# Make sure /boot is mounted before we modify it + +[Trigger] +Operation = Sync +Type = File +Target = boot/* + +[Trigger] +Operation = Remove +Type = File +Target = boot/* + +[Action] +AbortOnFail +Depends = util-linux +When = PreTransaction +Exec = /usr/share/alpm/hooks/checkmount diff --git a/hooks/checkmount b/hooks/checkmount new file mode 100644 index 0000000..00fff7f --- /dev/null +++ b/hooks/checkmount @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +[[ -f /etc/checkmount ]] || exit 0 + +source /etc/checkmount +ret=0 +for mp in "${mountpoints[@]}"; do + if ! findmnt "$mp" &>/dev/null; then + printf "error: $mp not mounted\n" >&2 + ret=1 + fi +done +exit $ret diff --git a/hooks/checkmount.conf b/hooks/checkmount.conf new file mode 100644 index 0000000..7ac515b --- /dev/null +++ b/hooks/checkmount.conf @@ -0,0 +1 @@ +mountpoints=( /boot ) diff --git a/hooks/checkmount.hook b/hooks/checkmount.hook new file mode 100644 index 0000000..7b9d9d7 --- /dev/null +++ b/hooks/checkmount.hook @@ -0,0 +1,17 @@ +# Make sure all our filesystems are mounted + +[Trigger] +Operation = Sync +Type = File +Target = * + +[Trigger] +Operation = Remove +Type = File +Target = * + +[Action] +AbortOnFail +Depends = util-linux +When = PreTransaction +Exec = /usr/share/alpm/hooks/checkmount diff --git a/hooks/sync.hook b/hooks/sync.hook new file mode 100644 index 0000000..43c2869 --- /dev/null +++ b/hooks/sync.hook @@ -0,0 +1,16 @@ +# Force disks to sync to prevent data corruption + +[Trigger] +Operation = Sync +Type = Package +Target = * + +[Trigger] +Operation = Remove +Type = Package +Target = * + +[Action] +Depends = coreutils +When = PostTransaction +Exec = /usr/bin/sync
--- Hopefully it will make it into the thread this time... lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.c | 7 +- lib/libalpm/alpm.h | 10 ++ lib/libalpm/handle.c | 59 +++++++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 443 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 34 ++++ lib/libalpm/trans.c | 6 + 8 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h diff --git a/lib/libalpm/Makefile.am b/lib/libalpm/Makefile.am index f66daed..77e68a4 100644 --- a/lib/libalpm/Makefile.am +++ b/lib/libalpm/Makefile.am @@ -42,6 +42,8 @@ libalpm_la_SOURCES = \ graph.h graph.c \ group.h group.c \ handle.h handle.c \ + hook.h hook.c \ + ini.h ini.c \ libarchive-compat.h \ log.h log.c \ package.h package.c \ diff --git a/lib/libalpm/alpm.c b/lib/libalpm/alpm.c index d77b43a..b3f0734 100644 --- a/lib/libalpm/alpm.c +++ b/lib/libalpm/alpm.c @@ -49,7 +49,8 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, alpm_errno_t *err) { alpm_errno_t myerr; - const char *lf = "db.lck"; + const char *lf = "db.lck", *syshookdir = "usr/share/alpm/hooks/"; + char *hookdir; size_t lockfilelen; alpm_handle_t *myhandle = _alpm_handle_new(); @@ -64,6 +65,10 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, goto cleanup; } + MALLOC(hookdir, strlen(myhandle->root) + strlen(syshookdir) + 1, goto cleanup); + sprintf(hookdir, "%s%s", myhandle->root, syshookdir); + myhandle->hookdirs = alpm_list_add(NULL, hookdir); + /* set default database extension */ STRDUP(myhandle->dbext, ".db", goto cleanup); diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 594f0b6..3049f2f 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -87,6 +87,7 @@ typedef enum _alpm_errno_t { ALPM_ERR_TRANS_ABORT, ALPM_ERR_TRANS_TYPE, ALPM_ERR_TRANS_NOT_LOCKED, + ALPM_ERR_TRANS_HOOK_FAILED, /* Packages */ ALPM_ERR_PKG_NOT_FOUND, ALPM_ERR_PKG_IGNORED, @@ -775,6 +776,15 @@ int alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir); int alpm_option_remove_cachedir(alpm_handle_t *handle, const char *cachedir); /** @} */ +/** @name Accessors to the list of package hook directories. + * @{ + */ +alpm_list_t *alpm_option_get_hookdirs(alpm_handle_t *handle); +int alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs); +int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir); +int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); +/** @} */ + /** Returns the logfile name. */ const char *alpm_option_get_logfile(alpm_handle_t *handle); /** Sets the logfile name. */ diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index a12ac50..98420b0 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -83,6 +83,7 @@ void _alpm_handle_free(alpm_handle_t *handle) FREE(handle->dbpath); FREE(handle->dbext); FREELIST(handle->cachedirs); + FREELIST(handle->hookdirs); FREE(handle->logfile); FREE(handle->lockfile); FREE(handle->arch); @@ -207,6 +208,12 @@ const char SYMEXPORT *alpm_option_get_dbpath(alpm_handle_t *handle) return handle->dbpath; } +alpm_list_t SYMEXPORT *alpm_option_get_hookdirs(alpm_handle_t *handle) +{ + CHECK_HANDLE(handle, return NULL); + return handle->hookdirs; +} + alpm_list_t SYMEXPORT *alpm_option_get_cachedirs(alpm_handle_t *handle) { CHECK_HANDLE(handle, return NULL); @@ -387,6 +394,58 @@ alpm_errno_t _alpm_set_directory_option(const char *value, return 0; } +int SYMEXPORT alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *newhookdir; + + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_add(handle->hookdirs, newhookdir); + _alpm_log(handle, ALPM_LOG_DEBUG, "option 'hookdir' = %s\n", newhookdir); + return 0; +} + +int SYMEXPORT alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs) +{ + alpm_list_t *i; + CHECK_HANDLE(handle, return -1); + if(handle->hookdirs) { + FREELIST(handle->hookdirs); + } + for(i = hookdirs; i; i = i->next) { + int ret = alpm_option_add_hookdir(handle, i->data); + if(ret) { + return ret; + } + } + return 0; +} + +int SYMEXPORT alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *vdata = NULL; + char *newhookdir; + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_remove_str(handle->hookdirs, newhookdir, &vdata); + FREE(newhookdir); + if(vdata != NULL) { + FREE(vdata); + return 1; + } + return 0; +} + int SYMEXPORT alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir) { char *newcachedir; diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h index 315d987..e252fbf 100644 --- a/lib/libalpm/handle.h +++ b/lib/libalpm/handle.h @@ -82,6 +82,7 @@ struct __alpm_handle_t { char *lockfile; /* Name of the lock file */ char *gpgdir; /* Directory where GnuPG files are stored */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */ + alpm_list_t *hookdirs; /* Paths to hook directories */ /* package lists */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */ diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c new file mode 100644 index 0000000..66cedaa --- /dev/null +++ b/lib/libalpm/hook.c @@ -0,0 +1,443 @@ +/* + * hook.c + * + * Copyright (c) 2015 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 <string.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> + +#include "alpm.h" +#include "handle.h" +#include "hook.h" +#include "ini.h" +#include "util.h" +#include "trans.h" +#include "log.h" + +enum alpm_hook_op_t { + ALPM_HOOK_OP_SYNC = 1, + ALPM_HOOK_OP_REMOVE, +}; + +enum alpm_hook_type_t { + ALPM_HOOK_TYPE_PACKAGE = 1, + ALPM_HOOK_TYPE_FILE, +}; + +struct alpm_trigger_t { + enum alpm_hook_op_t op; + enum alpm_hook_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_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: empty trigger section */ + 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 int _alpm_hook_parse_cb(const char *file, UNUSED 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 file %s: %s\n"), file, strerror(errno)); + } else if(!section) { + error(_("error parsing hook file %s: invalid option %s\n"), file, 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(_("error parsing hook file %s: invalid section %s\n"), file, section); + } + } else if(strcmp(section, "Trigger") == 0) { + struct alpm_trigger_t *t = hook->triggers->prev->data; + if(strcmp(key, "Operation") == 0) { + if(strcmp(value, "Sync") == 0) { + t->op = ALPM_HOOK_OP_SYNC; + } else if(strcmp(value, "Remove") == 0) { + t->op = ALPM_HOOK_OP_REMOVE; + } else { + error(_("error parsing hook file %s: invalid value %s\n"), file, 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(_("error parsing hook file %s: invalid value %s\n"), file, value); + } + } else if(strcmp(key, "Target") == 0) { + char *val; + STRDUP(val, value, return 1); + t->targets = alpm_list_add(t->targets, val); + } else { + error(_("error parsing hook file %s: invalid option %s\n"), file, 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(_("error parsing hook file %s: invalid value %s\n"), file, 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(_("error parsing hook file %s: invalid option %s\n"), file, 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; + alpm_db_t *localdb = handle->db_local; + + for(i = handle->trans->add; i; i = i->next) { + alpm_pkg_t *pkg = i->data; + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(alpm_option_match_noextract(handle, filelist.files[j].name) == 0) { + continue; + } + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return t->op == ALPM_HOOK_OP_SYNC ? 1 : 0; + } + } + } + + if(t->op == ALPM_HOOK_OP_REMOVE) { + for(i = handle->trans->add; i; i = i->next) { + alpm_pkg_t *spkg = i->data; + alpm_pkg_t *pkg = alpm_db_get_pkg(localdb, spkg->name); + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return 1; + } + } + } + for(i = handle->trans->remove; i; i = i->next) { + alpm_pkg_t *pkg = i->data; + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return 1; + } + } + } + } + + return 0; +} + +static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct alpm_trigger_t *t) +{ + 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) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + return t->op == ALPM_HOOK_OP_SYNC ? 1 : 0; + } + } + + if(t->op == ALPM_HOOK_OP_REMOVE) { + 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) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + 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); + + 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_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); + } + + 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: */ diff --git a/lib/libalpm/hook.h b/lib/libalpm/hook.h new file mode 100644 index 0000000..4894a19 --- /dev/null +++ b/lib/libalpm/hook.h @@ -0,0 +1,34 @@ +/* + * hook.h + * + * Copyright (c) 2015 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/>. + */ + +#ifndef _ALPM_HOOK_H +#define _ALPM_HOOK_H + +#include "alpm.h" + +enum _alpm_hook_when_t { + ALPM_HOOK_PRE_TRANSACTION = 1, + ALPM_HOOK_POST_TRANSACTION +}; + +int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when); + +#endif /* _ALPM_HOOK_H */ + +/* vim: set noet: */ diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index ed073c0..a6b1aef 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -40,6 +40,7 @@ #include "sync.h" #include "alpm.h" #include "deps.h" +#include "hook.h" /** \addtogroup alpm_trans Transaction Functions * @brief Functions to manipulate libalpm transactions @@ -189,6 +190,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) } } + if(_alpm_hook_run(handle, ALPM_HOOK_PRE_TRANSACTION) != 0) { + RET_ERR(handle, ALPM_ERR_TRANS_HOOK_FAILED, -1); + } + trans->state = STATE_COMMITING; alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction started\n"); @@ -215,6 +220,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction interrupted\n"); } else { alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction completed\n"); + _alpm_hook_run(handle, ALPM_HOOK_POST_TRANSACTION); } trans->state = STATE_COMMITED; -- 2.5.2
On 15/09/15 08:46, Andrew Gregory wrote:
---
Hopefully it will make it into the thread this time...
lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.c | 7 +- lib/libalpm/alpm.h | 10 ++ lib/libalpm/handle.c | 59 +++++++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 443 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 34 ++++ lib/libalpm/trans.c | 6 + 8 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h
Looks good overall. There are some minor comments below. I could not understand the various loops for hook matching. Can you give me an explanation?
diff --git a/lib/libalpm/Makefile.am b/lib/libalpm/Makefile.am index f66daed..77e68a4 100644 --- a/lib/libalpm/Makefile.am +++ b/lib/libalpm/Makefile.am @@ -42,6 +42,8 @@ libalpm_la_SOURCES = \ graph.h graph.c \ group.h group.c \ handle.h handle.c \ + hook.h hook.c \ + ini.h ini.c \ libarchive-compat.h \ log.h log.c \ package.h package.c \ diff --git a/lib/libalpm/alpm.c b/lib/libalpm/alpm.c index d77b43a..b3f0734 100644 --- a/lib/libalpm/alpm.c +++ b/lib/libalpm/alpm.c @@ -49,7 +49,8 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, alpm_errno_t *err) { alpm_errno_t myerr; - const char *lf = "db.lck"; + const char *lf = "db.lck", *syshookdir = "usr/share/alpm/hooks/";
This needs to be a define. Look at how $(conffile) is set in configure and then -DCONFFILE in pacman/Makefile.am. Use libalpm instead of just alpm. $datarootdir/libalpm/hooks.
+ char *hookdir; size_t lockfilelen; alpm_handle_t *myhandle = _alpm_handle_new();
@@ -64,6 +65,10 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, goto cleanup; }
+ MALLOC(hookdir, strlen(myhandle->root) + strlen(syshookdir) + 1, goto cleanup); + sprintf(hookdir, "%s%s", myhandle->root, syshookdir); + myhandle->hookdirs = alpm_list_add(NULL, hookdir); + /* set default database extension */ STRDUP(myhandle->dbext, ".db", goto cleanup);
OK.
diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 594f0b6..3049f2f 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -87,6 +87,7 @@ typedef enum _alpm_errno_t { ALPM_ERR_TRANS_ABORT, ALPM_ERR_TRANS_TYPE, ALPM_ERR_TRANS_NOT_LOCKED, + ALPM_ERR_TRANS_HOOK_FAILED, /* Packages */ ALPM_ERR_PKG_NOT_FOUND, ALPM_ERR_PKG_IGNORED, @@ -775,6 +776,15 @@ int alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir); int alpm_option_remove_cachedir(alpm_handle_t *handle, const char *cachedir); /** @} */
+/** @name Accessors to the list of package hook directories. + * @{ + */ +alpm_list_t *alpm_option_get_hookdirs(alpm_handle_t *handle); +int alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs); +int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir); +int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); +/** @} */ + /** Returns the logfile name. */ const char *alpm_option_get_logfile(alpm_handle_t *handle); /** Sets the logfile name. */
OK.
diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index a12ac50..98420b0 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -83,6 +83,7 @@ void _alpm_handle_free(alpm_handle_t *handle) FREE(handle->dbpath); FREE(handle->dbext); FREELIST(handle->cachedirs); + FREELIST(handle->hookdirs); FREE(handle->logfile); FREE(handle->lockfile); FREE(handle->arch); @@ -207,6 +208,12 @@ const char SYMEXPORT *alpm_option_get_dbpath(alpm_handle_t *handle) return handle->dbpath; }
+alpm_list_t SYMEXPORT *alpm_option_get_hookdirs(alpm_handle_t *handle) +{ + CHECK_HANDLE(handle, return NULL); + return handle->hookdirs; +} + alpm_list_t SYMEXPORT *alpm_option_get_cachedirs(alpm_handle_t *handle) { CHECK_HANDLE(handle, return NULL); @@ -387,6 +394,58 @@ alpm_errno_t _alpm_set_directory_option(const char *value, return 0; }
+int SYMEXPORT alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *newhookdir; + + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_add(handle->hookdirs, newhookdir); + _alpm_log(handle, ALPM_LOG_DEBUG, "option 'hookdir' = %s\n", newhookdir); + return 0; +} + +int SYMEXPORT alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs) +{ + alpm_list_t *i; + CHECK_HANDLE(handle, return -1); + if(handle->hookdirs) { + FREELIST(handle->hookdirs); + } + for(i = hookdirs; i; i = i->next) { + int ret = alpm_option_add_hookdir(handle, i->data); + if(ret) { + return ret; + } + } + return 0; +} + +int SYMEXPORT alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *vdata = NULL; + char *newhookdir; + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_remove_str(handle->hookdirs, newhookdir, &vdata); + FREE(newhookdir); + if(vdata != NULL) { + FREE(vdata); + return 1; + } + return 0; +} + int SYMEXPORT alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir) { char *newcachedir;
OK.
diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h index 315d987..e252fbf 100644 --- a/lib/libalpm/handle.h +++ b/lib/libalpm/handle.h @@ -82,6 +82,7 @@ struct __alpm_handle_t { char *lockfile; /* Name of the lock file */ char *gpgdir; /* Directory where GnuPG files are stored */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */ + alpm_list_t *hookdirs; /* Paths to hook directories */
/* package lists */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */
OK.
diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c new file mode 100644 index 0000000..66cedaa --- /dev/null +++ b/lib/libalpm/hook.c @@ -0,0 +1,443 @@ +/* + * hook.c + * + * Copyright (c) 2015 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 <string.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> + +#include "alpm.h" +#include "handle.h" +#include "hook.h" +#include "ini.h" +#include "util.h" +#include "trans.h" +#include "log.h" + +enum alpm_hook_op_t { + ALPM_HOOK_OP_SYNC = 1, + ALPM_HOOK_OP_REMOVE, +}; + +enum alpm_hook_type_t { + ALPM_HOOK_TYPE_PACKAGE = 1, + ALPM_HOOK_TYPE_FILE, +}; + +struct alpm_trigger_t { + enum alpm_hook_op_t op; + enum alpm_hook_type_t type; + alpm_list_t *targets; +}; +
OK.
+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; +};
OK.
+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); + } +} +
OK
+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); + } +}
OK
+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; +} +
OK
+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: empty trigger section */ + return 0; + } +
Why do we need this?
+ 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; +} +
OK
+static int _alpm_hook_parse_cb(const char *file, UNUSED 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 file %s: %s\n"), file, strerror(errno)); + } else if(!section) { + error(_("error parsing hook file %s: invalid option %s\n"), file, 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(_("error parsing hook file %s: invalid section %s\n"), file, section); + } + } else if(strcmp(section, "Trigger") == 0) { + struct alpm_trigger_t *t = hook->triggers->prev->data; + if(strcmp(key, "Operation") == 0) { + if(strcmp(value, "Sync") == 0) { + t->op = ALPM_HOOK_OP_SYNC; + } else if(strcmp(value, "Remove") == 0) { + t->op = ALPM_HOOK_OP_REMOVE; + } else { + error(_("error parsing hook file %s: invalid value %s\n"), file, 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(_("error parsing hook file %s: invalid value %s\n"), file, value); + } + } else if(strcmp(key, "Target") == 0) { + char *val; + STRDUP(val, value, return 1); + t->targets = alpm_list_add(t->targets, val); + } else { + error(_("error parsing hook file %s: invalid option %s\n"), file, 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(_("error parsing hook file %s: invalid value %s\n"), file, 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(_("error parsing hook file %s: invalid option %s\n"), file, value); + } + } + +#undef error + + return 0; +} +
OK
+static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, struct alpm_trigger_t *t) +{ + alpm_list_t *i; + alpm_db_t *localdb = handle->db_local; + + for(i = handle->trans->add; i; i = i->next) { + alpm_pkg_t *pkg = i->data; + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(alpm_option_match_noextract(handle, filelist.files[j].name) == 0) { + continue; + }
Nest the above within the if() below
+ if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return t->op == ALPM_HOOK_OP_SYNC ? 1 : 0; + } + } + } + + if(t->op == ALPM_HOOK_OP_REMOVE) { + for(i = handle->trans->add; i; i = i->next) {
trans->remove?
+ alpm_pkg_t *spkg = i->data; + alpm_pkg_t *pkg = alpm_db_get_pkg(localdb, spkg->name); + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return 1; + } + } + } + for(i = handle->trans->remove; i; i = i->next) { + alpm_pkg_t *pkg = i->data; + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return 1; + } + } + } + } + + return 0; +} +
I don't understand what is going on here... First for loop is over trans->add and then does ALPM_HOOK_O_SYNC ? 1 : 0. What is that doing? What are the two loops for ALPM_HOOK_OP_REMOVE? Comments to explain this would be helpful.
+static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct alpm_trigger_t *t) +{ + 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) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + return t->op == ALPM_HOOK_OP_SYNC ? 1 : 0; + } + } + + if(t->op == ALPM_HOOK_OP_REMOVE) { + 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) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + 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); +} +
OK.
+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; +} +
OK
+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; +}
OK.
+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); +}
OK
+ +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; + } + } +
OK
+ 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); + + 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; + } +
OK
+ if(find_hook(hooks, entry.d_name)) { + _alpm_log(handle, ALPM_LOG_DEBUG, "skipping overridden hook %s\n", path); + continue; + } +
OK
+ 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); + }
OK.
+ + 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); + } +
OK.
+ 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; + } + } + } +
OK.
+cleanup: + alpm_list_free_inner(hooks, (alpm_list_fn_free) _alpm_hook_free); + alpm_list_free(hooks); + + return ret; +} + +/* vim: set noet: */ diff --git a/lib/libalpm/hook.h b/lib/libalpm/hook.h new file mode 100644 index 0000000..4894a19 --- /dev/null +++ b/lib/libalpm/hook.h @@ -0,0 +1,34 @@ +/* + * hook.h + * + * Copyright (c) 2015 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/>. + */ + +#ifndef _ALPM_HOOK_H +#define _ALPM_HOOK_H + +#include "alpm.h" + +enum _alpm_hook_when_t { + ALPM_HOOK_PRE_TRANSACTION = 1, + ALPM_HOOK_POST_TRANSACTION +}; + +int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when); + +#endif /* _ALPM_HOOK_H */ + +/* vim: set noet: */
OK
diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index ed073c0..a6b1aef 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -40,6 +40,7 @@ #include "sync.h" #include "alpm.h" #include "deps.h" +#include "hook.h"
/** \addtogroup alpm_trans Transaction Functions * @brief Functions to manipulate libalpm transactions @@ -189,6 +190,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) } }
+ if(_alpm_hook_run(handle, ALPM_HOOK_PRE_TRANSACTION) != 0) { + RET_ERR(handle, ALPM_ERR_TRANS_HOOK_FAILED, -1); + } + trans->state = STATE_COMMITING;
alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction started\n"); @@ -215,6 +220,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction interrupted\n"); } else { alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction completed\n"); + _alpm_hook_run(handle, ALPM_HOOK_POST_TRANSACTION); }
trans->state = STATE_COMMITED;
OK
On 09/19/15 at 05:55pm, Allan McRae wrote:
On 15/09/15 08:46, Andrew Gregory wrote:
---
Hopefully it will make it into the thread this time...
lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.c | 7 +- lib/libalpm/alpm.h | 10 ++ lib/libalpm/handle.c | 59 +++++++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 443 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 34 ++++ lib/libalpm/trans.c | 6 + 8 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h
Looks good overall. There are some minor comments below.
I could not understand the various loops for hook matching. Can you give me an explanation?
diff --git a/lib/libalpm/Makefile.am b/lib/libalpm/Makefile.am index f66daed..77e68a4 100644 --- a/lib/libalpm/Makefile.am +++ b/lib/libalpm/Makefile.am @@ -42,6 +42,8 @@ libalpm_la_SOURCES = \ graph.h graph.c \ group.h group.c \ handle.h handle.c \ + hook.h hook.c \ + ini.h ini.c \ libarchive-compat.h \ log.h log.c \ package.h package.c \ diff --git a/lib/libalpm/alpm.c b/lib/libalpm/alpm.c index d77b43a..b3f0734 100644 --- a/lib/libalpm/alpm.c +++ b/lib/libalpm/alpm.c @@ -49,7 +49,8 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, alpm_errno_t *err) { alpm_errno_t myerr; - const char *lf = "db.lck"; + const char *lf = "db.lck", *syshookdir = "usr/share/alpm/hooks/";
This needs to be a define. Look at how $(conffile) is set in configure and then -DCONFFILE in pacman/Makefile.am. Use libalpm instead of just alpm. $datarootdir/libalpm/hooks.
+ char *hookdir; size_t lockfilelen; alpm_handle_t *myhandle = _alpm_handle_new();
@@ -64,6 +65,10 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, goto cleanup; }
+ MALLOC(hookdir, strlen(myhandle->root) + strlen(syshookdir) + 1, goto cleanup); + sprintf(hookdir, "%s%s", myhandle->root, syshookdir); + myhandle->hookdirs = alpm_list_add(NULL, hookdir); + /* set default database extension */ STRDUP(myhandle->dbext, ".db", goto cleanup);
OK.
diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 594f0b6..3049f2f 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -87,6 +87,7 @@ typedef enum _alpm_errno_t { ALPM_ERR_TRANS_ABORT, ALPM_ERR_TRANS_TYPE, ALPM_ERR_TRANS_NOT_LOCKED, + ALPM_ERR_TRANS_HOOK_FAILED, /* Packages */ ALPM_ERR_PKG_NOT_FOUND, ALPM_ERR_PKG_IGNORED, @@ -775,6 +776,15 @@ int alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir); int alpm_option_remove_cachedir(alpm_handle_t *handle, const char *cachedir); /** @} */
+/** @name Accessors to the list of package hook directories. + * @{ + */ +alpm_list_t *alpm_option_get_hookdirs(alpm_handle_t *handle); +int alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs); +int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir); +int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); +/** @} */ + /** Returns the logfile name. */ const char *alpm_option_get_logfile(alpm_handle_t *handle); /** Sets the logfile name. */
OK.
diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index a12ac50..98420b0 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -83,6 +83,7 @@ void _alpm_handle_free(alpm_handle_t *handle) FREE(handle->dbpath); FREE(handle->dbext); FREELIST(handle->cachedirs); + FREELIST(handle->hookdirs); FREE(handle->logfile); FREE(handle->lockfile); FREE(handle->arch); @@ -207,6 +208,12 @@ const char SYMEXPORT *alpm_option_get_dbpath(alpm_handle_t *handle) return handle->dbpath; }
+alpm_list_t SYMEXPORT *alpm_option_get_hookdirs(alpm_handle_t *handle) +{ + CHECK_HANDLE(handle, return NULL); + return handle->hookdirs; +} + alpm_list_t SYMEXPORT *alpm_option_get_cachedirs(alpm_handle_t *handle) { CHECK_HANDLE(handle, return NULL); @@ -387,6 +394,58 @@ alpm_errno_t _alpm_set_directory_option(const char *value, return 0; }
+int SYMEXPORT alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *newhookdir; + + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_add(handle->hookdirs, newhookdir); + _alpm_log(handle, ALPM_LOG_DEBUG, "option 'hookdir' = %s\n", newhookdir); + return 0; +} + +int SYMEXPORT alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs) +{ + alpm_list_t *i; + CHECK_HANDLE(handle, return -1); + if(handle->hookdirs) { + FREELIST(handle->hookdirs); + } + for(i = hookdirs; i; i = i->next) { + int ret = alpm_option_add_hookdir(handle, i->data); + if(ret) { + return ret; + } + } + return 0; +} + +int SYMEXPORT alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *vdata = NULL; + char *newhookdir; + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_remove_str(handle->hookdirs, newhookdir, &vdata); + FREE(newhookdir); + if(vdata != NULL) { + FREE(vdata); + return 1; + } + return 0; +} + int SYMEXPORT alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir) { char *newcachedir;
OK.
diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h index 315d987..e252fbf 100644 --- a/lib/libalpm/handle.h +++ b/lib/libalpm/handle.h @@ -82,6 +82,7 @@ struct __alpm_handle_t { char *lockfile; /* Name of the lock file */ char *gpgdir; /* Directory where GnuPG files are stored */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */ + alpm_list_t *hookdirs; /* Paths to hook directories */
/* package lists */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */
OK.
diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c new file mode 100644 index 0000000..66cedaa --- /dev/null +++ b/lib/libalpm/hook.c @@ -0,0 +1,443 @@ +/* + * hook.c + * + * Copyright (c) 2015 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 <string.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> + +#include "alpm.h" +#include "handle.h" +#include "hook.h" +#include "ini.h" +#include "util.h" +#include "trans.h" +#include "log.h" + +enum alpm_hook_op_t { + ALPM_HOOK_OP_SYNC = 1, + ALPM_HOOK_OP_REMOVE, +}; + +enum alpm_hook_type_t { + ALPM_HOOK_TYPE_PACKAGE = 1, + ALPM_HOOK_TYPE_FILE, +}; + +struct alpm_trigger_t { + enum alpm_hook_op_t op; + enum alpm_hook_type_t type; + alpm_list_t *targets; +}; +
OK.
+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; +};
OK.
+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); + } +} +
OK
+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); + } +}
OK
+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; +} +
OK
+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: empty trigger section */ + return 0; + } +
Why do we need this?
This is to allow masking. Despite what the documentation says, at the moment hooks can be disabled by a similarly named file without any trigger sections, not just a symlink to /dev/null. This may change before the final version.
+ 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; +} +
OK
+static int _alpm_hook_parse_cb(const char *file, UNUSED 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 file %s: %s\n"), file, strerror(errno)); + } else if(!section) { + error(_("error parsing hook file %s: invalid option %s\n"), file, 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(_("error parsing hook file %s: invalid section %s\n"), file, section); + } + } else if(strcmp(section, "Trigger") == 0) { + struct alpm_trigger_t *t = hook->triggers->prev->data; + if(strcmp(key, "Operation") == 0) { + if(strcmp(value, "Sync") == 0) { + t->op = ALPM_HOOK_OP_SYNC; + } else if(strcmp(value, "Remove") == 0) { + t->op = ALPM_HOOK_OP_REMOVE; + } else { + error(_("error parsing hook file %s: invalid value %s\n"), file, 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(_("error parsing hook file %s: invalid value %s\n"), file, value); + } + } else if(strcmp(key, "Target") == 0) { + char *val; + STRDUP(val, value, return 1); + t->targets = alpm_list_add(t->targets, val); + } else { + error(_("error parsing hook file %s: invalid option %s\n"), file, 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(_("error parsing hook file %s: invalid value %s\n"), file, 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(_("error parsing hook file %s: invalid option %s\n"), file, value); + } + } + +#undef error + + return 0; +} +
OK
+static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, struct alpm_trigger_t *t) +{ + alpm_list_t *i; + alpm_db_t *localdb = handle->db_local; + + for(i = handle->trans->add; i; i = i->next) { + alpm_pkg_t *pkg = i->data; + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(alpm_option_match_noextract(handle, filelist.files[j].name) == 0) { + continue; + }
Nest the above within the if() below
+ if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return t->op == ALPM_HOOK_OP_SYNC ? 1 : 0;
If it's a SYNC hook then we have a match, if it's a REMOVE hook then we know the file won't actually be removed even if one of the subsequent loops would have matched.
+ } + } + } + + if(t->op == ALPM_HOOK_OP_REMOVE) { + for(i = handle->trans->add; i; i = i->next) {
trans->remove?
This is looking for files that are being removed because an upgraded package no longer contains them.
+ alpm_pkg_t *spkg = i->data; + alpm_pkg_t *pkg = alpm_db_get_pkg(localdb, spkg->name); + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return 1; + } + } + } + for(i = handle->trans->remove; i; i = i->next) {
This is looking for files removed by a package removal.
+ alpm_pkg_t *pkg = i->data; + alpm_filelist_t filelist = pkg->files; + size_t j; + for(j = 0; j < filelist.count; j++) { + if(_alpm_fnmatch_patterns(t->targets, filelist.files[j].name) == 0) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched file %s\n", + filelist.files[j].name); + return 1; + } + } + } + } + + return 0; +} +
I don't understand what is going on here...
First for loop is over trans->add and then does ALPM_HOOK_O_SYNC ? 1 : 0. What is that doing?
What are the two loops for ALPM_HOOK_OP_REMOVE?
Comments to explain this would be helpful.
See above.
+static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct alpm_trigger_t *t) +{ + 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) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + return t->op == ALPM_HOOK_OP_SYNC ? 1 : 0; + } + } + + if(t->op == ALPM_HOOK_OP_REMOVE) { + 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) { + _alpm_log(handle, ALPM_LOG_DEBUG, "matched package %s\n", pkg->name); + 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); +} +
OK.
+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; +} +
OK
+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; +}
OK.
+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); +}
OK
+ +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; + } + } +
OK
+ 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); + + 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; + } +
OK
+ if(find_hook(hooks, entry.d_name)) { + _alpm_log(handle, ALPM_LOG_DEBUG, "skipping overridden hook %s\n", path); + continue; + } +
OK
+ 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); + }
OK.
+ + 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); + } +
OK.
+ 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; + } + } + } +
OK.
+cleanup: + alpm_list_free_inner(hooks, (alpm_list_fn_free) _alpm_hook_free); + alpm_list_free(hooks); + + return ret; +} + +/* vim: set noet: */ diff --git a/lib/libalpm/hook.h b/lib/libalpm/hook.h new file mode 100644 index 0000000..4894a19 --- /dev/null +++ b/lib/libalpm/hook.h @@ -0,0 +1,34 @@ +/* + * hook.h + * + * Copyright (c) 2015 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/>. + */ + +#ifndef _ALPM_HOOK_H +#define _ALPM_HOOK_H + +#include "alpm.h" + +enum _alpm_hook_when_t { + ALPM_HOOK_PRE_TRANSACTION = 1, + ALPM_HOOK_POST_TRANSACTION +}; + +int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when); + +#endif /* _ALPM_HOOK_H */ + +/* vim: set noet: */
OK
diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index ed073c0..a6b1aef 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -40,6 +40,7 @@ #include "sync.h" #include "alpm.h" #include "deps.h" +#include "hook.h"
/** \addtogroup alpm_trans Transaction Functions * @brief Functions to manipulate libalpm transactions @@ -189,6 +190,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) } }
+ if(_alpm_hook_run(handle, ALPM_HOOK_PRE_TRANSACTION) != 0) { + RET_ERR(handle, ALPM_ERR_TRANS_HOOK_FAILED, -1); + } + trans->state = STATE_COMMITING;
alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction started\n"); @@ -215,6 +220,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction interrupted\n"); } else { alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction completed\n"); + _alpm_hook_run(handle, ALPM_HOOK_POST_TRANSACTION); }
trans->state = STATE_COMMITED;
OK
On 15/09/15 08:37, Andrew Gregory wrote:
= Changes since v1 = * file trigger matching has been improved * sync triggers take noextract into account * remove triggers will match if a package update removes a file * remove triggers will not match if the file just moves to another package * documentation started * hooks are run after file conflict and diskspace checks * hooks can be overridden or masked * hooks are validated after parsing * system hook directory is relative to the root * user hook directories can be specified in pacman.conf
= TODO = * improve documentation * tests * compile-time configuration of system directory?
= TODO (deferred) = * status output * run order * allow arguments in Exec * pass triggering package(s)/file(s) to hook
Ack - these are all later things.
= RFC = * Should the system directory be configurable at compile-time? Our other defaults are configurable, but perhaps this shouldn't be because we expect packages to be putting files in it.
I assume the standard sysconfdir/libdir/datarootdir cover this enough. We don't have specific compile time configuration options for anything else pacman related. (apart from --with-makepkg-template-dir which really should just use $datarootdir/makepkg-template - added to my TODO...).
= Caveats = * See alpm-hooks.5.txt in the documentation patch for some remaining caveats regarding file matching.
Aside from the possible configuration of the system hook directory, I consider this feature-complete for our initial implementation. The remaining TODOs marked as deferred are non-essential and can be added later with minimal disruption.
I have pulled patches 1 to 3 as they are all pre-work. I'll take a look at the others now. A
= Changes since v2 = * separate INSTALL/UPGRADE trigger types * triggers can contain multiple operation fields * allow Exec fields to contain arguments (as well as a few shell features courtesy of wordexp(3)) * tests * make system hook directory relative to datarootdir * improved documentation * removed example hooks, they can still be found at: https://github.com/andrewgregory/libalpm-hooks = TODO (deferred) = * status output * run order * pass triggering package(s)/file(s) to hook = Trigger Matching = The new install/upgrade/remove filtering to support separate install/upgrade triggers requires building complete lists of all files being installed and removed and comparing them to each other to determine the actual final disposition of all files. Given the new additional complexity, I have removed some of the optimizations from the previous version. I never benchmarked the previous version, so I don't know how much of a performance hit this was, but in the tests I ran this is still fast enough that I'm not inclined to try to add them back just yet. With a transaction that included 682 packages and ~150,000 files, a hook with a single '*' file trigger matched its targets and ran in ~0.15s. Andrew Gregory (11): handle: add hookdirs option add hook data types and parser run hooks during trans_commit validate hooks after parsing pacman: add user hook directories pactest: use pacman --hookdir option util.py: return the created path pactest: add hook/script support add hook tests add alpm-hooks man page allow arguments in hook Exec fields doc/.gitignore | 1 + doc/Makefile.am | 4 + doc/alpm-hooks.5.txt | 117 +++++ doc/pacman.conf.5.txt | 8 + lib/libalpm/Makefile.am | 3 + lib/libalpm/alpm.c | 5 + lib/libalpm/alpm.h | 10 + lib/libalpm/error.c | 2 + lib/libalpm/handle.c | 59 +++ lib/libalpm/handle.h | 1 + lib/libalpm/hook.c | 504 +++++++++++++++++++++ lib/libalpm/hook.h | 34 ++ lib/libalpm/ini.c | 1 + lib/libalpm/ini.h | 1 + lib/libalpm/trans.c | 6 + src/pacman/Makefile.am | 2 + src/pacman/conf.c | 22 + src/pacman/conf.h | 2 + src/pacman/pacman.c | 10 + test/pacman/pmfile.py | 27 +- test/pacman/pmpkg.py | 3 +- test/pacman/pmtest.py | 30 +- test/pacman/tests/TESTS | 10 + test/pacman/tests/hook-abortonfail.py | 25 + test/pacman/tests/hook-exec-with-arguments.py | 22 + test/pacman/tests/hook-file-change-packages.py | 32 ++ .../pacman/tests/hook-file-remove-trigger-match.py | 24 + test/pacman/tests/hook-file-upgrade-nomatch.py | 27 ++ test/pacman/tests/hook-invalid-trigger.py | 25 + .../pacman/tests/hook-pkg-install-trigger-match.py | 23 + test/pacman/tests/hook-pkg-remove-trigger-match.py | 23 + .../pacman/tests/hook-pkg-upgrade-trigger-match.py | 26 ++ test/pacman/tests/hook-upgrade-trigger-no-match.py | 23 + test/pacman/util.py | 5 +- 34 files changed, 1107 insertions(+), 10 deletions(-) create mode 100644 doc/alpm-hooks.5.txt create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h create mode 120000 lib/libalpm/ini.c create mode 120000 lib/libalpm/ini.h create mode 100644 test/pacman/tests/hook-abortonfail.py create mode 100644 test/pacman/tests/hook-exec-with-arguments.py create mode 100644 test/pacman/tests/hook-file-change-packages.py create mode 100644 test/pacman/tests/hook-file-remove-trigger-match.py create mode 100644 test/pacman/tests/hook-file-upgrade-nomatch.py create mode 100644 test/pacman/tests/hook-invalid-trigger.py create mode 100644 test/pacman/tests/hook-pkg-install-trigger-match.py create mode 100644 test/pacman/tests/hook-pkg-remove-trigger-match.py create mode 100644 test/pacman/tests/hook-pkg-upgrade-trigger-match.py create mode 100644 test/pacman/tests/hook-upgrade-trigger-no-match.py -- 2.6.1
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/Makefile.am | 1 + lib/libalpm/alpm.c | 5 +++++ lib/libalpm/alpm.h | 9 ++++++++ lib/libalpm/handle.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/handle.h | 1 + 5 files changed, 75 insertions(+) diff --git a/lib/libalpm/Makefile.am b/lib/libalpm/Makefile.am index f66daed..1668da1 100644 --- a/lib/libalpm/Makefile.am +++ b/lib/libalpm/Makefile.am @@ -7,6 +7,7 @@ include_HEADERS = alpm_list.h alpm.h AM_CPPFLAGS = \ -imacros $(top_builddir)/config.h \ + -DSYSHOOKDIR=\"@datarootdir@/libalpm/hooks/\" \ -DLOCALEDIR=\"@localedir@\" AM_CFLAGS = -pedantic -D_GNU_SOURCE $(WARNING_CFLAGS) diff --git a/lib/libalpm/alpm.c b/lib/libalpm/alpm.c index d77b43a..0798c0b 100644 --- a/lib/libalpm/alpm.c +++ b/lib/libalpm/alpm.c @@ -50,6 +50,7 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, { alpm_errno_t myerr; const char *lf = "db.lck"; + char *hookdir; size_t lockfilelen; alpm_handle_t *myhandle = _alpm_handle_new(); @@ -64,6 +65,10 @@ alpm_handle_t SYMEXPORT *alpm_initialize(const char *root, const char *dbpath, goto cleanup; } + MALLOC(hookdir, strlen(myhandle->root) + strlen(SYSHOOKDIR) + 1, goto cleanup); + sprintf(hookdir, "%s%s", myhandle->root, SYSHOOKDIR); + myhandle->hookdirs = alpm_list_add(NULL, hookdir); + /* set default database extension */ STRDUP(myhandle->dbext, ".db", goto cleanup); diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 594f0b6..f224927 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -775,6 +775,15 @@ int alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir); int alpm_option_remove_cachedir(alpm_handle_t *handle, const char *cachedir); /** @} */ +/** @name Accessors to the list of package hook directories. + * @{ + */ +alpm_list_t *alpm_option_get_hookdirs(alpm_handle_t *handle); +int alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs); +int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir); +int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); +/** @} */ + /** Returns the logfile name. */ const char *alpm_option_get_logfile(alpm_handle_t *handle); /** Sets the logfile name. */ diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index a12ac50..98420b0 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -83,6 +83,7 @@ void _alpm_handle_free(alpm_handle_t *handle) FREE(handle->dbpath); FREE(handle->dbext); FREELIST(handle->cachedirs); + FREELIST(handle->hookdirs); FREE(handle->logfile); FREE(handle->lockfile); FREE(handle->arch); @@ -207,6 +208,12 @@ const char SYMEXPORT *alpm_option_get_dbpath(alpm_handle_t *handle) return handle->dbpath; } +alpm_list_t SYMEXPORT *alpm_option_get_hookdirs(alpm_handle_t *handle) +{ + CHECK_HANDLE(handle, return NULL); + return handle->hookdirs; +} + alpm_list_t SYMEXPORT *alpm_option_get_cachedirs(alpm_handle_t *handle) { CHECK_HANDLE(handle, return NULL); @@ -387,6 +394,58 @@ alpm_errno_t _alpm_set_directory_option(const char *value, return 0; } +int SYMEXPORT alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *newhookdir; + + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_add(handle->hookdirs, newhookdir); + _alpm_log(handle, ALPM_LOG_DEBUG, "option 'hookdir' = %s\n", newhookdir); + return 0; +} + +int SYMEXPORT alpm_option_set_hookdirs(alpm_handle_t *handle, alpm_list_t *hookdirs) +{ + alpm_list_t *i; + CHECK_HANDLE(handle, return -1); + if(handle->hookdirs) { + FREELIST(handle->hookdirs); + } + for(i = hookdirs; i; i = i->next) { + int ret = alpm_option_add_hookdir(handle, i->data); + if(ret) { + return ret; + } + } + return 0; +} + +int SYMEXPORT alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir) +{ + char *vdata = NULL; + char *newhookdir; + CHECK_HANDLE(handle, return -1); + ASSERT(hookdir != NULL, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, -1)); + + newhookdir = canonicalize_path(hookdir); + if(!newhookdir) { + RET_ERR(handle, ALPM_ERR_MEMORY, -1); + } + handle->hookdirs = alpm_list_remove_str(handle->hookdirs, newhookdir, &vdata); + FREE(newhookdir); + if(vdata != NULL) { + FREE(vdata); + return 1; + } + return 0; +} + int SYMEXPORT alpm_option_add_cachedir(alpm_handle_t *handle, const char *cachedir) { char *newcachedir; diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h index 315d987..e252fbf 100644 --- a/lib/libalpm/handle.h +++ b/lib/libalpm/handle.h @@ -82,6 +82,7 @@ struct __alpm_handle_t { char *lockfile; /* Name of the lock file */ char *gpgdir; /* Directory where GnuPG files are stored */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */ + alpm_list_t *hookdirs; /* Paths to hook directories */ /* package lists */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */ -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/Makefile.am | 1 + lib/libalpm/alpm.c | 5 +++++ lib/libalpm/alpm.h | 9 ++++++++ lib/libalpm/handle.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/handle.h | 1 + 5 files changed, 75 insertions(+)
OK.
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/hook.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 32 +++++++++++ 2 files changed, 188 insertions(+) create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c new file mode 100644 index 0000000..c4f232b --- /dev/null +++ b/lib/libalpm/hook.c @@ -0,0 +1,156 @@ +/* + * hook.c + * + * Copyright (c) 2015 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 <errno.h> + +#include "handle.h" +#include "hook.h" +#include "log.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; +} + +/* vim: set noet: */ diff --git a/lib/libalpm/hook.h b/lib/libalpm/hook.h new file mode 100644 index 0000000..0692176 --- /dev/null +++ b/lib/libalpm/hook.h @@ -0,0 +1,32 @@ +/* + * hook.h + * + * Copyright (c) 2015 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/>. + */ + +#ifndef _ALPM_HOOK_H +#define _ALPM_HOOK_H + +#include "alpm.h" + +enum _alpm_hook_when_t { + ALPM_HOOK_PRE_TRANSACTION = 1, + ALPM_HOOK_POST_TRANSACTION +}; + +#endif /* _ALPM_HOOK_H */ + +/* vim: set noet: */ -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/hook.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 32 +++++++++++ 2 files changed, 188 insertions(+) create mode 100644 lib/libalpm/hook.c create mode 100644 lib/libalpm/hook.h
OK.
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.h | 1 + lib/libalpm/error.c | 2 + lib/libalpm/hook.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 2 + lib/libalpm/ini.c | 1 + lib/libalpm/ini.h | 1 + lib/libalpm/trans.c | 6 ++ 8 files changed, 294 insertions(+) create mode 120000 lib/libalpm/ini.c create mode 120000 lib/libalpm/ini.h diff --git a/lib/libalpm/Makefile.am b/lib/libalpm/Makefile.am index 1668da1..4272ae7 100644 --- a/lib/libalpm/Makefile.am +++ b/lib/libalpm/Makefile.am @@ -43,6 +43,8 @@ libalpm_la_SOURCES = \ graph.h graph.c \ group.h group.c \ handle.h handle.c \ + hook.h hook.c \ + ini.h ini.c \ libarchive-compat.h \ log.h log.c \ package.h package.c \ diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index f224927..3049f2f 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -87,6 +87,7 @@ typedef enum _alpm_errno_t { ALPM_ERR_TRANS_ABORT, ALPM_ERR_TRANS_TYPE, ALPM_ERR_TRANS_NOT_LOCKED, + ALPM_ERR_TRANS_HOOK_FAILED, /* Packages */ ALPM_ERR_PKG_NOT_FOUND, ALPM_ERR_PKG_IGNORED, diff --git a/lib/libalpm/error.c b/lib/libalpm/error.c index 76b710c..0a12cde 100644 --- a/lib/libalpm/error.c +++ b/lib/libalpm/error.c @@ -100,6 +100,8 @@ const char SYMEXPORT *alpm_strerror(alpm_errno_t err) return _("operation not compatible with the transaction type"); case ALPM_ERR_TRANS_NOT_LOCKED: return _("transaction commit attempt when database is not locked"); + case ALPM_ERR_TRANS_HOOK_FAILED: + return _("failed to run transaction hooks"); /* Packages */ case ALPM_ERR_PKG_NOT_FOUND: return _("could not find or read package"); diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c index c4f232b..7c50787 100644 --- a/lib/libalpm/hook.c +++ b/lib/libalpm/hook.c @@ -17,11 +17,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <dirent.h> #include <errno.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 { @@ -153,4 +157,279 @@ static int _alpm_hook_parse_cb(const char *file, int line, 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: */ diff --git a/lib/libalpm/hook.h b/lib/libalpm/hook.h index 0692176..4894a19 100644 --- a/lib/libalpm/hook.h +++ b/lib/libalpm/hook.h @@ -27,6 +27,8 @@ enum _alpm_hook_when_t { ALPM_HOOK_POST_TRANSACTION }; +int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when); + #endif /* _ALPM_HOOK_H */ /* vim: set noet: */ diff --git a/lib/libalpm/ini.c b/lib/libalpm/ini.c new file mode 120000 index 0000000..b16d2f2 --- /dev/null +++ b/lib/libalpm/ini.c @@ -0,0 +1 @@ +../../src/common/ini.c \ No newline at end of file diff --git a/lib/libalpm/ini.h b/lib/libalpm/ini.h new file mode 120000 index 0000000..e395fa0 --- /dev/null +++ b/lib/libalpm/ini.h @@ -0,0 +1 @@ +../../src/common/ini.h \ No newline at end of file diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index ed073c0..a6b1aef 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -40,6 +40,7 @@ #include "sync.h" #include "alpm.h" #include "deps.h" +#include "hook.h" /** \addtogroup alpm_trans Transaction Functions * @brief Functions to manipulate libalpm transactions @@ -189,6 +190,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) } } + if(_alpm_hook_run(handle, ALPM_HOOK_PRE_TRANSACTION) != 0) { + RET_ERR(handle, ALPM_ERR_TRANS_HOOK_FAILED, -1); + } + trans->state = STATE_COMMITING; alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction started\n"); @@ -215,6 +220,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, alpm_list_t **data) alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction interrupted\n"); } else { alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction completed\n"); + _alpm_hook_run(handle, ALPM_HOOK_POST_TRANSACTION); } trans->state = STATE_COMMITED; -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/Makefile.am | 2 + lib/libalpm/alpm.h | 1 + lib/libalpm/error.c | 2 + lib/libalpm/hook.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/libalpm/hook.h | 2 + lib/libalpm/ini.c | 1 + lib/libalpm/ini.h | 1 + lib/libalpm/trans.c | 6 ++ 8 files changed, 294 insertions(+) create mode 120000 lib/libalpm/ini.c create mode 120000 lib/libalpm/ini.h
OK.
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/hook.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c index 7c50787..fe4b204 100644 --- a/lib/libalpm/hook.c +++ b/lib/libalpm/hook.c @@ -79,6 +79,68 @@ static void _alpm_hook_free(struct _alpm_hook_t *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 int _alpm_hook_parse_cb(const char *file, int line, const char *section, char *key, char *value, void *data) { @@ -395,7 +457,8 @@ int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when) 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) { + 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; -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- lib/libalpm/hook.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-)
OK.
--- doc/pacman.conf.5.txt | 8 ++++++++ src/pacman/Makefile.am | 2 ++ src/pacman/conf.c | 22 ++++++++++++++++++++++ src/pacman/conf.h | 2 ++ src/pacman/pacman.c | 10 ++++++++++ 5 files changed, 44 insertions(+) diff --git a/doc/pacman.conf.5.txt b/doc/pacman.conf.5.txt index 383e072..b78d1fe 100644 --- a/doc/pacman.conf.5.txt +++ b/doc/pacman.conf.5.txt @@ -70,6 +70,14 @@ Options to the first cache directory with write access. *NOTE*: this is an absolute path, the root path is not automatically prepended. +*HookDir =* path/to/hook/dir:: + Add directories to search for alpm hooks. A typical default is + +{sysconfdir}/pacman.d/hooks+. Multiple directories can be specified with + hooks in later directories taking precedence over hooks in earlier + directories. *NOTE*: this is an absolute path, the root path is not + automatically prepended. For more information on the alpm hooks, see + linkman:alpm-hooks[5]. + *GPGDir =* path/to/gpg/dir:: Overrides the default location of the directory containing configuration files for GnuPG. A typical default is +{sysconfdir}/pacman.d/gnupg/+. diff --git a/src/pacman/Makefile.am b/src/pacman/Makefile.am index d3ae071..b07c670 100644 --- a/src/pacman/Makefile.am +++ b/src/pacman/Makefile.am @@ -4,6 +4,7 @@ SUBDIRS = po conffile = ${sysconfdir}/pacman.conf dbpath = ${localstatedir}/lib/pacman/ gpgdir = ${sysconfdir}/pacman.d/gnupg/ +hookdir = ${sysconfdir}/pacman.d/hooks/ cachedir = ${localstatedir}/cache/pacman/pkg/ logfile = ${localstatedir}/log/pacman.log @@ -16,6 +17,7 @@ AM_CPPFLAGS = \ -DCONFFILE=\"$(conffile)\" \ -DDBPATH=\"$(dbpath)\" \ -DGPGDIR=\"$(gpgdir)\" \ + -DHOOKDIR=\"$(hookdir)\" \ -DCACHEDIR=\"$(cachedir)\" \ -DLOGFILE=\"$(logfile)\" diff --git a/src/pacman/conf.c b/src/pacman/conf.c index 738b026..6a2b206 100644 --- a/src/pacman/conf.c +++ b/src/pacman/conf.c @@ -146,6 +146,7 @@ int config_free(config_t *oldconfig) free(oldconfig->dbpath); free(oldconfig->logfile); free(oldconfig->gpgdir); + FREELIST(oldconfig->hookdirs); FREELIST(oldconfig->cachedirs); free(oldconfig->xfercommand); free(oldconfig->print_format); @@ -515,6 +516,8 @@ static int _parse_options(const char *key, char *value, setrepeatingoption(value, "HoldPkg", &(config->holdpkg)); } else if(strcmp(key, "CacheDir") == 0) { setrepeatingoption(value, "CacheDir", &(config->cachedirs)); + } else if(strcmp(key, "HookDir") == 0) { + setrepeatingoption(value, "HookDir", &(config->hookdirs)); } else if(strcmp(key, "Architecture") == 0) { if(!config->arch) { config_set_arch(value); @@ -751,6 +754,25 @@ static int setup_libalpm(void) return ret; } + /* Set user hook directory. This is not relative to rootdir, even if + * rootdir is defined. Reasoning: hookdir contains configuration data. */ + if(config->hookdirs == NULL) { + if((ret = alpm_option_add_hookdir(handle, HOOKDIR)) != 0) { + pm_printf(ALPM_LOG_ERROR, _("problem adding hookdir '%s' (%s)\n"), + HOOKDIR, alpm_strerror(alpm_errno(handle))); + return ret; + } + } else { + /* add hook directories 1-by-1 to avoid overwriting the system directory */ + for(i = config->hookdirs; i; i = alpm_list_next(i)) { + if((ret = alpm_option_add_hookdir(handle, i->data)) != 0) { + pm_printf(ALPM_LOG_ERROR, _("problem adding hookdir '%s' (%s)\n"), + (char *) i->data, alpm_strerror(alpm_errno(handle))); + return ret; + } + } + } + /* add a default cachedir if one wasn't specified */ if(config->cachedirs == NULL) { alpm_option_add_cachedir(handle, CACHEDIR); diff --git a/src/pacman/conf.h b/src/pacman/conf.h index 3fff900..cde6741 100644 --- a/src/pacman/conf.h +++ b/src/pacman/conf.h @@ -66,6 +66,7 @@ typedef struct __config_t { char *dbpath; char *logfile; char *gpgdir; + alpm_list_t *hookdirs; alpm_list_t *cachedirs; unsigned short op_q_isfile; @@ -156,6 +157,7 @@ enum { OP_NOSCRIPTLET, OP_ASK, OP_CACHEDIR, + OP_HOOKDIR, OP_ASDEPS, OP_LOGFILE, OP_IGNOREGROUP, diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c index c680067..d777663 100644 --- a/src/pacman/pacman.c +++ b/src/pacman/pacman.c @@ -210,6 +210,7 @@ static void usage(int op, const char * const myname) addlist(_(" -v, --verbose be verbose\n")); addlist(_(" --arch <arch> set an alternate architecture\n")); addlist(_(" --cachedir <dir> set an alternate package cache location\n")); + addlist(_(" --hookdir <dir> set an alternate hook location\n")); addlist(_(" --color <when> colorize the output\n")); addlist(_(" --config <path> set an alternate configuration file\n")); addlist(_(" --debug display debug messages\n")); @@ -463,6 +464,9 @@ static int parsearg_global(int opt) free(config->gpgdir); config->gpgdir = strdup(optarg); break; + case OP_HOOKDIR: + config->hookdirs = alpm_list_add(config->hookdirs, strdup(optarg)); + break; case OP_LOGFILE: free(config->logfile); config->logfile = strndup(optarg, PATH_MAX); @@ -959,6 +963,7 @@ static int parseargs(int argc, char *argv[]) {"noscriptlet", no_argument, 0, OP_NOSCRIPTLET}, {"ask", required_argument, 0, OP_ASK}, {"cachedir", required_argument, 0, OP_CACHEDIR}, + {"hookdir", required_argument, 0, OP_HOOKDIR}, {"asdeps", no_argument, 0, OP_ASDEPS}, {"logfile", required_argument, 0, OP_LOGFILE}, {"ignoregroup", required_argument, 0, OP_IGNOREGROUP}, @@ -1274,6 +1279,11 @@ int main(int argc, char *argv[]) printf("%s ", (const char *)j->data); } printf("\n"); + printf("Hook Dirs : "); + for(j = alpm_option_get_hookdirs(config->handle); j; j = alpm_list_next(j)) { + printf("%s ", (const char *)j->data); + } + printf("\n"); printf("Lock File : %s\n", alpm_option_get_lockfile(config->handle)); printf("Log File : %s\n", alpm_option_get_logfile(config->handle)); printf("GPG Dir : %s\n", alpm_option_get_gpgdir(config->handle)); -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
--- doc/pacman.conf.5.txt | 8 ++++++++ src/pacman/Makefile.am | 2 ++ src/pacman/conf.c | 22 ++++++++++++++++++++++ src/pacman/conf.h | 2 ++ src/pacman/pacman.c | 10 ++++++++++ 5 files changed, 44 insertions(+)
OK.
--- test/pacman/pmtest.py | 4 ++++ test/pacman/util.py | 1 + 2 files changed, 5 insertions(+) diff --git a/test/pacman/pmtest.py b/test/pacman/pmtest.py index c7bda9c..d48c03f 100644 --- a/test/pacman/pmtest.py +++ b/test/pacman/pmtest.py @@ -43,6 +43,7 @@ def __init__(self, name, root): "--config", self.configfile(), "--root", self.rootdir(), "--dbpath", self.dbdir(), + "--hookdir", self.hookdir(), "--cachedir", self.cachedir()] def __str__(self): @@ -306,4 +307,7 @@ def rootdir(self): def cachedir(self): return os.path.join(self.root, util.PM_CACHEDIR) + def hookdir(self): + return os.path.join(self.root, util.PM_HOOKDIR) + # vim: set ts=4 sw=4 et: diff --git a/test/pacman/util.py b/test/pacman/util.py index 2e643cc..aa9c63f 100644 --- a/test/pacman/util.py +++ b/test/pacman/util.py @@ -30,6 +30,7 @@ PM_LOCK = "var/lib/pacman/db.lck" PM_CACHEDIR = "var/cache/pacman/pkg" PM_EXT_PKG = ".pkg.tar.gz" +PM_HOOKDIR = "etc/pacman.d/hooks" # Pacman PACCONF = "etc/pacman.conf" -- 2.6.1
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- test/pacman/pmpkg.py | 3 +-- test/pacman/pmtest.py | 3 +-- test/pacman/util.py | 4 +++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/pacman/pmpkg.py b/test/pacman/pmpkg.py index 3133661..9033dc6 100644 --- a/test/pacman/pmpkg.py +++ b/test/pacman/pmpkg.py @@ -174,8 +174,7 @@ def makepkg(self, path): def install_package(self, root): """Install the package in the given root.""" for f in self.files: - util.mkfile(root, f, f) - path = os.path.join(root, f) + path = util.mkfile(root, f, f) if os.path.isfile(path): os.utime(path, (355, 355)) diff --git a/test/pacman/pmtest.py b/test/pacman/pmtest.py index d48c03f..7774a7b 100644 --- a/test/pacman/pmtest.py +++ b/test/pacman/pmtest.py @@ -180,8 +180,7 @@ def generate(self, pacman): vprint(" Populating file system") for f in self.filesystem: vprint("\t%s" % f) - util.mkfile(self.root, f, f) - path = os.path.join(self.root, f) + path = util.mkfile(self.root, f, f) if os.path.isfile(path): os.utime(path, (355, 355)) for pkg in self.db["local"].pkgs: diff --git a/test/pacman/util.py b/test/pacman/util.py index aa9c63f..e2acc52 100644 --- a/test/pacman/util.py +++ b/test/pacman/util.py @@ -84,7 +84,7 @@ def mkfile(base, name, data=""): if info["isdir"]: if not os.path.isdir(path): os.makedirs(path, 0o755) - return + return path dir_path = os.path.dirname(path) if dir_path and not os.path.isdir(dir_path): @@ -98,6 +98,8 @@ def mkfile(base, name, data=""): if info["perms"]: os.chmod(path, info["perms"]) + return path + def writedata(filename, data): if isinstance(data, list): data = "\n".join(data) -- 2.6.1
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- test/pacman/pmfile.py | 27 ++++++++++++++++++++++++++- test/pacman/pmtest.py | 25 ++++++++++++++++++++----- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/test/pacman/pmfile.py b/test/pacman/pmfile.py index 7f96f6c..417550d 100644 --- a/test/pacman/pmfile.py +++ b/test/pacman/pmfile.py @@ -20,7 +20,32 @@ import util -class PacmanFile(object): +class pmfile(object): + def __init__(self, path, content, mode=0o644): + self.path = path + self.content = content + self.mode = mode + + def mkfile(self, root): + path = os.path.join(root, self.path) + + dir_path = os.path.dirname(path) + if dir_path and not os.path.isdir(dir_path): + os.makedirs(dir_path, 0o755) + + fd = open(path, "w") + if self.content: + fd.write(self.content) + if self.content[-1] != "\n": + fd.write("\n") + fd.close() + + os.chmod(path, self.mode) + + return path + + +class snapshot(object): """File object """ diff --git a/test/pacman/pmtest.py b/test/pacman/pmtest.py index 7774a7b..a1081cc 100644 --- a/test/pacman/pmtest.py +++ b/test/pacman/pmtest.py @@ -179,10 +179,14 @@ def generate(self, pacman): # Filesystem vprint(" Populating file system") for f in self.filesystem: - vprint("\t%s" % f) - path = util.mkfile(self.root, f, f) - if os.path.isfile(path): - os.utime(path, (355, 355)) + if type(f) is pmfile.pmfile: + vprint("\t%s" % f.path) + f.mkfile(self.root); + else: + vprint("\t%s" % f) + path = util.mkfile(self.root, f, f) + if os.path.isfile(path): + os.utime(path, (355, 355)) for pkg in self.db["local"].pkgs: vprint("\tinstalling %s" % pkg.fullname()) pkg.install_package(self.root) @@ -193,10 +197,21 @@ def generate(self, pacman): # Done. vprint(" Taking a snapshot of the file system") for filename in self.snapshots_needed(): - f = pmfile.PacmanFile(self.root, filename) + f = pmfile.snapshot(self.root, filename) self.files.append(f) vprint("\t%s" % f.name) + def add_hook(self, name, content): + if not name.endswith(".hook"): + name = name + ".hook" + path = os.path.join("etc/pacman.d/hooks/", name) + self.filesystem.append(pmfile.pmfile(path, content)) + + def add_script(self, name, content): + if not content.startswith("#!"): + content = "#!/bin/sh\n" + content + path = os.path.join("bin/", name) + self.filesystem.append(pmfile.pmfile(path, content, mode=0o755)) def snapshots_needed(self): files = set() -- 2.6.1
--- test/pacman/tests/TESTS | 9 ++++++ test/pacman/tests/hook-abortonfail.py | 25 +++++++++++++++++ test/pacman/tests/hook-file-change-packages.py | 32 ++++++++++++++++++++++ .../pacman/tests/hook-file-remove-trigger-match.py | 24 ++++++++++++++++ test/pacman/tests/hook-file-upgrade-nomatch.py | 27 ++++++++++++++++++ test/pacman/tests/hook-invalid-trigger.py | 25 +++++++++++++++++ .../pacman/tests/hook-pkg-install-trigger-match.py | 23 ++++++++++++++++ test/pacman/tests/hook-pkg-remove-trigger-match.py | 23 ++++++++++++++++ .../pacman/tests/hook-pkg-upgrade-trigger-match.py | 26 ++++++++++++++++++ test/pacman/tests/hook-upgrade-trigger-no-match.py | 23 ++++++++++++++++ 10 files changed, 237 insertions(+) create mode 100644 test/pacman/tests/hook-abortonfail.py create mode 100644 test/pacman/tests/hook-file-change-packages.py create mode 100644 test/pacman/tests/hook-file-remove-trigger-match.py create mode 100644 test/pacman/tests/hook-file-upgrade-nomatch.py create mode 100644 test/pacman/tests/hook-invalid-trigger.py create mode 100644 test/pacman/tests/hook-pkg-install-trigger-match.py create mode 100644 test/pacman/tests/hook-pkg-remove-trigger-match.py create mode 100644 test/pacman/tests/hook-pkg-upgrade-trigger-match.py create mode 100644 test/pacman/tests/hook-upgrade-trigger-no-match.py diff --git a/test/pacman/tests/TESTS b/test/pacman/tests/TESTS index cce0d58..8ad1b9c 100644 --- a/test/pacman/tests/TESTS +++ b/test/pacman/tests/TESTS @@ -50,6 +50,15 @@ TESTS += test/pacman/tests/fileconflict025.py TESTS += test/pacman/tests/fileconflict030.py TESTS += test/pacman/tests/fileconflict031.py TESTS += test/pacman/tests/fileconflict032.py +TESTS += test/pacman/tests/hook-abortonfail.py +TESTS += test/pacman/tests/hook-file-change-packages.py +TESTS += test/pacman/tests/hook-file-remove-trigger-match.py +TESTS += test/pacman/tests/hook-file-upgrade-nomatch.py +TESTS += test/pacman/tests/hook-invalid-trigger.py +TESTS += test/pacman/tests/hook-pkg-install-trigger-match.py +TESTS += test/pacman/tests/hook-pkg-remove-trigger-match.py +TESTS += test/pacman/tests/hook-pkg-upgrade-trigger-match.py +TESTS += test/pacman/tests/hook-upgrade-trigger-no-match.py TESTS += test/pacman/tests/ignore001.py TESTS += test/pacman/tests/ignore002.py TESTS += test/pacman/tests/ignore003.py diff --git a/test/pacman/tests/hook-abortonfail.py b/test/pacman/tests/hook-abortonfail.py new file mode 100644 index 0000000..e5044c0 --- /dev/null +++ b/test/pacman/tests/hook-abortonfail.py @@ -0,0 +1,25 @@ +self.description = "Abort transaction on hook failure with AbortOnFail" + +self.add_script("hook-script", ": > hook-output; exit 1") +self.add_hook("hook", + """ + [Trigger] + Type = Package + Operation = Install + Target = foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + AbortOnFail + """); + +sp = pmpkg("foo") +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("!PACMAN_RETCODE=0") +self.addrule("!PKG_EXIST=foo") +self.addrule("FILE_EXIST=hook-output") +self.addrule("PACMAN_OUTPUT=failed to run transaction hooks") diff --git a/test/pacman/tests/hook-file-change-packages.py b/test/pacman/tests/hook-file-change-packages.py new file mode 100644 index 0000000..ad96fc1 --- /dev/null +++ b/test/pacman/tests/hook-file-change-packages.py @@ -0,0 +1,32 @@ +self.description = "Triggering file moves between packages" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = File + Operation = Upgrade + Target = bin/foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +lp = pmpkg("foo", "1-1") +lp.files = ["bin/foo"] +self.addpkg2db("local", lp) + +sp1 = pmpkg("foo", "1-2") +self.addpkg2db("sync", sp1) + +sp2 = pmpkg("bar", "1-2") +sp2.files = ["bin/foo"] +self.addpkg2db("sync", sp2) + +self.args = "-S foo bar" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_VERSION=foo|1-2") +self.addrule("PKG_VERSION=bar|1-2") +self.addrule("FILE_EXIST=hook-output") diff --git a/test/pacman/tests/hook-file-remove-trigger-match.py b/test/pacman/tests/hook-file-remove-trigger-match.py new file mode 100644 index 0000000..6c9375c --- /dev/null +++ b/test/pacman/tests/hook-file-remove-trigger-match.py @@ -0,0 +1,24 @@ +self.description = "Remove a package matching a file removal hook" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = File + Operation = Remove + Target = bin/foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +lp = pmpkg("foo") +lp.files = ["bin/foo"] +self.addpkg2db("local", lp) + +self.args = "-R foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("!PKG_EXIST=foo") +self.addrule("FILE_EXIST=hook-output") diff --git a/test/pacman/tests/hook-file-upgrade-nomatch.py b/test/pacman/tests/hook-file-upgrade-nomatch.py new file mode 100644 index 0000000..e984d37 --- /dev/null +++ b/test/pacman/tests/hook-file-upgrade-nomatch.py @@ -0,0 +1,27 @@ +self.description = "Add and remove separate files that match an upgrade hook" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = File + Operation = Upgrade + Target = bin/?* + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +lp = pmpkg("foo") +lp.files = ["bin/foo"] +self.addpkg2db("local", lp) + +sp = pmpkg("foo") +sp.files = ["bin/bar"] +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("!FILE_EXIST=hook-output") diff --git a/test/pacman/tests/hook-invalid-trigger.py b/test/pacman/tests/hook-invalid-trigger.py new file mode 100644 index 0000000..72ac610 --- /dev/null +++ b/test/pacman/tests/hook-invalid-trigger.py @@ -0,0 +1,25 @@ +self.description = "Abort on invalid hook trigger" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + InvalidTriggerOption + Type = Package + Operation = Install + Target = foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +sp = pmpkg("foo") +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("!PACMAN_RETCODE=0") +self.addrule("!PKG_EXIST=foo") +self.addrule("!FILE_EXIST=hook-output") +self.addrule("PACMAN_OUTPUT=failed to run transaction hooks") diff --git a/test/pacman/tests/hook-pkg-install-trigger-match.py b/test/pacman/tests/hook-pkg-install-trigger-match.py new file mode 100644 index 0000000..00dfb32 --- /dev/null +++ b/test/pacman/tests/hook-pkg-install-trigger-match.py @@ -0,0 +1,23 @@ +self.description = "Install a package matching a hook" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = Package + Operation = Install + Target = foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +sp = pmpkg("foo") +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_EXIST=foo") +self.addrule("FILE_EXIST=hook-output") diff --git a/test/pacman/tests/hook-pkg-remove-trigger-match.py b/test/pacman/tests/hook-pkg-remove-trigger-match.py new file mode 100644 index 0000000..887b205 --- /dev/null +++ b/test/pacman/tests/hook-pkg-remove-trigger-match.py @@ -0,0 +1,23 @@ +self.description = "Remove a package matching a removal hook" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = Package + Operation = Remove + Target = foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +lp = pmpkg("foo") +self.addpkg2db("local", lp) + +self.args = "-R foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("!PKG_EXIST=foo") +self.addrule("FILE_EXIST=hook-output") diff --git a/test/pacman/tests/hook-pkg-upgrade-trigger-match.py b/test/pacman/tests/hook-pkg-upgrade-trigger-match.py new file mode 100644 index 0000000..77c2830 --- /dev/null +++ b/test/pacman/tests/hook-pkg-upgrade-trigger-match.py @@ -0,0 +1,26 @@ +self.description = "Upgrade a package matching an Upgrade hook" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = Package + Operation = Upgrade + Target = foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +lp = pmpkg("foo", "1-1") +self.addpkg2db("local", lp) + +sp = pmpkg("foo", "1-2") +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_VERSION=foo|1-2") +self.addrule("FILE_EXIST=hook-output") diff --git a/test/pacman/tests/hook-upgrade-trigger-no-match.py b/test/pacman/tests/hook-upgrade-trigger-no-match.py new file mode 100644 index 0000000..26f2743 --- /dev/null +++ b/test/pacman/tests/hook-upgrade-trigger-no-match.py @@ -0,0 +1,23 @@ +self.description = "Install a package matching an Upgrade hook" + +self.add_script("hook-script", ": > hook-output") +self.add_hook("hook", + """ + [Trigger] + Type = Package + Operation = Upgrade + Target = foo + + [Action] + When = PreTransaction + Exec = bin/hook-script + """); + +sp = pmpkg("foo") +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_EXIST=foo") +self.addrule("!FILE_EXIST=hook-output") -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
--- test/pacman/tests/TESTS | 9 ++++++ test/pacman/tests/hook-abortonfail.py | 25 +++++++++++++++++ test/pacman/tests/hook-file-change-packages.py | 32 ++++++++++++++++++++++ .../pacman/tests/hook-file-remove-trigger-match.py | 24 ++++++++++++++++ test/pacman/tests/hook-file-upgrade-nomatch.py | 27 ++++++++++++++++++ test/pacman/tests/hook-invalid-trigger.py | 25 +++++++++++++++++ .../pacman/tests/hook-pkg-install-trigger-match.py | 23 ++++++++++++++++ test/pacman/tests/hook-pkg-remove-trigger-match.py | 23 ++++++++++++++++ .../pacman/tests/hook-pkg-upgrade-trigger-match.py | 26 ++++++++++++++++++ test/pacman/tests/hook-upgrade-trigger-no-match.py | 23 ++++++++++++++++ 10 files changed, 237 insertions(+) create mode 100644 test/pacman/tests/hook-abortonfail.py create mode 100644 test/pacman/tests/hook-file-change-packages.py create mode 100644 test/pacman/tests/hook-file-remove-trigger-match.py create mode 100644 test/pacman/tests/hook-file-upgrade-nomatch.py create mode 100644 test/pacman/tests/hook-invalid-trigger.py create mode 100644 test/pacman/tests/hook-pkg-install-trigger-match.py create mode 100644 test/pacman/tests/hook-pkg-remove-trigger-match.py create mode 100644 test/pacman/tests/hook-pkg-upgrade-trigger-match.py create mode 100644 test/pacman/tests/hook-upgrade-trigger-no-match.py
OK - along with all pactest patches prior to this.
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- doc/.gitignore | 1 + doc/Makefile.am | 4 ++ doc/alpm-hooks.5.txt | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 doc/alpm-hooks.5.txt diff --git a/doc/.gitignore b/doc/.gitignore index ad496ce..2eae9e4 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,3 +1,4 @@ +alpm-hooks.5 PKGBUILD.5 libalpm.3 makepkg.8 diff --git a/doc/Makefile.am b/doc/Makefile.am index 60a70b3..90d90f3 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -4,6 +4,7 @@ # man_MANS if --enable-asciidoc and/or --enable-doxygen are used. ASCIIDOC_MANS = \ + alpm-hooks.5 \ pacman.8 \ makepkg.8 \ makepkg-template.1 \ @@ -20,6 +21,7 @@ ASCIIDOC_MANS = \ DOXYGEN_MANS = $(wildcard man3/*.3) HTML_MANPAGES = \ + alpm-hooks.5.html \ pacman.8.html \ makepkg.8.html \ makepkg-template.1.html \ @@ -46,6 +48,7 @@ HTML_DOCS = \ EXTRA_DIST = \ asciidoc.conf \ asciidoc-override.css \ + alpm-hooks.5.txt \ pacman.8.txt \ makepkg.8.txt \ makepkg-template.1.txt \ @@ -147,6 +150,7 @@ $(HTML_OTHER): asciidoc.conf Makefile.am %.3.html: ASCIIDOC_OPTS += -d manpage # Dependency rules +alpm-hooks.5 alpm-hooks.5.html: alpm-hooks.5.txt pacman.8 pacman.8.html: pacman.8.txt makepkg.8 makepkg.8.html: makepkg.8.txt makepkg-template.1 makepkg-template.1.html: makepkg-template.1.txt diff --git a/doc/alpm-hooks.5.txt b/doc/alpm-hooks.5.txt new file mode 100644 index 0000000..213b078 --- /dev/null +++ b/doc/alpm-hooks.5.txt @@ -0,0 +1,117 @@ +///// +vim:set ts=4 sw=4 syntax=asciidoc noet spell spelllang=en_us: +///// +alpm-hooks(5) +============= + +NAME +---- + +alpm-hooks - alpm hook file format + +SYNOPSIS +-------- + +-------- +[Trigger] (Required, Repeatable) +Operation = Install|Upgrade|Remove (Required, Repeatable) +Type = File|Package (Required) +Target = <Path|PkgName> (Required, Repeatable) + +[Action] (Required) +When = PreTransaction|PostTransaction (Required) +Exec = <Command> (Required) +Depends = <PkgName> (Optional) +AbortOnFail (Optional, PreTransaction only) +-------- + +DESCRIPTION +----------- + +libalpm provides the ability to specify hooks to run before or after +transactions based on the packages and/or files being modified. Hooks consist +of a single '[Action]' section describing the action to be run and one or more +'[Trigger]' section describing which transactions it should be run for. + +TRIGGERS +-------- + +Hooks must contain at least one '[Trigger]' section that determines which +transactions will cause the hook to run. If multiple trigger sections are +defined the hook will run if the transaction matches *any* of the triggers. + +*Operation =* Install|Upgrade|Remove:: + Select the type of operation to match targets against. May be specified + multiple times. Installations are considered an upgrade if the package or + file is already present on the system regardless of whether the new package + version is actually greater than the currently installed version. For File + triggers, this is true even if the file changes ownership from one package + to another. Required. + +*Type =* File|Package:: + Select whether targets are matched against transaction packages or files. + See CAVEATS for special notes regarding File triggers. Required. + +*Target =* path|package:: + The file path or package name to match against the active transaction. + File paths refer to the files in the package archive; the installation root + should *not* be included in the path. Shell-style glob patterns are + allowed. It is possible to invert matches by prepending a file with an + exclamation mark. May be specified multiple times. Required. + +ACTIONS +------- + +*Exec =* /path/to/executable:: + Executable to run. Required. + +*When =* PreTransaction|PostTransaction:: + When to run the hook. Required. + +*Depends =* package:: + Packages that must be installed for the hook to run. May be specified + multiple times. + +*AbortOnFail*:: + Causes the transaction to be aborted if the hook exits non-zero. Only + applies to PreTransaction hooks. + +OVERRIDING HOOKS +---------------- + +Hooks may be overridden by placing a file with the same name in a higher +priority hook directory. Hooks may be disabled by overriding them with +a symlink to '/dev/null'. + +EXAMPLES +-------- + +-------- +# Force disks to sync to reduce the risk of data corruption + +[Trigger] +Operation = Install +Operation = Upgrade +Operation = Remove +Type = Package +Target = * + +[Action] +Depends = coreutils +When = PostTransaction +Exec = /usr/bin/sync +-------- + +CAVEATS +------- + +There is no way to guarantee that the trigger operation was actually performed +for file triggers. Removal triggers will match even if the file did not +actually exist on the file system when the transaction begin. Installed or +upgraded files may be extracted with a '.pacnew' extension, leaving the path in +the trigger unmodified. + +PostTransaction hooks will *not* run if the transaction fails to complete for +any reason. + +include::footer.txt[] -- 2.6.1
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> ---
Two things. I think this should be named "libalpm-hooks".
+CAVEATS +------- + +There is no way to guarantee that the trigger operation was actually performed +for file triggers. Removal triggers will match even if the file did not +actually exist on the file system when the transaction begin. Installed or +upgraded files may be extracted with a '.pacnew' extension, leaving the path in +the trigger unmodified.
This still confuses me - it sound like documentation written by the person who wrote the feature! How about: There are situations when file triggers may act in unexpected ways. Hooks are triggered using the file list of the installed, upgraded, or removed package. When installing or upgrading a file that is extracted with a '.pacnew' extension, the original file name is used in triggering the hook. When removing a package, all files owned by that package can trigger a hook whether or not they were actually present on the file system before package removal.
Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- Using wordexp to split Exec fields makes splitting them, including proper handling of quoted arguments, extremely easy, but it includes some features and caveats that I'm not sure we want for this. From wordexp(3): Since the expansion is the same as the expansion by the shell (see sh(1)) of the parameters to a command, the string s must not contain characters that would be illegal in shell command parameters. In particular, there must not be any unescaped newline or |, &, ;, <, >, (, ), {, } characters outside a command substitution or parameter substitution context. If the argument s contains a word that starts with an unquoted comment character #, then it is unspecified whether that word and all following words are ignored, or the # is treated as a non-comment character. The expansion done consists of the following stages: tilde expansion (replacing ~user by user's home directory), variable substitution (replacing $FOO by the value of the environment variable FOO), command substitution (replacing $(command) or `command` by the output of command), arithmetic expansion, field splitting, wildcard expansion, quote removal. The result of expansion of special parameters ($@, $*, $#, $?, $-, $$, $!, $0) is unspecified. Field splitting is done using the environment variable $IFS. If it is not set, the field separators are space, tab and newline. Does anybody have any objection to using wordexp for this? lib/libalpm/hook.c | 14 ++++++++++---- test/pacman/tests/TESTS | 1 + test/pacman/tests/hook-exec-with-arguments.py | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 test/pacman/tests/hook-exec-with-arguments.py diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c index fe4b204..7485135 100644 --- a/lib/libalpm/hook.c +++ b/lib/libalpm/hook.c @@ -20,6 +20,7 @@ #include <dirent.h> #include <errno.h> #include <string.h> +#include <wordexp.h> #include "handle.h" #include "hook.h" @@ -49,7 +50,7 @@ struct _alpm_hook_t { char *name; alpm_list_t *triggers; alpm_list_t *depends; - char *cmd; + wordexp_t *cmd; enum _alpm_hook_when_t when; int abort_on_fail; }; @@ -71,6 +72,7 @@ static void _alpm_hook_free(struct _alpm_hook_t *hook) { if(hook) { free(hook->name); + wordfree(hook->cmd); free(hook->cmd); alpm_list_free_inner(hook->triggers, (alpm_list_fn_free) _alpm_trigger_free); alpm_list_free(hook->triggers); @@ -208,7 +210,12 @@ static int _alpm_hook_parse_cb(const char *file, int line, } else if(strcmp(key, "AbortOnFail") == 0) { hook->abort_on_fail = 1; } else if(strcmp(key, "Exec") == 0) { - STRDUP(hook->cmd, value, return 1); + wordexp_t *p = calloc(sizeof(wordexp_t), 1); + if(wordexp(value, p, WRDE_NOCMD | WRDE_UNDEF) == 0) { + hook->cmd = p; + } else { + error(_("hook %s line %d: invalid Exec %s\n"), file, line, value); + } } else { error(_("hook %s line %d: invalid option %s\n"), file, line, value); } @@ -378,7 +385,6 @@ static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle) 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)) { @@ -388,7 +394,7 @@ static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook) } } - return _alpm_run_chroot(handle, hook->cmd, argv); + return _alpm_run_chroot(handle, hook->cmd->we_wordv[0], hook->cmd->we_wordv); } int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when) diff --git a/test/pacman/tests/TESTS b/test/pacman/tests/TESTS index 8ad1b9c..afd2e69 100644 --- a/test/pacman/tests/TESTS +++ b/test/pacman/tests/TESTS @@ -51,6 +51,7 @@ TESTS += test/pacman/tests/fileconflict030.py TESTS += test/pacman/tests/fileconflict031.py TESTS += test/pacman/tests/fileconflict032.py TESTS += test/pacman/tests/hook-abortonfail.py +TESTS += test/pacman/tests/hook-exec-with-arguments.py TESTS += test/pacman/tests/hook-file-change-packages.py TESTS += test/pacman/tests/hook-file-remove-trigger-match.py TESTS += test/pacman/tests/hook-file-upgrade-nomatch.py diff --git a/test/pacman/tests/hook-exec-with-arguments.py b/test/pacman/tests/hook-exec-with-arguments.py new file mode 100644 index 0000000..d3df87b --- /dev/null +++ b/test/pacman/tests/hook-exec-with-arguments.py @@ -0,0 +1,22 @@ +self.description = "Hook with arguments" + +self.add_hook("hook", + """ + [Trigger] + Type = Package + Operation = Install + Target = foo + + [Action] + When = PreTransaction + Exec = bin/sh -c ': > hook-output' + """); + +sp = pmpkg("foo") +self.addpkg2db("sync", sp) + +self.args = "-S foo" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_EXIST=foo") +self.addrule("FILE_EXIST=hook-output") -- 2.6.1
Allows callers to provide a callback to provide strings to pass to the chroot on stdin. --- lib/libalpm/hook.c | 2 +- lib/libalpm/trans.c | 2 +- lib/libalpm/util.c | 153 +++++++++++++++++++++++++++++++++++++++++++++------- lib/libalpm/util.h | 5 +- 4 files changed, 141 insertions(+), 21 deletions(-) diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c index fe4b204..3dd80be 100644 --- a/lib/libalpm/hook.c +++ b/lib/libalpm/hook.c @@ -388,7 +388,7 @@ static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook) } } - return _alpm_run_chroot(handle, hook->cmd, argv); + return _alpm_run_chroot(handle, hook->cmd, argv, NULL, NULL); } int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when) diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index a6b1aef..06997a0 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -391,7 +391,7 @@ int _alpm_runscriptlet(alpm_handle_t *handle, const char *filepath, _alpm_log(handle, ALPM_LOG_DEBUG, "executing \"%s\"\n", cmdline); - retval = _alpm_run_chroot(handle, SCRIPTLET_SHELL, argv); + retval = _alpm_run_chroot(handle, SCRIPTLET_SHELL, argv, NULL, NULL); cleanup: if(scriptfn && unlink(scriptfn)) { diff --git a/lib/libalpm/util.c b/lib/libalpm/util.c index 66a2742..d55126c 100644 --- a/lib/libalpm/util.c +++ b/lib/libalpm/util.c @@ -31,6 +31,7 @@ #include <limits.h> #include <sys/wait.h> #include <fnmatch.h> +#include <poll.h> /* libarchive */ #include <archive.h> @@ -445,16 +446,30 @@ ssize_t _alpm_files_in_directory(alpm_handle_t *handle, const char *path, return files; } +/* write wrapper that ignores SIGPIPE */ +static ssize_t _alpm_pipe_write(int fd, const void *buf, size_t count) +{ + sigset_t new, old; + ssize_t ret; + sigemptyset(&new); + sigaddset(&new, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &new, &old); + ret = write(fd, buf, count); + pthread_sigmask(SIG_SETMASK, &old, NULL); + return ret; +} + /** Execute a command with arguments in a chroot. * @param handle the context handle * @param cmd command to execute * @param argv arguments to pass to cmd * @return 0 on success, 1 on error */ -int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[]) +int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[], + _alpm_cb_io in_cb, void *in_ctx) { pid_t pid; - int pipefd[2], cwdfd; + int pipefd[2], ipipefd[2], cwdfd; int retval = 0; /* save the cwd so we can restore it later */ @@ -482,6 +497,12 @@ int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[]) goto cleanup; } + if(in_cb && pipe(ipipefd) == -1) { + _alpm_log(handle, ALPM_LOG_ERROR, _("could not create pipe (%s)\n"), strerror(errno)); + retval = 1; + goto cleanup; + } + /* fork- parent and child each have separate code blocks below */ pid = fork(); if(pid == -1) { @@ -497,6 +518,11 @@ int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[]) close(2); while(dup2(pipefd[1], 1) == -1 && errno == EINTR); while(dup2(pipefd[1], 2) == -1 && errno == EINTR); + if(in_cb) { + while(dup2(ipipefd[0], 0) == -1 && errno == EINTR); + close(ipipefd[0]); + close(ipipefd[1]); + } close(pipefd[0]); close(pipefd[1]); if(cwdfd >= 0) { @@ -521,27 +547,118 @@ int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[]) } else { /* this code runs for the parent only (wait on the child) */ int status; - FILE *pipe_file; - + char obuf[PIPE_BUF + 1], ibuf[PIPE_BUF + 1]; + ssize_t olen = 0, ilen = 0; + int in = pipefd[0], out = in_cb ? ipipefd[1] : -1; + struct pollfd fds[2]; + nfds_t nfds = 2; + int timeout = -1; + + fds[0].fd = in; + fds[0].events = POLLIN; + fcntl(in, F_SETFL, O_NONBLOCK); close(pipefd[1]); - pipe_file = fdopen(pipefd[0], "r"); - if(pipe_file == NULL) { - close(pipefd[0]); - retval = 1; - } else { - while(!feof(pipe_file)) { - char line[PATH_MAX]; + + fds[1].fd = out; + fds[1].events = POLLOUT; + if(in_cb) { + fcntl(out, F_SETFL, O_NONBLOCK); + close(ipipefd[0]); + } + + while((in != -1 || out != -1) && poll(fds, nfds, timeout) > 0) { + if(fds[0].revents & POLLIN) { alpm_event_scriptlet_info_t event = { .type = ALPM_EVENT_SCRIPTLET_INFO, - .line = line + .line = ibuf }; - if(safe_fgets(line, PATH_MAX, pipe_file) == NULL) { - break; + ssize_t space = PIPE_BUF - ilen; + ssize_t nread = read(in, ibuf + ilen, space); + if(nread > 0) { + char *newline = memchr(ibuf + ilen, '\n', nread); + ilen += nread; + if(newline) { + while(newline) { + size_t linelen = newline - ibuf + 1; + char old = ibuf[linelen]; + ibuf[linelen] = '\0'; + alpm_logaction(handle, "ALPM-SCRIPTLET", "%s", ibuf); + EVENT(handle, &event); + ibuf[linelen] = old; + ilen -= linelen; + memmove(ibuf, ibuf + linelen, ilen); + newline = memchr(ibuf, '\n', ilen); + } + } else if(nread == space) { + /* we didn't read a full line, but we're out of space */ + ibuf[PIPE_BUF] = '\0'; + alpm_logaction(handle, "ALPM-SCRIPTLET", "%s\n", ibuf); + EVENT(handle, &event); + ilen = 0; + } + } else { + if(nread == -1 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) { + /* nothing read, try again */ + } else { + /* we either encountered an error or the pipe was closed */ + if(nread == -1) { + _alpm_log(handle, ALPM_LOG_ERROR, + _("unable to read from pipe (%s)\n"), strerror(errno)); + } + if(ilen) { + ibuf[ilen] = '\0'; + alpm_logaction(handle, "ALPM-SCRIPTLET", "%s\n", ibuf); + EVENT(handle, &event); + } + close(in); + in = -1; + fds[0].fd = -1; + } } - alpm_logaction(handle, "ALPM-SCRIPTLET", "%s", line); - EVENT(handle, &event); + } else if(fds[0].revents) { + close(in); + in = -1; + fds[0].fd = -1; } - fclose(pipe_file); + if(fds[1].revents) { + ssize_t nwrite; + if(olen == 0) { + olen = in_cb(obuf, PIPE_BUF, in_ctx); + if(olen == 0) { + /* no more to write, close the pipe */ + close(out); + out = -1; + fds[1].fd = -1; + continue; + } + } + if(olen && (nwrite = _alpm_pipe_write(out, obuf, olen)) == -1) { + if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + /* nothing written, try again later */ + } else { + /* something went wrong, close the pipe */ + _alpm_log(handle, ALPM_LOG_ERROR, + _("unable to write to pipe (%s)\n"), strerror(errno)); + close(out); + out = -1; + fds[1].fd = -1; + } + } else { + olen -= nwrite; + memmove(obuf, obuf + nwrite, olen); + } + } else if(fds[1].revents) { + close(out); + out = -1; + fds[1].fd = -1; + } + } + + if(out != -1) { + close(out); + } + if(in != -1) { + close(in); } while(waitpid(pid, &status, 0) == -1) { @@ -605,7 +722,7 @@ int _alpm_ldconfig(alpm_handle_t *handle) char arg0[32]; char *argv[] = { arg0, NULL }; strcpy(arg0, "ldconfig"); - return _alpm_run_chroot(handle, LDCONFIG, argv); + return _alpm_run_chroot(handle, LDCONFIG, argv, NULL, NULL); } } diff --git a/lib/libalpm/util.h b/lib/libalpm/util.h index 95112cf..c0c9ff0 100644 --- a/lib/libalpm/util.h +++ b/lib/libalpm/util.h @@ -119,7 +119,10 @@ int _alpm_unpack(alpm_handle_t *handle, const char *archive, const char *prefix, ssize_t _alpm_files_in_directory(alpm_handle_t *handle, const char *path, int full_count); -int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[]); +typedef ssize_t (*_alpm_cb_io)(void *buf, ssize_t len, void *ctx); + +int _alpm_run_chroot(alpm_handle_t *handle, const char *cmd, char *const argv[], + _alpm_cb_io in_cb, void *in_ctx); int _alpm_ldconfig(alpm_handle_t *handle); int _alpm_str_cmp(const void *s1, const void *s2); char *_alpm_filecache_find(alpm_handle_t *handle, const char *filename); -- 2.6.1
Add a new hook option 'NeedsTargets' to indicate that matched targets should be passed on stdin. Targets are sorted and duplicates are removed. --- lib/libalpm/hook.c | 126 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 17 deletions(-) diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c index 3dd80be..3e3d1b9 100644 --- a/lib/libalpm/hook.c +++ b/lib/libalpm/hook.c @@ -49,9 +49,10 @@ struct _alpm_hook_t { char *name; alpm_list_t *triggers; alpm_list_t *depends; + alpm_list_t *matches; char *cmd; enum _alpm_hook_when_t when; - int abort_on_fail; + int abort_on_fail, needs_targets; }; struct _alpm_hook_cb_ctx { @@ -74,6 +75,7 @@ static void _alpm_hook_free(struct _alpm_hook_t *hook) 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); } @@ -207,6 +209,8 @@ static int _alpm_hook_parse_cb(const char *file, int line, 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) { STRDUP(hook->cmd, value, return 1); } else { @@ -219,7 +223,8 @@ static int _alpm_hook_parse_cb(const char *file, int line, return 0; } -static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, struct _alpm_trigger_t *t) +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; @@ -303,15 +308,31 @@ static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, struct _alpm_tri || (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); + 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_trigger_t *t) +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) { @@ -319,11 +340,19 @@ static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct _alpm_trig 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; + if(hook->needs_targets) { + upgrade = alpm_list_add(upgrade, pkg->name); + } else { + return 1; + } } } else { if(t->op & ALPM_HOOK_OP_INSTALL) { - return 1; + if(hook->needs_targets) { + install = alpm_list_add(install, pkg->name); + } else { + return 1; + } } } } @@ -336,31 +365,47 @@ static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct _alpm_trig 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; + if(hook->needs_targets) { + remove = alpm_list_add(remove, pkg->name); + } else { + return 1; + } } } } } - return 0; + /* 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_trigger_t *t) +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, t) - : _alpm_hook_trigger_match_file(handle, t); + ? _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, i->data)) { - return 1; + if(_alpm_hook_trigger_match(handle, hook, i->data)) { + if(!hook->needs_targets) { + return 1; + } else { + ret = 1; + } } } - return 0; + return ret; } static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle) @@ -375,6 +420,44 @@ static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle) 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); @@ -388,7 +471,16 @@ static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook) } } - return _alpm_run_chroot(handle, hook->cmd, argv, NULL, NULL); + 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); + ctx = hook->matches = _alpm_strlist_dedup(hook->matches); + return _alpm_run_chroot(handle, hook->cmd, argv, + (_alpm_cb_io) _alpm_hook_feed_targets, &ctx); + } else { + return _alpm_run_chroot(handle, hook->cmd, argv, NULL, NULL); + } } int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when) -- 2.6.1
participants (3)
-
Allan McRae
-
Andrew Gregory
-
Silvan Jegen