[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 =
Signed-off-by: Andrew Gregory
Signed-off-by: Andrew Gregory
---
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
Signed-off-by: Andrew Gregory
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 =
(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 =
(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
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
--- 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
#include #include #include @@ -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
--- 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
#include #include #include @@ -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
+/* 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
--- 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
On 15/09/15 08:37, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory
--- 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
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
--- 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
---
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 =
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 =
(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
--- 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
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
+ * + * 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 +#include +#include +#include + +#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
+ * + * 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
+ * + * 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 +#include +#include +#include + +#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
+ * + * 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
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory
--- 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
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory
--- 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
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory
--- 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
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory
--- 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
Signed-off-by: Andrew Gregory
--- 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
On 17/10/15 10:28, Andrew Gregory wrote:
Signed-off-by: Andrew Gregory
---
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
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
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