[pacman-dev] [PATCH] [RFC] alpm: Add a new hook type: PackageDir

Olivier Brunel jjk at jjacky.com
Thu Dec 31 19:38:54 UTC 2015


Same as Package only instead of defining matching packages in the .hook file, it
is done on the filesystem.

Signed-off-by: Olivier Brunel <jjk at jjacky.com>
---
Hey there,

So I may be totally wrong, but I thought one of the cool things that could be
done with hooks was that instead of having e.g. 3 packages refreshing a cache in
their post_upgrade scriptlets, it'll be done via one post-transaction hook, so
that it only runs once.

And unless I'm missing something, this doesn't seem possible currently. At
least not in a "good" way, since all matching packages must be included inside
the .hook file itself.

So am I wrong and was this never planned? Or is actually possible? Or is it
meant to be added later?

In case, here's a patch (though no documentation) that adds a new type of hooks:
PackageDir

Those are just like Package (and in fact turned into Package once
validated/filled), only no targets should be featured inside the hook
file itself, instead alpm will check for a directory by the hook name plus ".d"
inside all the hookdirs (in order), so e.g. for hook "foobar" folders
"foobar.d" will be looked for.

Inside those dirs, a regular file means a target to add, said target being the
name of the file. So files must be named after the packages to add/trigger the
hook. A symlink however will mean to remove said package from the targets; The
idea being that a user could create a symlink (to /dev/null) in e.g.
/etc/pacman.d/hooks/foobar.d by the name of a package, to "undo"/mask a file by
the same name in /usr/lib/alpm/hooks/foobar.d, file that could be part of any
(other) package.

That way, the font package provides the hook font-cache and any package that
needs to trigger it simply adds an empty file to
/usr/lib/alpm/hooks/font-cache.d by the name of the package.
Also works for mkinitcpio providing a hook to rebuild the initramfs, which can
then be triggered by any of e.g. mkinitcpio, linux, systemd, etc

Thoughts?
-j

 lib/libalpm/hook.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 91 insertions(+), 3 deletions(-)

diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c
index b5ed17d..876beaf 100644
--- a/lib/libalpm/hook.c
+++ b/lib/libalpm/hook.c
@@ -37,6 +37,7 @@ enum _alpm_hook_op_t {
 
 enum _alpm_trigger_type_t {
 	ALPM_HOOK_TYPE_PACKAGE = 1,
+	ALPM_HOOK_TYPE_PACKAGE_DIR,
 	ALPM_HOOK_TYPE_FILE,
 };
 
@@ -95,12 +96,97 @@ static void _alpm_hook_free(struct _alpm_hook_t *hook)
 	}
 }
 
+static int _alpm_trigger_load_targets(alpm_handle_t *handle,
+		struct _alpm_trigger_t *trigger, char *hookname)
+{
+	int ret = 0;
+	size_t len = strlen(hookname);
+	alpm_list_t *i;
+
+	for(i = handle->hookdirs; i && ret == 0; i = alpm_list_next(i)) {
+		int err;
+		char path[PATH_MAX];
+		size_t dirlen = strlen(i->data);
+		struct dirent entry, *result;
+		DIR *d;
+
+		if(dirlen + len + 3 >= PATH_MAX) {
+			_alpm_log(handle, ALPM_LOG_ERROR, _("could not open directory: %s: %s\n"),
+					(char *)i->data, strerror(ENAMETOOLONG));
+			ret = -1;
+			break;
+		}
+		memcpy(path, i->data, dirlen);
+		memcpy(path + dirlen, hookname, len);
+		memcpy(path + dirlen + len, ".d/", 4);
+
+		if(!(d = opendir(path))) {
+			if(errno == ENOENT) {
+				continue;
+			} else {
+				_alpm_log(handle, ALPM_LOG_ERROR,
+						_("could not open directory: %s: %s\n"), path, strerror(errno));
+				ret = -1;
+				break;
+			}
+		}
+
+		while((err = readdir_r(d, &entry, &result)) == 0 && result) {
+			struct stat buf;
+
+			if(strcmp(entry.d_name, ".") == 0 || strcmp(entry.d_name, "..") == 0) {
+					continue;
+			}
+
+			if(fstatat(dirfd(d), entry.d_name, &buf, 0) != 0) {
+				_alpm_log(handle, ALPM_LOG_ERROR,
+						_("could not stat file %s%s: %s\n"), path, entry.d_name, strerror(errno));
+				ret = -1;
+				break;
+			}
+
+			if(S_ISREG(buf.st_mode)) {
+				if(!alpm_list_find_str(trigger->targets, entry.d_name)) {
+					char *s;
+					STRDUP(s, entry.d_name, ret = -1; break);
+					trigger->targets = alpm_list_add(trigger->targets, s);
+				}
+			} else if(S_ISLNK(buf.st_mode)) {
+				char *s;
+				trigger->targets = alpm_list_remove_str(trigger->targets, entry.d_name, &s);
+				free(s);
+			}
+		}
+
+		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);
+	}
+
+	return ret;
+}
+
 static int _alpm_trigger_validate(alpm_handle_t *handle,
-		struct _alpm_trigger_t *trigger, const char *file)
+		struct _alpm_trigger_t *trigger, const char *file, char *hookname)
 {
 	int ret = 0;
 
-	if(trigger->targets == NULL) {
+	if(trigger->type == ALPM_HOOK_TYPE_PACKAGE_DIR) {
+		trigger->type = ALPM_HOOK_TYPE_PACKAGE;
+		if(trigger->targets) {
+			ret = -1;
+			_alpm_log(handle, ALPM_LOG_ERROR,
+					_("Unexpected targets for PackageDir trigger in hook: %s\n"), file);
+		} else if(_alpm_trigger_load_targets(handle, trigger, hookname) < 0) {
+			ret = -1;
+		}
+		/* we allow no targets in this case, since e.g. they could have been
+		 * "masked" by the user via symlinks */
+	} else if(trigger->targets == NULL) {
 		ret = -1;
 		_alpm_log(handle, ALPM_LOG_ERROR,
 				_("Missing trigger targets in hook: %s\n"), file);
@@ -134,7 +220,7 @@ static int _alpm_hook_validate(alpm_handle_t *handle,
 	}
 
 	for(i = hook->triggers; i; i = i->next) {
-		if(_alpm_trigger_validate(handle, i->data, file) != 0) {
+		if(_alpm_trigger_validate(handle, i->data, file, hook->name) != 0) {
 			ret = -1;
 		}
 	}
@@ -297,6 +383,8 @@ static int _alpm_hook_parse_cb(const char *file, int line,
 		} else if(strcmp(key, "Type") == 0) {
 			if(strcmp(value, "Package") == 0) {
 				t->type = ALPM_HOOK_TYPE_PACKAGE;
+			} else if(strcmp(value, "PackageDir") == 0) {
+				t->type = ALPM_HOOK_TYPE_PACKAGE_DIR;
 			} else if(strcmp(value, "File") == 0) {
 				t->type = ALPM_HOOK_TYPE_FILE;
 			} else {
-- 
2.6.4


More information about the pacman-dev mailing list