[pacman-dev] [PATCH] sortbydeps: skip local packages being updated

2015-09-14 Thread Andrew Gregory
Signed-off-by: Andrew Gregory 
---
 lib/libalpm/deps.c | 11 ++--
 test/pacman/tests/TESTS|  1 +
 .../tests/dependency-cycle-fixed-by-upgrade.py | 32 ++
 3 files changed, 41 insertions(+), 3 deletions(-)
 create mode 100644 test/pacman/tests/dependency-cycle-fixed-by-upgrade.py

diff --git a/lib/libalpm/deps.c b/lib/libalpm/deps.c
index daab9c3..34ac8d3 100644
--- a/lib/libalpm/deps.c
+++ b/lib/libalpm/deps.c
@@ -105,7 +105,13 @@ static alpm_list_t *dep_graph_init(alpm_handle_t *handle,
alpm_list_t *i, *j;
alpm_list_t *vertices = NULL;
alpm_list_t *localpkgs = alpm_list_diff(
-   alpm_db_get_pkgcache(handle->db_local), ignore, 
_alpm_pkg_cmp);
+   alpm_db_get_pkgcache(handle->db_local), targets, 
_alpm_pkg_cmp);
+
+   if(ignore) {
+   alpm_list_t *oldlocal = localpkgs;
+   localpkgs = alpm_list_diff(oldlocal, ignore, _alpm_pkg_cmp);
+   alpm_list_free(oldlocal);
+   }
 
/* We create the vertices */
for(i = targets; i; i = i->next) {
@@ -137,8 +143,7 @@ static alpm_list_t *dep_graph_init(alpm_handle_t *handle,
alpm_graph_t *vertex_j = _alpm_graph_new();
vertex_j->data = (void *)j->data;
vertices = alpm_list_add(vertices, vertex_j);
-   vertex_i->children =
-   alpm_list_add(vertex_i->children, 
vertex_j);
+   vertex_i->children = 
alpm_list_add(vertex_i->children, vertex_j);
localpkgs = alpm_list_remove_item(localpkgs, j);
free(j);
}
diff --git a/test/pacman/tests/TESTS b/test/pacman/tests/TESTS
index 3ff0b0c..cce0d58 100644
--- a/test/pacman/tests/TESTS
+++ b/test/pacman/tests/TESTS
@@ -14,6 +14,7 @@ TESTS += test/pacman/tests/depconflict100.py
 TESTS += test/pacman/tests/depconflict110.py
 TESTS += test/pacman/tests/depconflict111.py
 TESTS += test/pacman/tests/depconflict120.py
+TESTS += test/pacman/tests/dependency-cycle-fixed-by-upgrade.py
 TESTS += test/pacman/tests/deptest001.py
 TESTS += test/pacman/tests/dummy001.py
 TESTS += test/pacman/tests/epoch001.py
diff --git a/test/pacman/tests/dependency-cycle-fixed-by-upgrade.py 
b/test/pacman/tests/dependency-cycle-fixed-by-upgrade.py
new file mode 100644
index 000..dc042e5
--- /dev/null
+++ b/test/pacman/tests/dependency-cycle-fixed-by-upgrade.py
@@ -0,0 +1,32 @@
+self.description = "Circular dependency fixed by removed dependency"
+
+lp1 = pmpkg("pkg1", "1-1")
+lp1.depends = ["pkg2"]
+
+lp2 = pmpkg("pkg2", "1-1")
+lp2.depends = ["pkg3"]
+
+lp3 = pmpkg("pkg3", "1-1")
+lp3.depends = ["pkg1"]
+
+for p in lp1, lp2, lp3:
+   self.addpkg2db("local", p);
+
+sp1 = pmpkg("pkg1", "1-2")
+sp1.depends = ["pkg2"]
+
+sp2 = pmpkg("pkg2", "1-2")
+
+sp3 = pmpkg("pkg3", "1-2")
+sp3.depends = ["pkg1"]
+
+for p in sp1, sp2, sp3:
+   self.addpkg2db("sync", p);
+
+self.args = "-S %s %s %s" % (sp1.name, sp2.name, sp3.name)
+
+self.addrule("PACMAN_RETCODE=0")
+self.addrule("PKG_VERSION=pkg1|1-2")
+self.addrule("PKG_VERSION=pkg2|1-2")
+self.addrule("PKG_VERSION=pkg3|1-2")
+self.addrule("!PACMAN_OUTPUT=dependency cycle detected")
-- 
2.5.2


[pacman-dev] [PATCH v2 4/7] wip add hooks

2015-09-14 Thread Andrew Gregory
---

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 
*hook

[pacman-dev] [PATCH v2 6/7] start hook documentation

2015-09-14 Thread 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 000..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 =  (Required)
+ Depends =  (Optional)
+ AbortOnFail (Optional, PreTransaction only)
+
+DESCRIPTION
+---
+
+libalpm provides the ability to specify hooks to run before or after
+transactions based on the packages and/or files being modified.  Hooks consist
+of a single "[Action]" section describing the action to be run and one or more
+"[Trigger]" section describing which transactions it should be run for.
+
+TRIGGERS
+
+
+Hooks must contain at least one "[Trigger]" section that determines which
+transactions will cause the hook to run.  If multiple trigger sections are
+defined the hook will run if the transaction matches *any* of the triggers.
+
+*Operation =* Sync|Remove::
+   Select the type of operation to match targets against.
+
+*Type =* File|Package::
+   Select whether targets are matched against transaction packages or 
files.
+   See CAVEATS for special notes regarding File triggers.
+
+*Target =* path|package::
+   The file path or package name to match against the active transaction.
+   File paths refer to the files in the package archive; the installation 
root
+   should *not* be included in the path.  Shell-style glob patterns are
+   allowed. It is possible to invert matches by prepending a file with an
+   exclamation mark. May be specified multiple times. Required.
+
+ACTIONS
+---
+
+*Exec =* /path/to/executable::
+   Executable to run.  Required.
+
+*When =* PreTransaction|PostTransaction::
+   When to run the hook.  Required.
+
+*Depends =* package::
+   Packages that must be installed for the hook to run. May be specified
+   multiple times.
+
+*AbortOnFail*::
+   Causes the transaction to be aborted if the hook exits non-zero.  Only
+   applies to PreTransaction hooks.
+
+OVERRIDING HOOKS
+
+
+Hooks may be overridden by placing a file with the same name in a higher
+priority hook directory.  Hooks may be disabled by overriding them with
+a symlink to /dev/null.
+
+EXAMPLES
+
+
+ # Force disks to sync to prevent data corruption
+
+ [Trigger]
+ Operation = Sync
+ Type = Package
+ Target = *
+
+ [Trigger]
+ Operation = Remove
+ Type = Package
+ Target = *
+
+ [Action]
+ Depends = coreutils
+ When = PostTransaction
+ Exec = /usr/bin/sync
+
+CAVEATS
+---
+
+There is no way to guarantee that the trigger operation was actually performed
+for file triggers.  Removal triggers will match even if the file did not
+actually exist on the file system when the transaction begin.  Sync triggers
+may be extracted as a .pacnew file, leaving the trigger path unmodified.
+
+include::footer.txt[]
-- 
2.5.2


[pacman-dev] [PATCH v2 7/7] pacman: add user hook directories

2015-09-14 Thread Andrew Gregory
---
 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


[pacman-dev] [PATCH v2 5/7] add example hooks

2015-09-14 Thread Andrew Gregory
Signed-off-by: Andrew Gregory 
---
 hooks/checkboot.hook  | 17 +
 hooks/checkmount  | 13 +
 hooks/checkmount.conf |  1 +
 hooks/checkmount.hook | 17 +
 hooks/sync.hook   | 16 
 5 files changed, 64 insertions(+)
 create mode 100644 hooks/checkboot.hook
 create mode 100644 hooks/checkmount
 create mode 100644 hooks/checkmount.conf
 create mode 100644 hooks/checkmount.hook
 create mode 100644 hooks/sync.hook

diff --git a/hooks/checkboot.hook b/hooks/checkboot.hook
new file mode 100644
index 000..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 000..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 000..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 000..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 000..43c2869
--- /dev/null
+++ b/hooks/sync.hook
@@ -0,0 +1,16 @@
+# Force disks to sync to prevent data corruption
+
+[Trigger]
+Operation = Sync
+Type = Package
+Target = *
+
+[Trigger]
+Operation = Remove
+Type = Package
+Target = *
+
+[Action]
+Depends = coreutils
+When = PostTransaction
+Exec = /usr/bin/sync
-- 
2.5.2


[pacman-dev] (no subject)

2015-09-14 Thread Andrew Gregory
>From b3065d8c0a132c4c52707533014e7bc953d501ae Mon Sep 17 00:00:00 2001
Message-Id: 

In-Reply-To: 
References: <1436003967-4737-1-git-send-email-andrew.gregor...@gmail.com>

From: Andrew Gregory 
Date: Sat, 4 Jul 2015 03:35:59 -0400
Subject: [PATCH v2 4/7] wip add hooks

---
 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_a

[pacman-dev] [PATCH v2 1/7] move strtim to util-common

2015-09-14 Thread Andrew Gregory
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 .
  */
 
+#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';
+   }
+   }
+
+   /* check if there wasn't anything but whitespace in the string. */
+   if(*str == '\0') {
+   return 0;
+   }
+
+   end = (str + strlen(str) - 1);
+   while(isspace((unsigned char)*end)) {
+   end--;
+   }
+   *++end = '\0';
+
+   return end - pch;
+}
+
 #ifndef HAVE_STRNLEN
 /* A quick and dirty implementation derived from glibc */
 /** Determines the length of a fixed-size string.
diff --git a/src/common/util-common.h b/src/common/util-common.h
index a2093be..af2ebda 100644
--- a/src/common/util-common.h
+++ b/src/common/util-common.h
@@ -30,6 +30,8 @@ int llstat(char *path, struct stat *buf);
 
 char *safe_fgets(char *s, int size, FILE *stream);
 
+size_t strtrim(char *str);
+
 #ifndef HAVE_STRNDUP
 char *strndup(const char *s, size_t n);
 #endif
diff --git a/src/pacman/ini.c b/src/pacman/ini.c
index fed0b18..da30af1 100644
--- a/src/pacman/ini.c
+++ b/src/pacman/ini.c
@@ -24,7 +24,7 @@
 #include 
 
 #include "ini.h"
-#include "util.h"
+#include "util-common.h"
 
 /**
  * @brief Parse a pacman-style INI config file.
diff --git a/src/pacman/util.c b/src/pacman/util.c
index 3d71d8b..1542f8a 100644
--- a/src/pacman/util.c
+++ b/src/pacman/util.c
@@ -29,7 +29,6 @@
 #include  /* intmax_t */
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -353,44 +352,6 @@ void indentprint(const char *str, unsigned short indent, 
unsigned short cols)
free(wcstr);
 }
 
-/* Trim whitespace and newlines from a string
- */
-size_t strtrim(char *str)
-{
-   char *end, *pch = str;
-
-   if(str == NULL || *str == '\0') {
-   /* string is empty, so we're done. */
-   return 0;
-   }
-
-   while(isspace((unsigned char)*pch)) {
-   pch++;
-   }
-   if(pch != str) {
-   size_t len = strlen(pch);
-   if(len) {
-   memmove(str, pch, len + 1);
-   pch = str;
-   } else {
-   *str = '\0';
-   }
-   }
-
-   /* check if there wasn't anything but whitespace in the string. */
-   if(*str == '\0') {
-   return 0;
-   }
-
-   end = (str + strlen(str) - 1);
-   while(isspace((unsigned char)*end)) {
-   end--;
-   }
-   *++end = '\0';
-
-   return end - pch;
-}
-
 /* Replace all occurrences of 'needle' with 'replace' in 'str', returning
  * a new string (must be free'd) */
 char *strreplace(const char *str, const char *needle, const char *replace)
diff --git a/src/pacman/util.h b/src/pacman/util.h
index c82d816..744c13c 100644
--- a/src/pacman/util.h
+++ b/src/pacman/util.h
@@ -54,7 +54,6 @@ unsigned short getcols(void);
 void columns_cache_reset(void);
 int rmrf(const char *path);
 void indentprint(const char *str, unsigned short indent, unsigned short cols);
-size_t strtrim(char *str);
 char *strreplace(const char *str, const char *needle, const char *replace);
 void string_display(const char *title, const char *string, unsigned short 
cols);
 double humanize_size(off_t bytes, const char target_unit, int precision,
diff --git a/src/util/pactree.c b/src/util/pactree.c
index 11ad7ca..6969995 100644
--- a/src/util/pactree.c
+++ b/src/util/pactree.c
@@ -123,42 +123,6 @@ int searchsyncs = 0;
 const char *dbpath = DBPATH;
 const char *configfile = CONFFILE;
 
-static size_t strtrim(char *str)
-{
-   char *end, *pch = str;
-
-   if(str == NULL || *str == '\0') {
-   /* string is empty, so we're done. */
-   return 0;
-   }
-
-   while

[pacman-dev] [PATCH v2 2/7] move ini parser into common

2015-09-14 Thread Andrew Gregory
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 => 12 src/pacman/ini.c
 mode change 100644 => 12 src/pacman/ini.h

diff --git a/src/common/ini.c b/src/common/ini.c
new file mode 100644
index 000..fed0b18
--- /dev/null
+++ b/src/common/ini.c
@@ -0,0 +1,116 @@
+/*
+ *  ini.c
+ *
+ *  Copyright (c) 2013-2015 Pacman Development Team 
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see .
+ */
+
+#include 
+#include 
+#include  /* strdup */
+
+#include 
+
+#include "ini.h"
+#include "util.h"
+
+/**
+ * @brief Parse a pacman-style INI config file.
+ *
+ * @param file path to the config file
+ * @param cb callback for key/value pairs
+ * @param data caller defined data to be passed to the callback
+ *
+ * @return the callback return value
+ *
+ * @note The callback will be called at the beginning of each section with an
+ * empty key and value and for each key/value pair.
+ *
+ * @note If the parser encounters an error the callback will be called with
+ * section, key, and value set to NULL and errno set by fopen, fgets, or
+ * strdup.
+ *
+ * @note The @a key and @a value passed to @ cb will be overwritten between
+ * calls.  The section name will remain valid until after @a cb is called to
+ * begin a new section.
+ *
+ * @note Parsing will immediately stop if the callback returns non-zero.
+ */
+int parse_ini(const char *file, ini_parser_fn cb, void *data)
+{
+   char line[PATH_MAX], *section_name = NULL;
+   FILE *fp = NULL;
+   int linenum = 0;
+   int ret = 0;
+
+   fp = fopen(file, "r");
+   if(fp == NULL) {
+   return cb(file, 0, NULL, NULL, NULL, data);
+   }
+
+   while(safe_fgets(line, PATH_MAX, fp)) {
+   char *key, *value, *ptr;
+   size_t line_len;
+
+   linenum++;
+
+   /* ignore whole line and end of line comments */
+   if((ptr = strchr(line, '#'))) {
+   *ptr = '\0';
+   }
+
+   line_len = strtrim(line);
+
+   if(line_len == 0) {
+   continue;
+   }
+
+   if(line[0] == '[' && line[line_len - 1] == ']') {
+   char *name;
+   /* new config section, skip the '[' */
+   name = strdup(line + 1);
+   name[line_len - 2] = '\0';
+
+   ret = cb(file, linenum, name, NULL, NULL, data);
+   free(section_name);
+   section_name = name;
+
+   /* we're at a new section; perform any post-actions for 
the prior */
+   if(ret) {
+   goto cleanup;
+   }
+   continue;
+   }
+
+   /* directive */
+   /* strsep modifies the 'line' string: 'key \0 value' */
+   key = line;
+   value = line;
+   strsep(&value, "=");
+   strtrim(key);
+   strtrim(value);
+
+   if((ret = cb(file, linenum, section_name, key, value, data)) != 
0) {
+   goto cleanup;
+   }
+   }
+
+cleanup:
+   fclose(fp);
+   free(section_name);
+   return ret;
+}
+
+/* vim: set noet: */
diff --git a/src/common/ini.h b/src/common/ini.h
new file mode 100644
index 000..e85a505
--- /dev/null
+++ b/src/common/ini.h
@@ -0,0 +1,30 @@
+/*
+ *  ini.h
+ *
+ *  Copyright (c) 2013-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 Ge

[pacman-dev] [PATCH v2 3/7] check fileconflicts and diskspace outside commit

2015-09-14 Thread Andrew Gregory
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(-)

diff --git a/lib/libalpm/sync.c b/lib/libalpm/sync.c
index e843b07..c5607bc 100644
--- a/lib/libalpm/sync.c
+++ b/lib/libalpm/sync.c
@@ -1338,7 +1338,7 @@ int _alpm_sync_load(alpm_handle_t *handle, alpm_list_t 
**data)
return 0;
 }
 
-int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data)
+int _alpm_sync_check(alpm_handle_t *handle, alpm_list_t **data)
 {
alpm_trans_t *trans = handle->trans;
alpm_event_t event;
@@ -1355,7 +1355,8 @@ int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t 
**data)
if(data) {
*data = conflict;
} else {
-   alpm_list_free_inner(conflict, 
(alpm_list_fn_free)alpm_fileconflict_free);
+   alpm_list_free_inner(conflict,
+   
(alpm_list_fn_free)alpm_fileconflict_free);
alpm_list_free(conflict);
}
RET_ERR(handle, ALPM_ERR_FILE_CONFLICTS, -1);
@@ -1380,12 +1381,21 @@ int _alpm_sync_commit(alpm_handle_t *handle, 
alpm_list_t **data)
EVENT(handle, &event);
}
 
+   return 0;
+}
+
+int _alpm_sync_commit(alpm_handle_t *handle)
+{
+   alpm_trans_t *trans = handle->trans;
+
/* remove conflicting and to-be-replaced packages */
if(trans->remove) {
-   _alpm_log(handle, ALPM_LOG_DEBUG, "removing conflicting and 
to-be-replaced packages\n");
+   _alpm_log(handle, ALPM_LOG_DEBUG,
+   "removing conflicting and to-be-replaced 
packages\n");
/* we want the frontend to be aware of commit details */
if(_alpm_remove_packages(handle, 0) == -1) {
-   _alpm_log(handle, ALPM_LOG_ERROR, _("could not commit 
removal transaction\n"));
+   _alpm_log(handle, ALPM_LOG_ERROR,
+   _("could not commit removal 
transaction\n"));
return -1;
}
}
diff --git a/lib/libalpm/sync.h b/lib/libalpm/sync.h
index 6281550..60ebb75 100644
--- a/lib/libalpm/sync.h
+++ b/lib/libalpm/sync.h
@@ -26,7 +26,8 @@
 
 int _alpm_sync_prepare(alpm_handle_t *handle, alpm_list_t **data);
 int _alpm_sync_load(alpm_handle_t *handle, alpm_list_t **data);
-int _alpm_sync_commit(alpm_handle_t *handle, alpm_list_t **data);
+int _alpm_sync_check(alpm_handle_t *handle, alpm_list_t **data);
+int _alpm_sync_commit(alpm_handle_t *handle);
 
 #endif /* _ALPM_SYNC_H */
 
diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c
index 6a26e75..ed073c0 100644
--- a/lib/libalpm/trans.c
+++ b/lib/libalpm/trans.c
@@ -183,6 +183,10 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, 
alpm_list_t **data)
if(trans->flags & ALPM_TRANS_FLAG_DOWNLOADONLY) {
return 0;
}
+   if(_alpm_sync_check(handle, data) != 0) {
+   /* pm_errno is set by _alpm_sync_check() */
+   return -1;
+   }
}
 
trans->state = STATE_COMMITING;
@@ -198,7 +202,7 @@ int SYMEXPORT alpm_trans_commit(alpm_handle_t *handle, 
alpm_list_t **data)
return -1;
}
} else {
-   if(_alpm_sync_commit(handle, data) == -1) {
+   if(_alpm_sync_commit(handle) == -1) {
/* pm_errno is set by _alpm_sync_commit() */
alpm_errno_t save = handle->pm_errno;
alpm_logaction(handle, ALPM_CALLER_PREFIX, "transaction 
failed\n");
-- 
2.5.2


[pacman-dev] [PATCH v2 0/7][WIP] hooks: the sequel

2015-09-14 Thread Andrew Gregory
= 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 => 12 src/pacman/ini.c
 mode change 100644 => 12 src/pacman/ini.h

-- 
2.5.2