public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/sandbox:master commit in: libsbutil/, libsbutil/gnulib/, /
@ 2015-09-20  8:15 Mike Frysinger
  0 siblings, 0 replies; only message in thread
From: Mike Frysinger @ 2015-09-20  8:15 UTC (permalink / raw
  To: gentoo-commits

commit:     105b7e047e98e8f9211a30133d0cc1cb97aef9b0
Author:     Mike Frysinger <vapier <AT> gentoo <DOT> org>
AuthorDate: Sun Sep 20 07:03:30 2015 +0000
Commit:     Mike Frysinger <vapier <AT> gentoo <DOT> org>
CommitDate: Sun Sep 20 07:03:30 2015 +0000
URL:        https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=105b7e04

libsbutil: gnulib: import modules for canonicalize_filename_mode

This lays the groundwork for fixing handling of broken symlinks.  The
gnulib code is hand imported because using the gnulib tool imports a
ton of code we do not want.  Only the bare minimum is imported so we
can use the canonicalize_filename_mode function.

This function is needed to canonicalize symlinks that are ultimately
broken.  The current sandbox/C library code only supports two modes:
(1) dereference a single symlink
(2) dereference *all* symlinks, but only if all links are valid

For sandbox, we need to know the final path a symlink points to even
if that path doesn't (yet) exist.

Note: This commit doesn't actually fix the bug, just brings in the
functions we need to do so.

URL: https://bugs.gentoo.org/540828
Reported-by: Rick Farina <zerochaos <AT> gentoo.org>
Signed-off-by: Mike Frysinger <vapier <AT> gentoo.org>

 configure.ac                           |    5 +-
 headers.h                              |    2 +
 libsbutil/Makefile.am                  |   24 +
 libsbutil/gnulib/areadlink-with-size.c |  104 +++
 libsbutil/gnulib/areadlink.h           |   33 +
 libsbutil/gnulib/bitrotate.c           |    3 +
 libsbutil/gnulib/bitrotate.h           |  136 ++++
 libsbutil/gnulib/canonicalize.c        |  354 +++++++++
 libsbutil/gnulib/canonicalize.h        |   48 ++
 libsbutil/gnulib/careadlinkat.h        |   67 ++
 libsbutil/gnulib/dosname.h             |   53 ++
 libsbutil/gnulib/file-set.c            |   74 ++
 libsbutil/gnulib/file-set.h            |   15 +
 libsbutil/gnulib/gl-inline.h           |   92 +++
 libsbutil/gnulib/glue.h                |   10 +
 libsbutil/gnulib/hash-pjw.c            |   40 ++
 libsbutil/gnulib/hash-pjw.h            |   23 +
 libsbutil/gnulib/hash-triple.c         |   77 ++
 libsbutil/gnulib/hash-triple.h         |   24 +
 libsbutil/gnulib/hash.c                | 1225 ++++++++++++++++++++++++++++++++
 libsbutil/gnulib/hash.h                |  103 +++
 libsbutil/gnulib/pathmax.h             |   83 +++
 libsbutil/gnulib/same-inode.h          |   33 +
 libsbutil/gnulib/same.h                |   25 +
 libsbutil/gnulib/xalloc-oversized.h    |   38 +
 libsbutil/gnulib/xalloc.h              |    1 +
 libsbutil/gnulib/xgetcwd.h             |   21 +
 libsbutil/sbutil.h                     |    3 +
 28 files changed, 2715 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 73227db..e705d41 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ([2.61])
 AC_INIT([sandbox], [2.8], [sandbox@gentoo.org])
-AM_INIT_AUTOMAKE([1.12 dist-xz no-dist-gzip silent-rules -Wall])
+AM_INIT_AUTOMAKE([1.12 dist-xz no-dist-gzip silent-rules subdir-objects -Wall])
 AM_SILENT_RULES([yes]) # AM_INIT_AUTOMAKE([silent-rules]) is broken atm
 AC_CONFIG_HEADER([config.h])
 AC_CONFIG_MACRO_DIR([m4])
@@ -417,6 +417,9 @@ if test "x${LDFLAG_VER}" = "x" ; then
 fi
 AC_SUBST([LDFLAG_VER])
 
+dnl Add some glue for gnulib modules that include config.h directly.
+AH_BOTTOM([#include "headers.h"])
+
 AC_CONFIG_TESTDIR([tests])
 
 AC_CONFIG_FILES([src/sandbox.sh], [chmod +x src/sandbox.sh])

diff --git a/headers.h b/headers.h
index 42b7c25..1dc140e 100644
--- a/headers.h
+++ b/headers.h
@@ -146,4 +146,6 @@
 # include "localdecls.h"
 #endif
 
+#include "libsbutil/gnulib/glue.h"
+
 #endif

diff --git a/libsbutil/Makefile.am b/libsbutil/Makefile.am
index 39a5ab6..0c41500 100644
--- a/libsbutil/Makefile.am
+++ b/libsbutil/Makefile.am
@@ -42,6 +42,30 @@ libsbutil_la_SOURCES =                        \
 	src/config.c                          \
 	include/rcscripts/util/dynbuf.h       \
 	src/dynbuf.c                          \
+	gnulib/areadlink.h                    \
+	gnulib/areadlink-with-size.c          \
+	gnulib/bitrotate.c                    \
+	gnulib/bitrotate.h                    \
+	gnulib/canonicalize.c                 \
+	gnulib/canonicalize.h                 \
+	gnulib/careadlinkat.h                 \
+	gnulib/dosname.h                      \
+	gnulib/file-set.c                     \
+	gnulib/file-set.h                     \
+	gnulib/gl-inline.h                    \
+	gnulib/glue.h                         \
+	gnulib/hash.c                         \
+	gnulib/hash.h                         \
+	gnulib/hash-pjw.c                     \
+	gnulib/hash-pjw.h                     \
+	gnulib/hash-triple.c                  \
+	gnulib/hash-triple.h                  \
+	gnulib/pathmax.h                      \
+	gnulib/same.h                         \
+	gnulib/same-inode.h                   \
+	gnulib/xalloc.h                       \
+	gnulib/xalloc-oversized.h             \
+	gnulib/xgetcwd.h                      \
 	$(LOCAL_INCLUDES)
 
 EXTRA_DIST = headers.h

diff --git a/libsbutil/gnulib/areadlink-with-size.c b/libsbutil/gnulib/areadlink-with-size.c
new file mode 100644
index 0000000..e3a8c50
--- /dev/null
+++ b/libsbutil/gnulib/areadlink-with-size.c
@@ -0,0 +1,104 @@
+/* readlink wrapper to return the link name in malloc'd storage.
+   Unlike xreadlink and xreadlink_with_size, don't ever call exit.
+
+   Copyright (C) 2001, 2003-2007, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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/>.  */
+
+/* Written by Jim Meyering <jim@meyering.net>  */
+
+#include <config.h>
+
+#include "areadlink.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+/* SYMLINK_MAX is used only for an initial memory-allocation sanity
+   check, so it's OK to guess too small on hosts where there is no
+   arbitrary limit to symbolic link length.  */
+#ifndef SYMLINK_MAX
+# define SYMLINK_MAX 1024
+#endif
+
+#define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
+
+/* Call readlink to get the symbolic link value of FILE.
+   SIZE is a hint as to how long the link is expected to be;
+   typically it is taken from st_size.  It need not be correct.
+   Return a pointer to that NUL-terminated string in malloc'd storage.
+   If readlink fails, malloc fails, or if the link value is longer
+   than SSIZE_MAX, return NULL (caller may use errno to diagnose).  */
+
+char *
+areadlink_with_size (char const *file, size_t size)
+{
+  /* Some buggy file systems report garbage in st_size.  Defend
+     against them by ignoring outlandish st_size values in the initial
+     memory allocation.  */
+  size_t symlink_max = SYMLINK_MAX;
+  size_t INITIAL_LIMIT_BOUND = 8 * 1024;
+  size_t initial_limit = (symlink_max < INITIAL_LIMIT_BOUND
+                          ? symlink_max + 1
+                          : INITIAL_LIMIT_BOUND);
+
+  /* The initial buffer size for the link value.  */
+  size_t buf_size = size < initial_limit ? size + 1 : initial_limit;
+
+  while (1)
+    {
+      ssize_t r;
+      size_t link_length;
+      char *buffer = malloc (buf_size);
+
+      if (buffer == NULL)
+        return NULL;
+      r = readlink (file, buffer, buf_size);
+      link_length = r;
+
+      /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
+         with errno == ERANGE if the buffer is too small.  */
+      if (r < 0 && errno != ERANGE)
+        {
+          int saved_errno = errno;
+          free (buffer);
+          errno = saved_errno;
+          return NULL;
+        }
+
+      if (link_length < buf_size)
+        {
+          buffer[link_length] = 0;
+          return buffer;
+        }
+
+      free (buffer);
+      if (buf_size <= MAXSIZE / 2)
+        buf_size *= 2;
+      else if (buf_size < MAXSIZE)
+        buf_size = MAXSIZE;
+      else
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+    }
+}

diff --git a/libsbutil/gnulib/areadlink.h b/libsbutil/gnulib/areadlink.h
new file mode 100644
index 0000000..d9e0fa1
--- /dev/null
+++ b/libsbutil/gnulib/areadlink.h
@@ -0,0 +1,33 @@
+/* Read symbolic links without size limitation.
+
+   Copyright (C) 2001, 2003-2004, 2007, 2009-2015 Free Software Foundation,
+   Inc.
+
+   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 3 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/>.  */
+
+/* Written by Jim Meyering <jim@meyering.net>  */
+
+#include <stddef.h>
+
+extern char *areadlink (char const *filename);
+extern char *areadlink_with_size (char const *filename, size_t size_hint);
+
+#if GNULIB_AREADLINKAT
+extern char *areadlinkat (int fd, char const *filename);
+#endif
+
+#if GNULIB_AREADLINKAT_WITH_SIZE
+extern char *areadlinkat_with_size (int fd, char const *filename,
+                                    size_t size_hint);
+#endif

diff --git a/libsbutil/gnulib/bitrotate.c b/libsbutil/gnulib/bitrotate.c
new file mode 100644
index 0000000..a8f6028
--- /dev/null
+++ b/libsbutil/gnulib/bitrotate.c
@@ -0,0 +1,3 @@
+#include <config.h>
+#define BITROTATE_INLINE _GL_EXTERN_INLINE
+#include "bitrotate.h"

diff --git a/libsbutil/gnulib/bitrotate.h b/libsbutil/gnulib/bitrotate.h
new file mode 100644
index 0000000..1665e99
--- /dev/null
+++ b/libsbutil/gnulib/bitrotate.h
@@ -0,0 +1,136 @@
+/* bitrotate.h - Rotate bits in integers
+   Copyright (C) 2008-2015 Free Software Foundation, Inc.
+
+   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 3 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/>.  */
+
+/* Written by Simon Josefsson <simon@josefsson.org>, 2008. */
+
+#ifndef _GL_BITROTATE_H
+#define _GL_BITROTATE_H
+
+#include <limits.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifndef _GL_INLINE_HEADER_BEGIN
+ #error "Please include config.h first."
+#endif
+_GL_INLINE_HEADER_BEGIN
+#ifndef BITROTATE_INLINE
+# define BITROTATE_INLINE _GL_INLINE
+#endif
+
+#ifdef UINT64_MAX
+/* Given an unsigned 64-bit argument X, return the value corresponding
+   to rotating the bits N steps to the left.  N must be between 1 and
+   63 inclusive. */
+BITROTATE_INLINE uint64_t
+rotl64 (uint64_t x, int n)
+{
+  return ((x << n) | (x >> (64 - n))) & UINT64_MAX;
+}
+
+/* Given an unsigned 64-bit argument X, return the value corresponding
+   to rotating the bits N steps to the right.  N must be between 1 to
+   63 inclusive.*/
+BITROTATE_INLINE uint64_t
+rotr64 (uint64_t x, int n)
+{
+  return ((x >> n) | (x << (64 - n))) & UINT64_MAX;
+}
+#endif
+
+/* Given an unsigned 32-bit argument X, return the value corresponding
+   to rotating the bits N steps to the left.  N must be between 1 and
+   31 inclusive. */
+BITROTATE_INLINE uint32_t
+rotl32 (uint32_t x, int n)
+{
+  return ((x << n) | (x >> (32 - n))) & UINT32_MAX;
+}
+
+/* Given an unsigned 32-bit argument X, return the value corresponding
+   to rotating the bits N steps to the right.  N must be between 1 to
+   31 inclusive.*/
+BITROTATE_INLINE uint32_t
+rotr32 (uint32_t x, int n)
+{
+  return ((x >> n) | (x << (32 - n))) & UINT32_MAX;
+}
+
+/* Given a size_t argument X, return the value corresponding
+   to rotating the bits N steps to the left.  N must be between 1 and
+   (CHAR_BIT * sizeof (size_t) - 1) inclusive.  */
+BITROTATE_INLINE size_t
+rotl_sz (size_t x, int n)
+{
+  return ((x << n) | (x >> ((CHAR_BIT * sizeof x) - n))) & SIZE_MAX;
+}
+
+/* Given a size_t argument X, return the value corresponding
+   to rotating the bits N steps to the right.  N must be between 1 to
+   (CHAR_BIT * sizeof (size_t) - 1) inclusive.  */
+BITROTATE_INLINE size_t
+rotr_sz (size_t x, int n)
+{
+  return ((x >> n) | (x << ((CHAR_BIT * sizeof x) - n))) & SIZE_MAX;
+}
+
+/* Given an unsigned 16-bit argument X, return the value corresponding
+   to rotating the bits N steps to the left.  N must be between 1 to
+   15 inclusive, but on most relevant targets N can also be 0 and 16
+   because 'int' is at least 32 bits and the arguments must widen
+   before shifting. */
+BITROTATE_INLINE uint16_t
+rotl16 (uint16_t x, int n)
+{
+  return ((x << n) | (x >> (16 - n))) & UINT16_MAX;
+}
+
+/* Given an unsigned 16-bit argument X, return the value corresponding
+   to rotating the bits N steps to the right.  N must be in 1 to 15
+   inclusive, but on most relevant targets N can also be 0 and 16
+   because 'int' is at least 32 bits and the arguments must widen
+   before shifting. */
+BITROTATE_INLINE uint16_t
+rotr16 (uint16_t x, int n)
+{
+  return ((x >> n) | (x << (16 - n))) & UINT16_MAX;
+}
+
+/* Given an unsigned 8-bit argument X, return the value corresponding
+   to rotating the bits N steps to the left.  N must be between 1 to 7
+   inclusive, but on most relevant targets N can also be 0 and 8
+   because 'int' is at least 32 bits and the arguments must widen
+   before shifting. */
+BITROTATE_INLINE uint8_t
+rotl8 (uint8_t x, int n)
+{
+  return ((x << n) | (x >> (8 - n))) & UINT8_MAX;
+}
+
+/* Given an unsigned 8-bit argument X, return the value corresponding
+   to rotating the bits N steps to the right.  N must be in 1 to 7
+   inclusive, but on most relevant targets N can also be 0 and 8
+   because 'int' is at least 32 bits and the arguments must widen
+   before shifting. */
+BITROTATE_INLINE uint8_t
+rotr8 (uint8_t x, int n)
+{
+  return ((x >> n) | (x << (8 - n))) & UINT8_MAX;
+}
+
+_GL_INLINE_HEADER_END
+
+#endif /* _GL_BITROTATE_H */

diff --git a/libsbutil/gnulib/canonicalize.c b/libsbutil/gnulib/canonicalize.c
new file mode 100644
index 0000000..397ac76
--- /dev/null
+++ b/libsbutil/gnulib/canonicalize.c
@@ -0,0 +1,354 @@
+/* Return the canonical absolute name of a given file.
+   Copyright (C) 1996-2015 Free Software Foundation, Inc.
+
+   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 3 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 <config.h>
+
+#include "canonicalize.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "areadlink.h"
+#include "file-set.h"
+#include "hash-triple.h"
+#include "pathmax.h"
+#include "xalloc.h"
+#include "xgetcwd.h"
+#include "dosname.h"
+
+#define MULTIPLE_BITS_SET(i) (((i) & ((i) - 1)) != 0)
+
+/* In this file, we cannot handle file names longer than PATH_MAX.
+   On systems with no file name length limit, use a fallback.  */
+#ifndef PATH_MAX
+# define PATH_MAX 8192
+#endif
+
+#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+#endif
+
+#if ISSLASH ('\\')
+# define SLASHES "/\\"
+#else
+# define SLASHES "/"
+#endif
+
+#if !((HAVE_CANONICALIZE_FILE_NAME && FUNC_REALPATH_WORKS)      \
+      || GNULIB_CANONICALIZE_LGPL)
+/* Return the canonical absolute name of file NAME.  A canonical name
+   does not contain any ".", ".." components nor any repeated file name
+   separators ('/') or symlinks.  All components must exist.
+   The result is malloc'd.  */
+
+char *
+canonicalize_file_name (const char *name)
+{
+  return canonicalize_filename_mode (name, CAN_EXISTING);
+}
+#endif /* !HAVE_CANONICALIZE_FILE_NAME */
+
+/* Return true if we've already seen the triple, <FILENAME, dev, ino>.
+   If *HT is not initialized, initialize it.  */
+static bool
+seen_triple (Hash_table **ht, char const *filename, struct stat const *st)
+{
+  if (*ht == NULL)
+    {
+      size_t initial_capacity = 7;
+      *ht = hash_initialize (initial_capacity,
+                            NULL,
+                            triple_hash,
+                            triple_compare_ino_str,
+                            triple_free);
+      if (*ht == NULL)
+        xalloc_die ();
+    }
+
+  if (seen_file (*ht, filename, st))
+    return true;
+
+  record_file (*ht, filename, st);
+  return false;
+}
+
+/* Return the canonical absolute name of file NAME, while treating
+   missing elements according to CAN_MODE.  A canonical name
+   does not contain any ".", ".." components nor any repeated file name
+   separators ('/') or, depending on other CAN_MODE flags, symlinks.
+   Whether components must exist or not depends on canonicalize mode.
+   The result is malloc'd.  */
+
+char *
+canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
+{
+  char *rname, *dest, *extra_buf = NULL;
+  char const *start;
+  char const *end;
+  char const *rname_limit;
+  size_t extra_len = 0;
+  Hash_table *ht = NULL;
+  int saved_errno;
+  int can_flags = can_mode & ~CAN_MODE_MASK;
+  bool logical = can_flags & CAN_NOLINKS;
+  size_t prefix_len;
+
+  can_mode &= CAN_MODE_MASK;
+
+  if (MULTIPLE_BITS_SET (can_mode))
+    {
+      errno = EINVAL;
+      return NULL;
+    }
+
+  if (name == NULL)
+    {
+      errno = EINVAL;
+      return NULL;
+    }
+
+  if (name[0] == '\0')
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* This is always zero for Posix hosts, but can be 2 for MS-Windows
+     and MS-DOS X:/foo/bar file names.  */
+  prefix_len = FILE_SYSTEM_PREFIX_LEN (name);
+
+  if (!IS_ABSOLUTE_FILE_NAME (name))
+    {
+      rname = xgetcwd ();
+      if (!rname)
+        return NULL;
+      dest = strchr (rname, '\0');
+      if (dest - rname < PATH_MAX)
+        {
+          char *p = xrealloc (rname, PATH_MAX);
+          dest = p + (dest - rname);
+          rname = p;
+          rname_limit = rname + PATH_MAX;
+        }
+      else
+        {
+          rname_limit = dest;
+        }
+      start = name;
+      prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
+    }
+  else
+    {
+      rname = xmalloc (PATH_MAX);
+      rname_limit = rname + PATH_MAX;
+      dest = rname;
+      if (prefix_len)
+        {
+          memcpy (rname, name, prefix_len);
+          dest += prefix_len;
+        }
+      *dest++ = '/';
+      if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+        {
+          if (ISSLASH (name[1]) && !ISSLASH (name[2]) && !prefix_len)
+            *dest++ = '/';
+          *dest = '\0';
+        }
+      start = name + prefix_len;
+    }
+
+  for ( ; *start; start = end)
+    {
+      /* Skip sequence of multiple file name separators.  */
+      while (ISSLASH (*start))
+        ++start;
+
+      /* Find end of component.  */
+      for (end = start; *end && !ISSLASH (*end); ++end)
+        /* Nothing.  */;
+
+      if (end - start == 0)
+        break;
+      else if (end - start == 1 && start[0] == '.')
+        /* nothing */;
+      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
+        {
+          /* Back up to previous component, ignore if at root already.  */
+          if (dest > rname + prefix_len + 1)
+            for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
+              continue;
+          if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+              && !prefix_len && ISSLASH (*dest) && !ISSLASH (dest[1]))
+            dest++;
+        }
+      else
+        {
+          struct stat st;
+
+          if (!ISSLASH (dest[-1]))
+            *dest++ = '/';
+
+          if (dest + (end - start) >= rname_limit)
+            {
+              ptrdiff_t dest_offset = dest - rname;
+              size_t new_size = rname_limit - rname;
+
+              if (end - start + 1 > PATH_MAX)
+                new_size += end - start + 1;
+              else
+                new_size += PATH_MAX;
+              rname = xrealloc (rname, new_size);
+              rname_limit = rname + new_size;
+
+              dest = rname + dest_offset;
+            }
+
+          dest = memcpy (dest, start, end - start);
+          dest += end - start;
+          *dest = '\0';
+
+          if (logical && (can_mode == CAN_MISSING))
+            {
+              /* Avoid the stat in this case as it's inconsequential.
+                 i.e. we're neither resolving symlinks or testing
+                 component existence.  */
+              st.st_mode = 0;
+            }
+          else if ((logical ? stat (rname, &st) : lstat (rname, &st)) != 0)
+            {
+              saved_errno = errno;
+              if (can_mode == CAN_EXISTING)
+                goto error;
+              if (can_mode == CAN_ALL_BUT_LAST)
+                {
+                  if (end[strspn (end, SLASHES)] || saved_errno != ENOENT)
+                    goto error;
+                  continue;
+                }
+              st.st_mode = 0;
+            }
+
+          if (S_ISLNK (st.st_mode))
+            {
+              char *buf;
+              size_t n, len;
+
+              /* Detect loops.  We cannot use the cycle-check module here,
+                 since it's actually possible to encounter the same symlink
+                 more than once in a given traversal.  However, encountering
+                 the same symlink,NAME pair twice does indicate a loop.  */
+              if (seen_triple (&ht, name, &st))
+                {
+                  if (can_mode == CAN_MISSING)
+                    continue;
+                  saved_errno = ELOOP;
+                  goto error;
+                }
+
+              buf = areadlink_with_size (rname, st.st_size);
+              if (!buf)
+                {
+                  if (can_mode == CAN_MISSING && errno != ENOMEM)
+                    continue;
+                  saved_errno = errno;
+                  goto error;
+                }
+
+              n = strlen (buf);
+              len = strlen (end);
+
+              if (!extra_len)
+                {
+                  extra_len =
+                    ((n + len + 1) > PATH_MAX) ? (n + len + 1) : PATH_MAX;
+                  extra_buf = xmalloc (extra_len);
+                }
+              else if ((n + len + 1) > extra_len)
+                {
+                  extra_len = n + len + 1;
+                  extra_buf = xrealloc (extra_buf, extra_len);
+                }
+
+              /* Careful here, end may be a pointer into extra_buf... */
+              memmove (&extra_buf[n], end, len + 1);
+              name = end = memcpy (extra_buf, buf, n);
+
+              if (IS_ABSOLUTE_FILE_NAME (buf))
+                {
+                  size_t pfxlen = FILE_SYSTEM_PREFIX_LEN (buf);
+
+                  if (pfxlen)
+                    memcpy (rname, buf, pfxlen);
+                  dest = rname + pfxlen;
+                  *dest++ = '/'; /* It's an absolute symlink */
+                  if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+                    {
+                      if (ISSLASH (buf[1]) && !ISSLASH (buf[2]) && !pfxlen)
+                        *dest++ = '/';
+                      *dest = '\0';
+                    }
+                  /* Install the new prefix to be in effect hereafter.  */
+                  prefix_len = pfxlen;
+                }
+              else
+                {
+                  /* Back up to previous component, ignore if at root
+                     already: */
+                  if (dest > rname + prefix_len + 1)
+                    for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
+                      continue;
+                  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+                      && ISSLASH (*dest) && !ISSLASH (dest[1]) && !prefix_len)
+                    dest++;
+                }
+
+              free (buf);
+            }
+          else
+            {
+              if (!S_ISDIR (st.st_mode) && *end && (can_mode != CAN_MISSING))
+                {
+                  saved_errno = ENOTDIR;
+                  goto error;
+                }
+            }
+        }
+    }
+  if (dest > rname + prefix_len + 1 && ISSLASH (dest[-1]))
+    --dest;
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && !prefix_len
+      && ISSLASH (*dest) && !ISSLASH (dest[1]))
+    dest++;
+  *dest = '\0';
+  if (rname_limit != dest + 1)
+    rname = xrealloc (rname, dest - rname + 1);
+
+  free (extra_buf);
+  if (ht)
+    hash_free (ht);
+  return rname;
+
+error:
+  free (extra_buf);
+  free (rname);
+  if (ht)
+    hash_free (ht);
+  errno = saved_errno;
+  return NULL;
+}

diff --git a/libsbutil/gnulib/canonicalize.h b/libsbutil/gnulib/canonicalize.h
new file mode 100644
index 0000000..236cba5
--- /dev/null
+++ b/libsbutil/gnulib/canonicalize.h
@@ -0,0 +1,48 @@
+/* Return the canonical absolute name of a given file.
+   Copyright (C) 1996-2007, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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 CANONICALIZE_H_
+# define CANONICALIZE_H_
+
+#include <stdlib.h> /* for canonicalize_file_name */
+
+#define CAN_MODE_MASK (CAN_EXISTING | CAN_ALL_BUT_LAST | CAN_MISSING)
+
+enum canonicalize_mode_t
+  {
+    /* All components must exist.  */
+    CAN_EXISTING = 0,
+
+    /* All components excluding last one must exist.  */
+    CAN_ALL_BUT_LAST = 1,
+
+    /* No requirements on components existence.  */
+    CAN_MISSING = 2,
+
+    /* Don't expand symlinks.  */
+    CAN_NOLINKS = 4
+  };
+typedef enum canonicalize_mode_t canonicalize_mode_t;
+
+/* Return the canonical absolute name of file NAME, while treating
+   missing elements according to CAN_MODE.  A canonical name
+   does not contain any `.', `..' components nor any repeated file name
+   separators ('/') or, depending on other CAN_MODE flags, symlinks.
+   Whether components must exist or not depends on canonicalize mode.
+   The result is malloc'd.  */
+char *canonicalize_filename_mode (const char *, canonicalize_mode_t);
+
+#endif /* !CANONICALIZE_H_ */

diff --git a/libsbutil/gnulib/careadlinkat.h b/libsbutil/gnulib/careadlinkat.h
new file mode 100644
index 0000000..4eb9fcc
--- /dev/null
+++ b/libsbutil/gnulib/careadlinkat.h
@@ -0,0 +1,67 @@
+/* Read symbolic links into a buffer without size limitation, relative to fd.
+
+   Copyright (C) 2011-2015 Free Software Foundation, Inc.
+
+   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 3 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/>.  */
+
+/* Written by Paul Eggert, Bruno Haible, and Jim Meyering.  */
+
+#ifndef _GL_CAREADLINKAT_H
+#define _GL_CAREADLINKAT_H
+
+#include <fcntl.h>
+#include <unistd.h>
+
+struct allocator;
+
+/* Assuming the current directory is FD, get the symbolic link value
+   of FILENAME as a null-terminated string and put it into a buffer.
+   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
+   working directory, as in openat.
+
+   If the link is small enough to fit into BUFFER put it there.
+   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
+   if BUFFER_SIZE is zero.
+
+   If the link is not small, put it into a dynamically allocated
+   buffer managed by ALLOC.  It is the caller's responsibility to free
+   the returned value if it is nonnull and is not BUFFER.
+
+   The PREADLINKAT function specifies how to read links.  It operates
+   like POSIX readlinkat()
+   <http://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html>
+   but can assume that its first argument is the same as FD.
+
+   If successful, return the buffer address; otherwise return NULL and
+   set errno.  */
+
+char *careadlinkat (int fd, char const *filename,
+                    char *buffer, size_t buffer_size,
+                    struct allocator const *alloc,
+                    ssize_t (*preadlinkat) (int, char const *,
+                                            char *, size_t));
+
+/* Suitable value for careadlinkat's FD argument.  */
+#if HAVE_READLINKAT
+/* AT_FDCWD is declared in <fcntl.h>.  */
+#else
+/* Define AT_FDCWD independently, so that the careadlinkat module does
+   not depend on the fcntl-h module.  We might as well use the same value
+   as fcntl-h.  */
+# ifndef AT_FDCWD
+#  define AT_FDCWD (-3041965)
+# endif
+#endif
+
+#endif /* _GL_CAREADLINKAT_H */

diff --git a/libsbutil/gnulib/dosname.h b/libsbutil/gnulib/dosname.h
new file mode 100644
index 0000000..893baf6
--- /dev/null
+++ b/libsbutil/gnulib/dosname.h
@@ -0,0 +1,53 @@
+/* File names on MS-DOS/Windows systems.
+
+   Copyright (C) 2000-2001, 2004-2006, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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/>.
+
+   From Paul Eggert and Jim Meyering.  */
+
+#ifndef _DOSNAME_H
+#define _DOSNAME_H
+
+#if (defined _WIN32 || defined __WIN32__ ||     \
+     defined __MSDOS__ || defined __CYGWIN__ || \
+     defined __EMX__ || defined __DJGPP__)
+   /* This internal macro assumes ASCII, but all hosts that support drive
+      letters use ASCII.  */
+# define _IS_DRIVE_LETTER(C) (((unsigned int) (C) | ('a' - 'A')) - 'a'  \
+                              <= 'z' - 'a')
+# define FILE_SYSTEM_PREFIX_LEN(Filename) \
+          (_IS_DRIVE_LETTER ((Filename)[0]) && (Filename)[1] == ':' ? 2 : 0)
+# ifndef __CYGWIN__
+#  define FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE 1
+# endif
+# define ISSLASH(C) ((C) == '/' || (C) == '\\')
+#else
+# define FILE_SYSTEM_PREFIX_LEN(Filename) 0
+# define ISSLASH(C) ((C) == '/')
+#endif
+
+#ifndef FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE
+# define FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE 0
+#endif
+
+#if FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE
+#  define IS_ABSOLUTE_FILE_NAME(F) ISSLASH ((F)[FILE_SYSTEM_PREFIX_LEN (F)])
+# else
+#  define IS_ABSOLUTE_FILE_NAME(F)                              \
+     (ISSLASH ((F)[0]) || FILE_SYSTEM_PREFIX_LEN (F) != 0)
+#endif
+#define IS_RELATIVE_FILE_NAME(F) (! IS_ABSOLUTE_FILE_NAME (F))
+
+#endif /* DOSNAME_H_ */

diff --git a/libsbutil/gnulib/file-set.c b/libsbutil/gnulib/file-set.c
new file mode 100644
index 0000000..1cf2f0c
--- /dev/null
+++ b/libsbutil/gnulib/file-set.c
@@ -0,0 +1,74 @@
+/* Specialized functions to manipulate a set of files.
+   Copyright (C) 2007, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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/>.  */
+
+/* written by Jim Meyering */
+
+#include <config.h>
+#include "file-set.h"
+
+#include "hash-triple.h"
+#include "xalloc.h"
+
+/* Record file, FILE, and dev/ino from *STATS, in the hash table, HT.
+   If HT is NULL, return immediately.
+   If memory allocation fails, exit immediately.  */
+void
+record_file (Hash_table *ht, char const *file, struct stat const *stats)
+{
+  struct F_triple *ent;
+
+  if (ht == NULL)
+    return;
+
+  ent = xmalloc (sizeof *ent);
+  ent->name = xstrdup (file);
+  ent->st_ino = stats->st_ino;
+  ent->st_dev = stats->st_dev;
+
+  {
+    struct F_triple *ent_from_table = hash_insert (ht, ent);
+    if (ent_from_table == NULL)
+      {
+        /* Insertion failed due to lack of memory.  */
+        xalloc_die ();
+      }
+
+    if (ent_from_table != ent)
+      {
+        /* There was alread a matching entry in the table, so ENT was
+           not inserted.  Free it.  */
+        triple_free (ent);
+      }
+  }
+}
+
+/* Return true if there is an entry in hash table, HT,
+   for the file described by FILE and STATS.  */
+bool
+seen_file (Hash_table const *ht, char const *file,
+           struct stat const *stats)
+{
+  struct F_triple new_ent;
+
+  if (ht == NULL)
+    return false;
+
+  new_ent.name = (char *) file;
+  new_ent.st_ino = stats->st_ino;
+  new_ent.st_dev = stats->st_dev;
+
+  return !!hash_lookup (ht, &new_ent);
+}

diff --git a/libsbutil/gnulib/file-set.h b/libsbutil/gnulib/file-set.h
new file mode 100644
index 0000000..4e47d95
--- /dev/null
+++ b/libsbutil/gnulib/file-set.h
@@ -0,0 +1,15 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+
+#include "hash.h"
+
+extern void record_file (Hash_table *ht, char const *file,
+                         struct stat const *stats)
+#if defined __GNUC__ && ((__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || __GNUC__ > 3)
+  __attribute__ ((nonnull (2, 3)))
+#endif
+;
+
+extern bool seen_file (Hash_table const *ht, char const *file,
+                       struct stat const *stats);

diff --git a/libsbutil/gnulib/gl-inline.h b/libsbutil/gnulib/gl-inline.h
new file mode 100644
index 0000000..bb8598e
--- /dev/null
+++ b/libsbutil/gnulib/gl-inline.h
@@ -0,0 +1,92 @@
+/* Expansion of gl_EXTERN_INLINE */
+
+/* Please see the Gnulib manual for how to use these macros.
+
+   Suppress extern inline with HP-UX cc, as it appears to be broken; see
+   <http://lists.gnu.org/archive/html/bug-texinfo/2013-02/msg00030.html>.
+
+   Suppress extern inline with Sun C in standards-conformance mode, as it
+   mishandles inline functions that call each other.  E.g., for 'inline void f
+   (void) { } inline void g (void) { f (); }', c99 incorrectly complains
+   'reference to static identifier "f" in extern inline function'.
+   This bug was observed with Sun C 5.12 SunOS_i386 2011/11/16.
+
+   Suppress extern inline (with or without __attribute__ ((__gnu_inline__)))
+   on configurations that mistakenly use 'static inline' to implement
+   functions or macros in standard C headers like <ctype.h>.  For example,
+   if isdigit is mistakenly implemented via a static inline function,
+   a program containing an extern inline function that calls isdigit
+   may not work since the C standard prohibits extern inline functions
+   from calling static functions.  This bug is known to occur on:
+
+     OS X 10.8 and earlier; see:
+     http://lists.gnu.org/archive/html/bug-gnulib/2012-12/msg00023.html
+
+     DragonFly; see
+     http://muscles.dragonflybsd.org/bulk/bleeding-edge-potential/latest-per-pkg/ah-tty-0.3.12.log
+
+     FreeBSD; see:
+     http://lists.gnu.org/archive/html/bug-gnulib/2014-07/msg00104.html
+
+   OS X 10.9 has a macro __header_inline indicating the bug is fixed for C and
+   for clang but remains for g++; see <http://trac.macports.org/ticket/41033>.
+   Assume DragonFly and FreeBSD will be similar.  */
+#if (((defined __APPLE__ && defined __MACH__) \
+      || defined __DragonFly__ || defined __FreeBSD__) \
+     && (defined __header_inline \
+         ? (defined __cplusplus && defined __GNUC_STDC_INLINE__ \
+            && ! defined __clang__) \
+         : ((! defined _DONT_USE_CTYPE_INLINE_ \
+             && (defined __GNUC__ || defined __cplusplus)) \
+            || (defined _FORTIFY_SOURCE && 0 < _FORTIFY_SOURCE \
+                && defined __GNUC__ && ! defined __cplusplus))))
+# define _GL_EXTERN_INLINE_STDHEADER_BUG
+#endif
+#if ((__GNUC__ \
+      ? defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__ \
+      : (199901L <= __STDC_VERSION__ \
+         && !defined __HP_cc \
+         && !(defined __SUNPRO_C && __STDC__))) \
+     && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
+# define _GL_INLINE inline
+# define _GL_EXTERN_INLINE extern inline
+# define _GL_EXTERN_INLINE_IN_USE
+#elif (2 < __GNUC__ + (7 <= __GNUC_MINOR__) && !defined __STRICT_ANSI__ \
+       && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
+# if defined __GNUC_GNU_INLINE__ && __GNUC_GNU_INLINE__
+   /* __gnu_inline__ suppresses a GCC 4.2 diagnostic.  */
+#  define _GL_INLINE extern inline __attribute__ ((__gnu_inline__))
+# else
+#  define _GL_INLINE extern inline
+# endif
+# define _GL_EXTERN_INLINE extern
+# define _GL_EXTERN_INLINE_IN_USE
+#else
+# define _GL_INLINE static _GL_UNUSED
+# define _GL_EXTERN_INLINE static _GL_UNUSED
+#endif
+
+/* In GCC 4.6 (inclusive) to 5.1 (exclusive),
+   suppress bogus "no previous prototype for 'FOO'"
+   and "no previous declaration for 'FOO'" diagnostics,
+   when FOO is an inline function in the header; see
+   <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54113> and
+   <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63877>.  */
+#if __GNUC__ == 4 && 6 <= __GNUC_MINOR__
+# if defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__
+#  define _GL_INLINE_HEADER_CONST_PRAGMA
+# else
+#  define _GL_INLINE_HEADER_CONST_PRAGMA \
+     _Pragma ("GCC diagnostic ignored \"-Wsuggest-attribute=const\"")
+# endif
+# define _GL_INLINE_HEADER_BEGIN \
+    _Pragma ("GCC diagnostic push") \
+    _Pragma ("GCC diagnostic ignored \"-Wmissing-prototypes\"") \
+    _Pragma ("GCC diagnostic ignored \"-Wmissing-declarations\"") \
+    _GL_INLINE_HEADER_CONST_PRAGMA
+# define _GL_INLINE_HEADER_END \
+    _Pragma ("GCC diagnostic pop")
+#else
+# define _GL_INLINE_HEADER_BEGIN
+# define _GL_INLINE_HEADER_END
+#endif

diff --git a/libsbutil/gnulib/glue.h b/libsbutil/gnulib/glue.h
new file mode 100644
index 0000000..083ab73
--- /dev/null
+++ b/libsbutil/gnulib/glue.h
@@ -0,0 +1,10 @@
+/* gnulib-specific glue logic that normally gnulib macros would set up.
+ *
+ * Copyright 1999-2015 Gentoo Foundation
+ * Licensed under the GPL-2
+ */
+
+#define _GL_ATTRIBUTE_CONST __attribute__ ((__const__))
+#define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
+
+#include "gl-inline.h"

diff --git a/libsbutil/gnulib/hash-pjw.c b/libsbutil/gnulib/hash-pjw.c
new file mode 100644
index 0000000..b2e0251
--- /dev/null
+++ b/libsbutil/gnulib/hash-pjw.c
@@ -0,0 +1,40 @@
+/* hash-pjw.c -- compute a hash value from a NUL-terminated string.
+
+   Copyright (C) 2001, 2003, 2006, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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 <config.h>
+
+#include "hash-pjw.h"
+
+#include <limits.h>
+
+#define SIZE_BITS (sizeof (size_t) * CHAR_BIT)
+
+/* A hash function for NUL-terminated char* strings using
+   the method described by Bruno Haible.
+   See http://www.haible.de/bruno/hashfunc.html.  */
+
+size_t
+hash_pjw (const void *x, size_t tablesize)
+{
+  const char *s;
+  size_t h = 0;
+
+  for (s = x; *s; s++)
+    h = *s + ((h << 9) | (h >> (SIZE_BITS - 9)));
+
+  return h % tablesize;
+}

diff --git a/libsbutil/gnulib/hash-pjw.h b/libsbutil/gnulib/hash-pjw.h
new file mode 100644
index 0000000..f4b005c
--- /dev/null
+++ b/libsbutil/gnulib/hash-pjw.h
@@ -0,0 +1,23 @@
+/* hash-pjw.h -- declaration for a simple hash function
+   Copyright (C) 2001, 2003, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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 <stddef.h>
+
+/* Compute a hash code for a NUL-terminated string starting at X,
+   and return the hash code modulo TABLESIZE.
+   The result is platform dependent: it depends on the size of the 'size_t'
+   type and on the signedness of the 'char' type.  */
+extern size_t hash_pjw (void const *x, size_t tablesize) _GL_ATTRIBUTE_PURE;

diff --git a/libsbutil/gnulib/hash-triple.c b/libsbutil/gnulib/hash-triple.c
new file mode 100644
index 0000000..c3b6d9f
--- /dev/null
+++ b/libsbutil/gnulib/hash-triple.c
@@ -0,0 +1,77 @@
+/* Hash functions for file-related triples: name, device, inode.
+   Copyright (C) 2007, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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/>.  */
+
+/* written by Jim Meyering */
+
+#include <config.h>
+
+#include "hash-triple.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "hash-pjw.h"
+#include "same.h"
+#include "same-inode.h"
+
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
+/* Hash an F_triple, and *do* consider the file name.  */
+size_t
+triple_hash (void const *x, size_t table_size)
+{
+  struct F_triple const *p = x;
+  size_t tmp = hash_pjw (p->name, table_size);
+
+  /* Ignoring the device number here should be fine.  */
+  return (tmp ^ p->st_ino) % table_size;
+}
+
+/* Hash an F_triple, without considering the file name.  */
+size_t
+triple_hash_no_name (void const *x, size_t table_size)
+{
+  struct F_triple const *p = x;
+
+  /* Ignoring the device number here should be fine.  */
+  return p->st_ino % table_size;
+}
+
+/* Compare two F_triple structs.  */
+bool
+triple_compare (void const *x, void const *y)
+{
+  struct F_triple const *a = x;
+  struct F_triple const *b = y;
+  return (SAME_INODE (*a, *b) && same_name (a->name, b->name)) ? true : false;
+}
+
+bool
+triple_compare_ino_str (void const *x, void const *y)
+{
+  struct F_triple const *a = x;
+  struct F_triple const *b = y;
+  return (SAME_INODE (*a, *b) && STREQ (a->name, b->name)) ? true : false;
+}
+
+/* Free an F_triple.  */
+void
+triple_free (void *x)
+{
+  struct F_triple *a = x;
+  free (a->name);
+  free (a);
+}

diff --git a/libsbutil/gnulib/hash-triple.h b/libsbutil/gnulib/hash-triple.h
new file mode 100644
index 0000000..0658d81
--- /dev/null
+++ b/libsbutil/gnulib/hash-triple.h
@@ -0,0 +1,24 @@
+#ifndef HASH_TRIPLE_H
+#define HASH_TRIPLE_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+
+/* Describe a just-created or just-renamed destination file.  */
+struct F_triple
+{
+  char *name;
+  ino_t st_ino;
+  dev_t st_dev;
+};
+
+extern size_t triple_hash (void const *x, size_t table_size) _GL_ATTRIBUTE_PURE;
+extern size_t triple_hash_no_name (void const *x, size_t table_size)
+  _GL_ATTRIBUTE_PURE;
+extern bool triple_compare (void const *x, void const *y);
+extern bool triple_compare_ino_str (void const *x, void const *y)
+  _GL_ATTRIBUTE_PURE;
+extern void triple_free (void *x);
+
+#endif

diff --git a/libsbutil/gnulib/hash.c b/libsbutil/gnulib/hash.c
new file mode 100644
index 0000000..4f27d5c
--- /dev/null
+++ b/libsbutil/gnulib/hash.c
@@ -0,0 +1,1225 @@
+/* hash - hashing table processing.
+
+   Copyright (C) 1998-2004, 2006-2007, 2009-2015 Free Software Foundation, Inc.
+
+   Written by Jim Meyering, 1992.
+
+   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 3 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/>.  */
+
+/* A generic hash table package.  */
+
+/* Define USE_OBSTACK to 1 if you want the allocator to use obstacks instead
+   of malloc.  If you change USE_OBSTACK, you have to recompile!  */
+
+#include <config.h>
+
+#include "hash.h"
+
+#include "bitrotate.h"
+#include "xalloc-oversized.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if USE_OBSTACK
+# include "obstack.h"
+# ifndef obstack_chunk_alloc
+#  define obstack_chunk_alloc malloc
+# endif
+# ifndef obstack_chunk_free
+#  define obstack_chunk_free free
+# endif
+#endif
+
+struct hash_entry
+  {
+    void *data;
+    struct hash_entry *next;
+  };
+
+struct hash_table
+  {
+    /* The array of buckets starts at BUCKET and extends to BUCKET_LIMIT-1,
+       for a possibility of N_BUCKETS.  Among those, N_BUCKETS_USED buckets
+       are not empty, there are N_ENTRIES active entries in the table.  */
+    struct hash_entry *bucket;
+    struct hash_entry const *bucket_limit;
+    size_t n_buckets;
+    size_t n_buckets_used;
+    size_t n_entries;
+
+    /* Tuning arguments, kept in a physically separate structure.  */
+    const Hash_tuning *tuning;
+
+    /* Three functions are given to 'hash_initialize', see the documentation
+       block for this function.  In a word, HASHER randomizes a user entry
+       into a number up from 0 up to some maximum minus 1; COMPARATOR returns
+       true if two user entries compare equally; and DATA_FREER is the cleanup
+       function for a user entry.  */
+    Hash_hasher hasher;
+    Hash_comparator comparator;
+    Hash_data_freer data_freer;
+
+    /* A linked list of freed struct hash_entry structs.  */
+    struct hash_entry *free_entry_list;
+
+#if USE_OBSTACK
+    /* Whenever obstacks are used, it is possible to allocate all overflowed
+       entries into a single stack, so they all can be freed in a single
+       operation.  It is not clear if the speedup is worth the trouble.  */
+    struct obstack entry_stack;
+#endif
+  };
+
+/* A hash table contains many internal entries, each holding a pointer to
+   some user-provided data (also called a user entry).  An entry indistinctly
+   refers to both the internal entry and its associated user entry.  A user
+   entry contents may be hashed by a randomization function (the hashing
+   function, or just "hasher" for short) into a number (or "slot") between 0
+   and the current table size.  At each slot position in the hash table,
+   starts a linked chain of entries for which the user data all hash to this
+   slot.  A bucket is the collection of all entries hashing to the same slot.
+
+   A good "hasher" function will distribute entries rather evenly in buckets.
+   In the ideal case, the length of each bucket is roughly the number of
+   entries divided by the table size.  Finding the slot for a data is usually
+   done in constant time by the "hasher", and the later finding of a precise
+   entry is linear in time with the size of the bucket.  Consequently, a
+   larger hash table size (that is, a larger number of buckets) is prone to
+   yielding shorter chains, *given* the "hasher" function behaves properly.
+
+   Long buckets slow down the lookup algorithm.  One might use big hash table
+   sizes in hope to reduce the average length of buckets, but this might
+   become inordinate, as unused slots in the hash table take some space.  The
+   best bet is to make sure you are using a good "hasher" function (beware
+   that those are not that easy to write! :-), and to use a table size
+   larger than the actual number of entries.  */
+
+/* If an insertion makes the ratio of nonempty buckets to table size larger
+   than the growth threshold (a number between 0.0 and 1.0), then increase
+   the table size by multiplying by the growth factor (a number greater than
+   1.0).  The growth threshold defaults to 0.8, and the growth factor
+   defaults to 1.414, meaning that the table will have doubled its size
+   every second time 80% of the buckets get used.  */
+#define DEFAULT_GROWTH_THRESHOLD 0.8f
+#define DEFAULT_GROWTH_FACTOR 1.414f
+
+/* If a deletion empties a bucket and causes the ratio of used buckets to
+   table size to become smaller than the shrink threshold (a number between
+   0.0 and 1.0), then shrink the table by multiplying by the shrink factor (a
+   number greater than the shrink threshold but smaller than 1.0).  The shrink
+   threshold and factor default to 0.0 and 1.0, meaning that the table never
+   shrinks.  */
+#define DEFAULT_SHRINK_THRESHOLD 0.0f
+#define DEFAULT_SHRINK_FACTOR 1.0f
+
+/* Use this to initialize or reset a TUNING structure to
+   some sensible values. */
+static const Hash_tuning default_tuning =
+  {
+    DEFAULT_SHRINK_THRESHOLD,
+    DEFAULT_SHRINK_FACTOR,
+    DEFAULT_GROWTH_THRESHOLD,
+    DEFAULT_GROWTH_FACTOR,
+    false
+  };
+
+/* Information and lookup.  */
+
+/* The following few functions provide information about the overall hash
+   table organization: the number of entries, number of buckets and maximum
+   length of buckets.  */
+
+/* Return the number of buckets in the hash table.  The table size, the total
+   number of buckets (used plus unused), or the maximum number of slots, are
+   the same quantity.  */
+
+size_t
+hash_get_n_buckets (const Hash_table *table)
+{
+  return table->n_buckets;
+}
+
+/* Return the number of slots in use (non-empty buckets).  */
+
+size_t
+hash_get_n_buckets_used (const Hash_table *table)
+{
+  return table->n_buckets_used;
+}
+
+/* Return the number of active entries.  */
+
+size_t
+hash_get_n_entries (const Hash_table *table)
+{
+  return table->n_entries;
+}
+
+/* Return the length of the longest chain (bucket).  */
+
+size_t
+hash_get_max_bucket_length (const Hash_table *table)
+{
+  struct hash_entry const *bucket;
+  size_t max_bucket_length = 0;
+
+  for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+    {
+      if (bucket->data)
+        {
+          struct hash_entry const *cursor = bucket;
+          size_t bucket_length = 1;
+
+          while (cursor = cursor->next, cursor)
+            bucket_length++;
+
+          if (bucket_length > max_bucket_length)
+            max_bucket_length = bucket_length;
+        }
+    }
+
+  return max_bucket_length;
+}
+
+/* Do a mild validation of a hash table, by traversing it and checking two
+   statistics.  */
+
+bool
+hash_table_ok (const Hash_table *table)
+{
+  struct hash_entry const *bucket;
+  size_t n_buckets_used = 0;
+  size_t n_entries = 0;
+
+  for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+    {
+      if (bucket->data)
+        {
+          struct hash_entry const *cursor = bucket;
+
+          /* Count bucket head.  */
+          n_buckets_used++;
+          n_entries++;
+
+          /* Count bucket overflow.  */
+          while (cursor = cursor->next, cursor)
+            n_entries++;
+        }
+    }
+
+  if (n_buckets_used == table->n_buckets_used && n_entries == table->n_entries)
+    return true;
+
+  return false;
+}
+
+void
+hash_print_statistics (const Hash_table *table, FILE *stream)
+{
+  size_t n_entries = hash_get_n_entries (table);
+  size_t n_buckets = hash_get_n_buckets (table);
+  size_t n_buckets_used = hash_get_n_buckets_used (table);
+  size_t max_bucket_length = hash_get_max_bucket_length (table);
+
+  fprintf (stream, "# entries:         %lu\n", (unsigned long int) n_entries);
+  fprintf (stream, "# buckets:         %lu\n", (unsigned long int) n_buckets);
+  fprintf (stream, "# buckets used:    %lu (%.2f%%)\n",
+           (unsigned long int) n_buckets_used,
+           (100.0 * n_buckets_used) / n_buckets);
+  fprintf (stream, "max bucket length: %lu\n",
+           (unsigned long int) max_bucket_length);
+}
+
+/* Hash KEY and return a pointer to the selected bucket.
+   If TABLE->hasher misbehaves, abort.  */
+static struct hash_entry *
+safe_hasher (const Hash_table *table, const void *key)
+{
+  size_t n = table->hasher (key, table->n_buckets);
+  if (! (n < table->n_buckets))
+    abort ();
+  return table->bucket + n;
+}
+
+/* If ENTRY matches an entry already in the hash table, return the
+   entry from the table.  Otherwise, return NULL.  */
+
+void *
+hash_lookup (const Hash_table *table, const void *entry)
+{
+  struct hash_entry const *bucket = safe_hasher (table, entry);
+  struct hash_entry const *cursor;
+
+  if (bucket->data == NULL)
+    return NULL;
+
+  for (cursor = bucket; cursor; cursor = cursor->next)
+    if (entry == cursor->data || table->comparator (entry, cursor->data))
+      return cursor->data;
+
+  return NULL;
+}
+
+/* Walking.  */
+
+/* The functions in this page traverse the hash table and process the
+   contained entries.  For the traversal to work properly, the hash table
+   should not be resized nor modified while any particular entry is being
+   processed.  In particular, entries should not be added, and an entry
+   may be removed only if there is no shrink threshold and the entry being
+   removed has already been passed to hash_get_next.  */
+
+/* Return the first data in the table, or NULL if the table is empty.  */
+
+void *
+hash_get_first (const Hash_table *table)
+{
+  struct hash_entry const *bucket;
+
+  if (table->n_entries == 0)
+    return NULL;
+
+  for (bucket = table->bucket; ; bucket++)
+    if (! (bucket < table->bucket_limit))
+      abort ();
+    else if (bucket->data)
+      return bucket->data;
+}
+
+/* Return the user data for the entry following ENTRY, where ENTRY has been
+   returned by a previous call to either 'hash_get_first' or 'hash_get_next'.
+   Return NULL if there are no more entries.  */
+
+void *
+hash_get_next (const Hash_table *table, const void *entry)
+{
+  struct hash_entry const *bucket = safe_hasher (table, entry);
+  struct hash_entry const *cursor;
+
+  /* Find next entry in the same bucket.  */
+  cursor = bucket;
+  do
+    {
+      if (cursor->data == entry && cursor->next)
+        return cursor->next->data;
+      cursor = cursor->next;
+    }
+  while (cursor != NULL);
+
+  /* Find first entry in any subsequent bucket.  */
+  while (++bucket < table->bucket_limit)
+    if (bucket->data)
+      return bucket->data;
+
+  /* None found.  */
+  return NULL;
+}
+
+/* Fill BUFFER with pointers to active user entries in the hash table, then
+   return the number of pointers copied.  Do not copy more than BUFFER_SIZE
+   pointers.  */
+
+size_t
+hash_get_entries (const Hash_table *table, void **buffer,
+                  size_t buffer_size)
+{
+  size_t counter = 0;
+  struct hash_entry const *bucket;
+  struct hash_entry const *cursor;
+
+  for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+    {
+      if (bucket->data)
+        {
+          for (cursor = bucket; cursor; cursor = cursor->next)
+            {
+              if (counter >= buffer_size)
+                return counter;
+              buffer[counter++] = cursor->data;
+            }
+        }
+    }
+
+  return counter;
+}
+
+/* Call a PROCESSOR function for each entry of a hash table, and return the
+   number of entries for which the processor function returned success.  A
+   pointer to some PROCESSOR_DATA which will be made available to each call to
+   the processor function.  The PROCESSOR accepts two arguments: the first is
+   the user entry being walked into, the second is the value of PROCESSOR_DATA
+   as received.  The walking continue for as long as the PROCESSOR function
+   returns nonzero.  When it returns zero, the walking is interrupted.  */
+
+size_t
+hash_do_for_each (const Hash_table *table, Hash_processor processor,
+                  void *processor_data)
+{
+  size_t counter = 0;
+  struct hash_entry const *bucket;
+  struct hash_entry const *cursor;
+
+  for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+    {
+      if (bucket->data)
+        {
+          for (cursor = bucket; cursor; cursor = cursor->next)
+            {
+              if (! processor (cursor->data, processor_data))
+                return counter;
+              counter++;
+            }
+        }
+    }
+
+  return counter;
+}
+
+/* Allocation and clean-up.  */
+
+/* Return a hash index for a NUL-terminated STRING between 0 and N_BUCKETS-1.
+   This is a convenience routine for constructing other hashing functions.  */
+
+#if USE_DIFF_HASH
+
+/* About hashings, Paul Eggert writes to me (FP), on 1994-01-01: "Please see
+   B. J. McKenzie, R. Harries & T. Bell, Selecting a hashing algorithm,
+   Software--practice & experience 20, 2 (Feb 1990), 209-224.  Good hash
+   algorithms tend to be domain-specific, so what's good for [diffutils'] io.c
+   may not be good for your application."  */
+
+size_t
+hash_string (const char *string, size_t n_buckets)
+{
+# define HASH_ONE_CHAR(Value, Byte) \
+  ((Byte) + rotl_sz (Value, 7))
+
+  size_t value = 0;
+  unsigned char ch;
+
+  for (; (ch = *string); string++)
+    value = HASH_ONE_CHAR (value, ch);
+  return value % n_buckets;
+
+# undef HASH_ONE_CHAR
+}
+
+#else /* not USE_DIFF_HASH */
+
+/* This one comes from 'recode', and performs a bit better than the above as
+   per a few experiments.  It is inspired from a hashing routine found in the
+   very old Cyber 'snoop', itself written in typical Greg Mansfield style.
+   (By the way, what happened to this excellent man?  Is he still alive?)  */
+
+size_t
+hash_string (const char *string, size_t n_buckets)
+{
+  size_t value = 0;
+  unsigned char ch;
+
+  for (; (ch = *string); string++)
+    value = (value * 31 + ch) % n_buckets;
+  return value;
+}
+
+#endif /* not USE_DIFF_HASH */
+
+/* Return true if CANDIDATE is a prime number.  CANDIDATE should be an odd
+   number at least equal to 11.  */
+
+static bool _GL_ATTRIBUTE_CONST
+is_prime (size_t candidate)
+{
+  size_t divisor = 3;
+  size_t square = divisor * divisor;
+
+  while (square < candidate && (candidate % divisor))
+    {
+      divisor++;
+      square += 4 * divisor;
+      divisor++;
+    }
+
+  return (candidate % divisor ? true : false);
+}
+
+/* Round a given CANDIDATE number up to the nearest prime, and return that
+   prime.  Primes lower than 10 are merely skipped.  */
+
+static size_t _GL_ATTRIBUTE_CONST
+next_prime (size_t candidate)
+{
+  /* Skip small primes.  */
+  if (candidate < 10)
+    candidate = 10;
+
+  /* Make it definitely odd.  */
+  candidate |= 1;
+
+  while (SIZE_MAX != candidate && !is_prime (candidate))
+    candidate += 2;
+
+  return candidate;
+}
+
+void
+hash_reset_tuning (Hash_tuning *tuning)
+{
+  *tuning = default_tuning;
+}
+
+/* If the user passes a NULL hasher, we hash the raw pointer.  */
+static size_t
+raw_hasher (const void *data, size_t n)
+{
+  /* When hashing unique pointers, it is often the case that they were
+     generated by malloc and thus have the property that the low-order
+     bits are 0.  As this tends to give poorer performance with small
+     tables, we rotate the pointer value before performing division,
+     in an attempt to improve hash quality.  */
+  size_t val = rotr_sz ((size_t) data, 3);
+  return val % n;
+}
+
+/* If the user passes a NULL comparator, we use pointer comparison.  */
+static bool
+raw_comparator (const void *a, const void *b)
+{
+  return a == b;
+}
+
+
+/* For the given hash TABLE, check the user supplied tuning structure for
+   reasonable values, and return true if there is no gross error with it.
+   Otherwise, definitively reset the TUNING field to some acceptable default
+   in the hash table (that is, the user loses the right of further modifying
+   tuning arguments), and return false.  */
+
+static bool
+check_tuning (Hash_table *table)
+{
+  const Hash_tuning *tuning = table->tuning;
+  float epsilon;
+  if (tuning == &default_tuning)
+    return true;
+
+  /* Be a bit stricter than mathematics would require, so that
+     rounding errors in size calculations do not cause allocations to
+     fail to grow or shrink as they should.  The smallest allocation
+     is 11 (due to next_prime's algorithm), so an epsilon of 0.1
+     should be good enough.  */
+  epsilon = 0.1f;
+
+  if (epsilon < tuning->growth_threshold
+      && tuning->growth_threshold < 1 - epsilon
+      && 1 + epsilon < tuning->growth_factor
+      && 0 <= tuning->shrink_threshold
+      && tuning->shrink_threshold + epsilon < tuning->shrink_factor
+      && tuning->shrink_factor <= 1
+      && tuning->shrink_threshold + epsilon < tuning->growth_threshold)
+    return true;
+
+  table->tuning = &default_tuning;
+  return false;
+}
+
+/* Compute the size of the bucket array for the given CANDIDATE and
+   TUNING, or return 0 if there is no possible way to allocate that
+   many entries.  */
+
+static size_t _GL_ATTRIBUTE_PURE
+compute_bucket_size (size_t candidate, const Hash_tuning *tuning)
+{
+  if (!tuning->is_n_buckets)
+    {
+      float new_candidate = candidate / tuning->growth_threshold;
+      if (SIZE_MAX <= new_candidate)
+        return 0;
+      candidate = new_candidate;
+    }
+  candidate = next_prime (candidate);
+  if (xalloc_oversized (candidate, sizeof (struct hash_entry *)))
+    return 0;
+  return candidate;
+}
+
+/* Allocate and return a new hash table, or NULL upon failure.  The initial
+   number of buckets is automatically selected so as to _guarantee_ that you
+   may insert at least CANDIDATE different user entries before any growth of
+   the hash table size occurs.  So, if have a reasonably tight a-priori upper
+   bound on the number of entries you intend to insert in the hash table, you
+   may save some table memory and insertion time, by specifying it here.  If
+   the IS_N_BUCKETS field of the TUNING structure is true, the CANDIDATE
+   argument has its meaning changed to the wanted number of buckets.
+
+   TUNING points to a structure of user-supplied values, in case some fine
+   tuning is wanted over the default behavior of the hasher.  If TUNING is
+   NULL, the default tuning parameters are used instead.  If TUNING is
+   provided but the values requested are out of bounds or might cause
+   rounding errors, return NULL.
+
+   The user-supplied HASHER function, when not NULL, accepts two
+   arguments ENTRY and TABLE_SIZE.  It computes, by hashing ENTRY contents, a
+   slot number for that entry which should be in the range 0..TABLE_SIZE-1.
+   This slot number is then returned.
+
+   The user-supplied COMPARATOR function, when not NULL, accepts two
+   arguments pointing to user data, it then returns true for a pair of entries
+   that compare equal, or false otherwise.  This function is internally called
+   on entries which are already known to hash to the same bucket index,
+   but which are distinct pointers.
+
+   The user-supplied DATA_FREER function, when not NULL, may be later called
+   with the user data as an argument, just before the entry containing the
+   data gets freed.  This happens from within 'hash_free' or 'hash_clear'.
+   You should specify this function only if you want these functions to free
+   all of your 'data' data.  This is typically the case when your data is
+   simply an auxiliary struct that you have malloc'd to aggregate several
+   values.  */
+
+Hash_table *
+hash_initialize (size_t candidate, const Hash_tuning *tuning,
+                 Hash_hasher hasher, Hash_comparator comparator,
+                 Hash_data_freer data_freer)
+{
+  Hash_table *table;
+
+  if (hasher == NULL)
+    hasher = raw_hasher;
+  if (comparator == NULL)
+    comparator = raw_comparator;
+
+  table = malloc (sizeof *table);
+  if (table == NULL)
+    return NULL;
+
+  if (!tuning)
+    tuning = &default_tuning;
+  table->tuning = tuning;
+  if (!check_tuning (table))
+    {
+      /* Fail if the tuning options are invalid.  This is the only occasion
+         when the user gets some feedback about it.  Once the table is created,
+         if the user provides invalid tuning options, we silently revert to
+         using the defaults, and ignore further request to change the tuning
+         options.  */
+      goto fail;
+    }
+
+  table->n_buckets = compute_bucket_size (candidate, tuning);
+  if (!table->n_buckets)
+    goto fail;
+
+  table->bucket = calloc (table->n_buckets, sizeof *table->bucket);
+  if (table->bucket == NULL)
+    goto fail;
+  table->bucket_limit = table->bucket + table->n_buckets;
+  table->n_buckets_used = 0;
+  table->n_entries = 0;
+
+  table->hasher = hasher;
+  table->comparator = comparator;
+  table->data_freer = data_freer;
+
+  table->free_entry_list = NULL;
+#if USE_OBSTACK
+  obstack_init (&table->entry_stack);
+#endif
+  return table;
+
+ fail:
+  free (table);
+  return NULL;
+}
+
+/* Make all buckets empty, placing any chained entries on the free list.
+   Apply the user-specified function data_freer (if any) to the datas of any
+   affected entries.  */
+
+void
+hash_clear (Hash_table *table)
+{
+  struct hash_entry *bucket;
+
+  for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+    {
+      if (bucket->data)
+        {
+          struct hash_entry *cursor;
+          struct hash_entry *next;
+
+          /* Free the bucket overflow.  */
+          for (cursor = bucket->next; cursor; cursor = next)
+            {
+              if (table->data_freer)
+                table->data_freer (cursor->data);
+              cursor->data = NULL;
+
+              next = cursor->next;
+              /* Relinking is done one entry at a time, as it is to be expected
+                 that overflows are either rare or short.  */
+              cursor->next = table->free_entry_list;
+              table->free_entry_list = cursor;
+            }
+
+          /* Free the bucket head.  */
+          if (table->data_freer)
+            table->data_freer (bucket->data);
+          bucket->data = NULL;
+          bucket->next = NULL;
+        }
+    }
+
+  table->n_buckets_used = 0;
+  table->n_entries = 0;
+}
+
+/* Reclaim all storage associated with a hash table.  If a data_freer
+   function has been supplied by the user when the hash table was created,
+   this function applies it to the data of each entry before freeing that
+   entry.  */
+
+void
+hash_free (Hash_table *table)
+{
+  struct hash_entry *bucket;
+  struct hash_entry *cursor;
+  struct hash_entry *next;
+
+  /* Call the user data_freer function.  */
+  if (table->data_freer && table->n_entries)
+    {
+      for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+        {
+          if (bucket->data)
+            {
+              for (cursor = bucket; cursor; cursor = cursor->next)
+                table->data_freer (cursor->data);
+            }
+        }
+    }
+
+#if USE_OBSTACK
+
+  obstack_free (&table->entry_stack, NULL);
+
+#else
+
+  /* Free all bucket overflowed entries.  */
+  for (bucket = table->bucket; bucket < table->bucket_limit; bucket++)
+    {
+      for (cursor = bucket->next; cursor; cursor = next)
+        {
+          next = cursor->next;
+          free (cursor);
+        }
+    }
+
+  /* Also reclaim the internal list of previously freed entries.  */
+  for (cursor = table->free_entry_list; cursor; cursor = next)
+    {
+      next = cursor->next;
+      free (cursor);
+    }
+
+#endif
+
+  /* Free the remainder of the hash table structure.  */
+  free (table->bucket);
+  free (table);
+}
+
+/* Insertion and deletion.  */
+
+/* Get a new hash entry for a bucket overflow, possibly by recycling a
+   previously freed one.  If this is not possible, allocate a new one.  */
+
+static struct hash_entry *
+allocate_entry (Hash_table *table)
+{
+  struct hash_entry *new;
+
+  if (table->free_entry_list)
+    {
+      new = table->free_entry_list;
+      table->free_entry_list = new->next;
+    }
+  else
+    {
+#if USE_OBSTACK
+      new = obstack_alloc (&table->entry_stack, sizeof *new);
+#else
+      new = malloc (sizeof *new);
+#endif
+    }
+
+  return new;
+}
+
+/* Free a hash entry which was part of some bucket overflow,
+   saving it for later recycling.  */
+
+static void
+free_entry (Hash_table *table, struct hash_entry *entry)
+{
+  entry->data = NULL;
+  entry->next = table->free_entry_list;
+  table->free_entry_list = entry;
+}
+
+/* This private function is used to help with insertion and deletion.  When
+   ENTRY matches an entry in the table, return a pointer to the corresponding
+   user data and set *BUCKET_HEAD to the head of the selected bucket.
+   Otherwise, return NULL.  When DELETE is true and ENTRY matches an entry in
+   the table, unlink the matching entry.  */
+
+static void *
+hash_find_entry (Hash_table *table, const void *entry,
+                 struct hash_entry **bucket_head, bool delete)
+{
+  struct hash_entry *bucket = safe_hasher (table, entry);
+  struct hash_entry *cursor;
+
+  *bucket_head = bucket;
+
+  /* Test for empty bucket.  */
+  if (bucket->data == NULL)
+    return NULL;
+
+  /* See if the entry is the first in the bucket.  */
+  if (entry == bucket->data || table->comparator (entry, bucket->data))
+    {
+      void *data = bucket->data;
+
+      if (delete)
+        {
+          if (bucket->next)
+            {
+              struct hash_entry *next = bucket->next;
+
+              /* Bump the first overflow entry into the bucket head, then save
+                 the previous first overflow entry for later recycling.  */
+              *bucket = *next;
+              free_entry (table, next);
+            }
+          else
+            {
+              bucket->data = NULL;
+            }
+        }
+
+      return data;
+    }
+
+  /* Scan the bucket overflow.  */
+  for (cursor = bucket; cursor->next; cursor = cursor->next)
+    {
+      if (entry == cursor->next->data
+          || table->comparator (entry, cursor->next->data))
+        {
+          void *data = cursor->next->data;
+
+          if (delete)
+            {
+              struct hash_entry *next = cursor->next;
+
+              /* Unlink the entry to delete, then save the freed entry for later
+                 recycling.  */
+              cursor->next = next->next;
+              free_entry (table, next);
+            }
+
+          return data;
+        }
+    }
+
+  /* No entry found.  */
+  return NULL;
+}
+
+/* Internal helper, to move entries from SRC to DST.  Both tables must
+   share the same free entry list.  If SAFE, only move overflow
+   entries, saving bucket heads for later, so that no allocations will
+   occur.  Return false if the free entry list is exhausted and an
+   allocation fails.  */
+
+static bool
+transfer_entries (Hash_table *dst, Hash_table *src, bool safe)
+{
+  struct hash_entry *bucket;
+  struct hash_entry *cursor;
+  struct hash_entry *next;
+  for (bucket = src->bucket; bucket < src->bucket_limit; bucket++)
+    if (bucket->data)
+      {
+        void *data;
+        struct hash_entry *new_bucket;
+
+        /* Within each bucket, transfer overflow entries first and
+           then the bucket head, to minimize memory pressure.  After
+           all, the only time we might allocate is when moving the
+           bucket head, but moving overflow entries first may create
+           free entries that can be recycled by the time we finally
+           get to the bucket head.  */
+        for (cursor = bucket->next; cursor; cursor = next)
+          {
+            data = cursor->data;
+            new_bucket = safe_hasher (dst, data);
+
+            next = cursor->next;
+
+            if (new_bucket->data)
+              {
+                /* Merely relink an existing entry, when moving from a
+                   bucket overflow into a bucket overflow.  */
+                cursor->next = new_bucket->next;
+                new_bucket->next = cursor;
+              }
+            else
+              {
+                /* Free an existing entry, when moving from a bucket
+                   overflow into a bucket header.  */
+                new_bucket->data = data;
+                dst->n_buckets_used++;
+                free_entry (dst, cursor);
+              }
+          }
+        /* Now move the bucket head.  Be sure that if we fail due to
+           allocation failure that the src table is in a consistent
+           state.  */
+        data = bucket->data;
+        bucket->next = NULL;
+        if (safe)
+          continue;
+        new_bucket = safe_hasher (dst, data);
+
+        if (new_bucket->data)
+          {
+            /* Allocate or recycle an entry, when moving from a bucket
+               header into a bucket overflow.  */
+            struct hash_entry *new_entry = allocate_entry (dst);
+
+            if (new_entry == NULL)
+              return false;
+
+            new_entry->data = data;
+            new_entry->next = new_bucket->next;
+            new_bucket->next = new_entry;
+          }
+        else
+          {
+            /* Move from one bucket header to another.  */
+            new_bucket->data = data;
+            dst->n_buckets_used++;
+          }
+        bucket->data = NULL;
+        src->n_buckets_used--;
+      }
+  return true;
+}
+
+/* For an already existing hash table, change the number of buckets through
+   specifying CANDIDATE.  The contents of the hash table are preserved.  The
+   new number of buckets is automatically selected so as to _guarantee_ that
+   the table may receive at least CANDIDATE different user entries, including
+   those already in the table, before any other growth of the hash table size
+   occurs.  If TUNING->IS_N_BUCKETS is true, then CANDIDATE specifies the
+   exact number of buckets desired.  Return true iff the rehash succeeded.  */
+
+bool
+hash_rehash (Hash_table *table, size_t candidate)
+{
+  Hash_table storage;
+  Hash_table *new_table;
+  size_t new_size = compute_bucket_size (candidate, table->tuning);
+
+  if (!new_size)
+    return false;
+  if (new_size == table->n_buckets)
+    return true;
+  new_table = &storage;
+  new_table->bucket = calloc (new_size, sizeof *new_table->bucket);
+  if (new_table->bucket == NULL)
+    return false;
+  new_table->n_buckets = new_size;
+  new_table->bucket_limit = new_table->bucket + new_size;
+  new_table->n_buckets_used = 0;
+  new_table->n_entries = 0;
+  new_table->tuning = table->tuning;
+  new_table->hasher = table->hasher;
+  new_table->comparator = table->comparator;
+  new_table->data_freer = table->data_freer;
+
+  /* In order for the transfer to successfully complete, we need
+     additional overflow entries when distinct buckets in the old
+     table collide into a common bucket in the new table.  The worst
+     case possible is a hasher that gives a good spread with the old
+     size, but returns a constant with the new size; if we were to
+     guarantee table->n_buckets_used-1 free entries in advance, then
+     the transfer would be guaranteed to not allocate memory.
+     However, for large tables, a guarantee of no further allocation
+     introduces a lot of extra memory pressure, all for an unlikely
+     corner case (most rehashes reduce, rather than increase, the
+     number of overflow entries needed).  So, we instead ensure that
+     the transfer process can be reversed if we hit a memory
+     allocation failure mid-transfer.  */
+
+  /* Merely reuse the extra old space into the new table.  */
+#if USE_OBSTACK
+  new_table->entry_stack = table->entry_stack;
+#endif
+  new_table->free_entry_list = table->free_entry_list;
+
+  if (transfer_entries (new_table, table, false))
+    {
+      /* Entries transferred successfully; tie up the loose ends.  */
+      free (table->bucket);
+      table->bucket = new_table->bucket;
+      table->bucket_limit = new_table->bucket_limit;
+      table->n_buckets = new_table->n_buckets;
+      table->n_buckets_used = new_table->n_buckets_used;
+      table->free_entry_list = new_table->free_entry_list;
+      /* table->n_entries and table->entry_stack already hold their value.  */
+      return true;
+    }
+
+  /* We've allocated new_table->bucket (and possibly some entries),
+     exhausted the free list, and moved some but not all entries into
+     new_table.  We must undo the partial move before returning
+     failure.  The only way to get into this situation is if new_table
+     uses fewer buckets than the old table, so we will reclaim some
+     free entries as overflows in the new table are put back into
+     distinct buckets in the old table.
+
+     There are some pathological cases where a single pass through the
+     table requires more intermediate overflow entries than using two
+     passes.  Two passes give worse cache performance and takes
+     longer, but at this point, we're already out of memory, so slow
+     and safe is better than failure.  */
+  table->free_entry_list = new_table->free_entry_list;
+  if (! (transfer_entries (table, new_table, true)
+         && transfer_entries (table, new_table, false)))
+    abort ();
+  /* table->n_entries already holds its value.  */
+  free (new_table->bucket);
+  return false;
+}
+
+/* Insert ENTRY into hash TABLE if there is not already a matching entry.
+
+   Return -1 upon memory allocation failure.
+   Return 1 if insertion succeeded.
+   Return 0 if there is already a matching entry in the table,
+   and in that case, if MATCHED_ENT is non-NULL, set *MATCHED_ENT
+   to that entry.
+
+   This interface is easier to use than hash_insert when you must
+   distinguish between the latter two cases.  More importantly,
+   hash_insert is unusable for some types of ENTRY values.  When using
+   hash_insert, the only way to distinguish those cases is to compare
+   the return value and ENTRY.  That works only when you can have two
+   different ENTRY values that point to data that compares "equal".  Thus,
+   when the ENTRY value is a simple scalar, you must use
+   hash_insert_if_absent.  ENTRY must not be NULL.  */
+int
+hash_insert_if_absent (Hash_table *table, void const *entry,
+                       void const **matched_ent)
+{
+  void *data;
+  struct hash_entry *bucket;
+
+  /* The caller cannot insert a NULL entry, since hash_lookup returns NULL
+     to indicate "not found", and hash_find_entry uses "bucket->data == NULL"
+     to indicate an empty bucket.  */
+  if (! entry)
+    abort ();
+
+  /* If there's a matching entry already in the table, return that.  */
+  if ((data = hash_find_entry (table, entry, &bucket, false)) != NULL)
+    {
+      if (matched_ent)
+        *matched_ent = data;
+      return 0;
+    }
+
+  /* If the growth threshold of the buckets in use has been reached, increase
+     the table size and rehash.  There's no point in checking the number of
+     entries:  if the hashing function is ill-conditioned, rehashing is not
+     likely to improve it.  */
+
+  if (table->n_buckets_used
+      > table->tuning->growth_threshold * table->n_buckets)
+    {
+      /* Check more fully, before starting real work.  If tuning arguments
+         became invalid, the second check will rely on proper defaults.  */
+      check_tuning (table);
+      if (table->n_buckets_used
+          > table->tuning->growth_threshold * table->n_buckets)
+        {
+          const Hash_tuning *tuning = table->tuning;
+          float candidate =
+            (tuning->is_n_buckets
+             ? (table->n_buckets * tuning->growth_factor)
+             : (table->n_buckets * tuning->growth_factor
+                * tuning->growth_threshold));
+
+          if (SIZE_MAX <= candidate)
+            return -1;
+
+          /* If the rehash fails, arrange to return NULL.  */
+          if (!hash_rehash (table, candidate))
+            return -1;
+
+          /* Update the bucket we are interested in.  */
+          if (hash_find_entry (table, entry, &bucket, false) != NULL)
+            abort ();
+        }
+    }
+
+  /* ENTRY is not matched, it should be inserted.  */
+
+  if (bucket->data)
+    {
+      struct hash_entry *new_entry = allocate_entry (table);
+
+      if (new_entry == NULL)
+        return -1;
+
+      /* Add ENTRY in the overflow of the bucket.  */
+
+      new_entry->data = (void *) entry;
+      new_entry->next = bucket->next;
+      bucket->next = new_entry;
+      table->n_entries++;
+      return 1;
+    }
+
+  /* Add ENTRY right in the bucket head.  */
+
+  bucket->data = (void *) entry;
+  table->n_entries++;
+  table->n_buckets_used++;
+
+  return 1;
+}
+
+/* If ENTRY matches an entry already in the hash table, return the pointer
+   to the entry from the table.  Otherwise, insert ENTRY and return ENTRY.
+   Return NULL if the storage required for insertion cannot be allocated.
+   This implementation does not support duplicate entries or insertion of
+   NULL.  */
+
+void *
+hash_insert (Hash_table *table, void const *entry)
+{
+  void const *matched_ent;
+  int err = hash_insert_if_absent (table, entry, &matched_ent);
+  return (err == -1
+          ? NULL
+          : (void *) (err == 0 ? matched_ent : entry));
+}
+
+/* If ENTRY is already in the table, remove it and return the just-deleted
+   data (the user may want to deallocate its storage).  If ENTRY is not in the
+   table, don't modify the table and return NULL.  */
+
+void *
+hash_delete (Hash_table *table, const void *entry)
+{
+  void *data;
+  struct hash_entry *bucket;
+
+  data = hash_find_entry (table, entry, &bucket, true);
+  if (!data)
+    return NULL;
+
+  table->n_entries--;
+  if (!bucket->data)
+    {
+      table->n_buckets_used--;
+
+      /* If the shrink threshold of the buckets in use has been reached,
+         rehash into a smaller table.  */
+
+      if (table->n_buckets_used
+          < table->tuning->shrink_threshold * table->n_buckets)
+        {
+          /* Check more fully, before starting real work.  If tuning arguments
+             became invalid, the second check will rely on proper defaults.  */
+          check_tuning (table);
+          if (table->n_buckets_used
+              < table->tuning->shrink_threshold * table->n_buckets)
+            {
+              const Hash_tuning *tuning = table->tuning;
+              size_t candidate =
+                (tuning->is_n_buckets
+                 ? table->n_buckets * tuning->shrink_factor
+                 : (table->n_buckets * tuning->shrink_factor
+                    * tuning->growth_threshold));
+
+              if (!hash_rehash (table, candidate))
+                {
+                  /* Failure to allocate memory in an attempt to
+                     shrink the table is not fatal.  But since memory
+                     is low, we can at least be kind and free any
+                     spare entries, rather than keeping them tied up
+                     in the free entry list.  */
+#if ! USE_OBSTACK
+                  struct hash_entry *cursor = table->free_entry_list;
+                  struct hash_entry *next;
+                  while (cursor)
+                    {
+                      next = cursor->next;
+                      free (cursor);
+                      cursor = next;
+                    }
+                  table->free_entry_list = NULL;
+#endif
+                }
+            }
+        }
+    }
+
+  return data;
+}
+
+/* Testing.  */
+
+#if TESTING
+
+void
+hash_print (const Hash_table *table)
+{
+  struct hash_entry *bucket = (struct hash_entry *) table->bucket;
+
+  for ( ; bucket < table->bucket_limit; bucket++)
+    {
+      struct hash_entry *cursor;
+
+      if (bucket)
+        printf ("%lu:\n", (unsigned long int) (bucket - table->bucket));
+
+      for (cursor = bucket; cursor; cursor = cursor->next)
+        {
+          char const *s = cursor->data;
+          /* FIXME */
+          if (s)
+            printf ("  %s\n", s);
+        }
+    }
+}
+
+#endif /* TESTING */

diff --git a/libsbutil/gnulib/hash.h b/libsbutil/gnulib/hash.h
new file mode 100644
index 0000000..1e90c31
--- /dev/null
+++ b/libsbutil/gnulib/hash.h
@@ -0,0 +1,103 @@
+/* hash - hashing table processing.
+   Copyright (C) 1998-1999, 2001, 2003, 2009-2015 Free Software Foundation,
+   Inc.
+   Written by Jim Meyering <meyering@ascend.com>, 1998.
+
+   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 3 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/>.  */
+
+/* A generic hash table package.  */
+
+/* Make sure USE_OBSTACK is defined to 1 if you want the allocator to use
+   obstacks instead of malloc, and recompile 'hash.c' with same setting.  */
+
+#ifndef HASH_H_
+# define HASH_H_
+
+# include <stdio.h>
+# include <stdbool.h>
+
+/* The __attribute__ feature is available in gcc versions 2.5 and later.
+   The warn_unused_result attribute appeared first in gcc-3.4.0.  */
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define _GL_ATTRIBUTE_WUR __attribute__ ((__warn_unused_result__))
+# else
+#  define _GL_ATTRIBUTE_WUR /* empty */
+# endif
+
+# ifndef _GL_ATTRIBUTE_DEPRECATED
+/* The __attribute__((__deprecated__)) feature
+   is available in gcc versions 3.1 and newer.  */
+#  if __GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 1)
+#   define _GL_ATTRIBUTE_DEPRECATED /* empty */
+#  else
+#   define _GL_ATTRIBUTE_DEPRECATED __attribute__ ((__deprecated__))
+#  endif
+# endif
+
+typedef size_t (*Hash_hasher) (const void *, size_t);
+typedef bool (*Hash_comparator) (const void *, const void *);
+typedef void (*Hash_data_freer) (void *);
+typedef bool (*Hash_processor) (void *, void *);
+
+struct hash_tuning
+  {
+    /* This structure is mainly used for 'hash_initialize', see the block
+       documentation of 'hash_reset_tuning' for more complete comments.  */
+
+    float shrink_threshold;     /* ratio of used buckets to trigger a shrink */
+    float shrink_factor;        /* ratio of new smaller size to original size */
+    float growth_threshold;     /* ratio of used buckets to trigger a growth */
+    float growth_factor;        /* ratio of new bigger size to original size */
+    bool is_n_buckets;          /* if CANDIDATE really means table size */
+  };
+
+typedef struct hash_tuning Hash_tuning;
+
+struct hash_table;
+
+typedef struct hash_table Hash_table;
+
+/* Information and lookup.  */
+size_t hash_get_n_buckets (const Hash_table *) _GL_ATTRIBUTE_PURE;
+size_t hash_get_n_buckets_used (const Hash_table *) _GL_ATTRIBUTE_PURE;
+size_t hash_get_n_entries (const Hash_table *) _GL_ATTRIBUTE_PURE;
+size_t hash_get_max_bucket_length (const Hash_table *) _GL_ATTRIBUTE_PURE;
+bool hash_table_ok (const Hash_table *) _GL_ATTRIBUTE_PURE;
+void hash_print_statistics (const Hash_table *, FILE *);
+void *hash_lookup (const Hash_table *, const void *);
+
+/* Walking.  */
+void *hash_get_first (const Hash_table *) _GL_ATTRIBUTE_PURE;
+void *hash_get_next (const Hash_table *, const void *);
+size_t hash_get_entries (const Hash_table *, void **, size_t);
+size_t hash_do_for_each (const Hash_table *, Hash_processor, void *);
+
+/* Allocation and clean-up.  */
+size_t hash_string (const char *, size_t) _GL_ATTRIBUTE_PURE;
+void hash_reset_tuning (Hash_tuning *);
+Hash_table *hash_initialize (size_t, const Hash_tuning *,
+                             Hash_hasher, Hash_comparator,
+                             Hash_data_freer) _GL_ATTRIBUTE_WUR;
+void hash_clear (Hash_table *);
+void hash_free (Hash_table *);
+
+/* Insertion and deletion.  */
+bool hash_rehash (Hash_table *, size_t) _GL_ATTRIBUTE_WUR;
+void *hash_insert (Hash_table *, const void *) _GL_ATTRIBUTE_WUR;
+
+int hash_insert_if_absent (Hash_table *table, const void *entry,
+                           const void **matched_ent);
+void *hash_delete (Hash_table *, const void *);
+
+#endif

diff --git a/libsbutil/gnulib/pathmax.h b/libsbutil/gnulib/pathmax.h
new file mode 100644
index 0000000..2a5af08
--- /dev/null
+++ b/libsbutil/gnulib/pathmax.h
@@ -0,0 +1,83 @@
+/* Define PATH_MAX somehow.  Requires sys/types.h.
+   Copyright (C) 1992, 1999, 2001, 2003, 2005, 2009-2015 Free Software
+   Foundation, Inc.
+
+   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, 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 _PATHMAX_H
+# define _PATHMAX_H
+
+/* POSIX:2008 defines PATH_MAX to be the maximum number of bytes in a filename,
+   including the terminating NUL byte.
+   <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html>
+   PATH_MAX is not defined on systems which have no limit on filename length,
+   such as GNU/Hurd.
+
+   This file does *not* define PATH_MAX always.  Programs that use this file
+   can handle the GNU/Hurd case in several ways:
+     - Either with a package-wide handling, or with a per-file handling,
+     - Either through a
+         #ifdef PATH_MAX
+       or through a fallback like
+         #ifndef PATH_MAX
+         # define PATH_MAX 8192
+         #endif
+       or through a fallback like
+         #ifndef PATH_MAX
+         # define PATH_MAX pathconf ("/", _PC_PATH_MAX)
+         #endif
+ */
+
+# include <unistd.h>
+
+# include <limits.h>
+
+# ifndef _POSIX_PATH_MAX
+#  define _POSIX_PATH_MAX 256
+# endif
+
+/* Don't include sys/param.h if it already has been.  */
+# if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN
+#  include <sys/param.h>
+# endif
+
+# if !defined PATH_MAX && defined MAXPATHLEN
+#  define PATH_MAX MAXPATHLEN
+# endif
+
+# ifdef __hpux
+/* On HP-UX, PATH_MAX designates the maximum number of bytes in a filename,
+   *not* including the terminating NUL byte, and is set to 1023.
+   Additionally, when _XOPEN_SOURCE is defined to 500 or more, PATH_MAX is
+   not defined at all any more.  */
+#  undef PATH_MAX
+#  define PATH_MAX 1024
+# endif
+
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+/* The page "Naming Files, Paths, and Namespaces" on msdn.microsoft.com,
+   section "Maximum Path Length Limitation",
+   <http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx#maxpath>
+   explains that the maximum size of a filename, including the terminating
+   NUL byte, is 260 = 3 + 256 + 1.
+   This is the same value as
+     - FILENAME_MAX in <stdio.h>,
+     - _MAX_PATH in <stdlib.h>,
+     - MAX_PATH in <windef.h>.
+   Undefine the original value, because mingw's <limits.h> gets it wrong.  */
+#  undef PATH_MAX
+#  define PATH_MAX 260
+# endif
+
+#endif /* _PATHMAX_H */

diff --git a/libsbutil/gnulib/same-inode.h b/libsbutil/gnulib/same-inode.h
new file mode 100644
index 0000000..ecc3049
--- /dev/null
+++ b/libsbutil/gnulib/same-inode.h
@@ -0,0 +1,33 @@
+/* Determine whether two stat buffers refer to the same file.
+
+   Copyright (C) 2006, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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 SAME_INODE_H
+# define SAME_INODE_H 1
+
+# ifdef __VMS
+#  define SAME_INODE(a, b)             \
+    ((a).st_ino[0] == (b).st_ino[0]    \
+     && (a).st_ino[1] == (b).st_ino[1] \
+     && (a).st_ino[2] == (b).st_ino[2] \
+     && (a).st_dev == (b).st_dev)
+# else
+#  define SAME_INODE(a, b)    \
+    ((a).st_ino == (b).st_ino \
+     && (a).st_dev == (b).st_dev)
+# endif
+
+#endif

diff --git a/libsbutil/gnulib/same.h b/libsbutil/gnulib/same.h
new file mode 100644
index 0000000..ee313c5
--- /dev/null
+++ b/libsbutil/gnulib/same.h
@@ -0,0 +1,25 @@
+/* Determine whether two file names refer to the same file.
+
+   Copyright (C) 1997-2000, 2003-2004, 2009-2015 Free Software Foundation, Inc.
+
+   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 3 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 SAME_H_
+# define SAME_H_ 1
+
+# include <stdbool.h>
+
+bool same_name (const char *source, const char *dest);
+
+#endif /* SAME_H_ */

diff --git a/libsbutil/gnulib/xalloc-oversized.h b/libsbutil/gnulib/xalloc-oversized.h
new file mode 100644
index 0000000..f0e9778
--- /dev/null
+++ b/libsbutil/gnulib/xalloc-oversized.h
@@ -0,0 +1,38 @@
+/* xalloc-oversized.h -- memory allocation size checking
+
+   Copyright (C) 1990-2000, 2003-2004, 2006-2015 Free Software Foundation, Inc.
+
+   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 3 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 XALLOC_OVERSIZED_H_
+# define XALLOC_OVERSIZED_H_
+
+# include <stddef.h>
+
+/* Return 1 if an array of N objects, each of size S, cannot exist due
+   to size arithmetic overflow.  S must be positive and N must be
+   nonnegative.  This is a macro, not a function, so that it
+   works correctly even when SIZE_MAX < N.
+
+   By gnulib convention, SIZE_MAX represents overflow in size
+   calculations, so the conservative dividend to use here is
+   SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value.
+   However, malloc (SIZE_MAX) fails on all known hosts where
+   sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for
+   exactly-SIZE_MAX allocations on such hosts; this avoids a test and
+   branch when S is known to be 1.  */
+# define xalloc_oversized(n, s) \
+    ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n))
+
+#endif /* !XALLOC_OVERSIZED_H_ */

diff --git a/libsbutil/gnulib/xalloc.h b/libsbutil/gnulib/xalloc.h
new file mode 100644
index 0000000..3077f27
--- /dev/null
+++ b/libsbutil/gnulib/xalloc.h
@@ -0,0 +1 @@
+#include "sbutil.h"

diff --git a/libsbutil/gnulib/xgetcwd.h b/libsbutil/gnulib/xgetcwd.h
new file mode 100644
index 0000000..765fab4
--- /dev/null
+++ b/libsbutil/gnulib/xgetcwd.h
@@ -0,0 +1,21 @@
+/* This is slightly wrong as libsbutil code is supposed to work both in
+ * libsandbox and in sandbox, but egetcwd is only available in the former.
+ * We'll worry about that if/when it becomes an issue for other programs.
+ *
+ * Copyright 1999-2015 Gentoo Foundation
+ * Licensed under the GPL-2
+ */
+
+_GL_INLINE_HEADER_BEGIN
+
+extern char *egetcwd(char *buf, size_t size);
+
+_GL_INLINE char *xgetcwd(void)
+{
+	char *ret = egetcwd(NULL, 0);
+	if (ret == NULL && errno == ENOMEM)
+		xalloc_die();
+	return ret;
+}
+
+_GL_INLINE_HEADER_END

diff --git a/libsbutil/sbutil.h b/libsbutil/sbutil.h
index c76465f..56fe6d3 100644
--- a/libsbutil/sbutil.h
+++ b/libsbutil/sbutil.h
@@ -140,10 +140,13 @@ char *__xstrndup(const char *str, size_t size, const char *file, const char *fun
 #define xrealloc(_ptr, _size)  __xrealloc(_ptr, _size, __FILE__, __func__, __LINE__)
 #define xstrdup(_str)          __xstrdup(_str, __FILE__, __func__, __LINE__)
 #define xstrndup(_str, _size)  __xstrndup(_str, _size, __FILE__, __func__, __LINE__)
+#define xalloc_die()           __sb_ebort(__FILE__, __func__, __LINE__, "out of memory")
 
 /* errno helpers */
 #define save_errno()    int old_errno = errno;
 #define restore_errno() errno = old_errno;
 #define saved_errno     old_errno
 
+#include "gnulib/canonicalize.h"
+
 #endif /* __SBUTIL_H__ */


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2015-09-20  8:15 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-09-20  8:15 [gentoo-commits] proj/sandbox:master commit in: libsbutil/, libsbutil/gnulib/, / Mike Frysinger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox