diff options
Diffstat (limited to 'libsbutil')
26 files changed, 2709 insertions, 0 deletions
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__ */ |