summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/alloc.c29
-rw-r--r--src/alloc.h172
-rw-r--r--src/atomic.h4
-rw-r--r--src/bar.h4
-rw-r--r--src/bfs.h7
-rw-r--r--src/bfstd.h110
-rw-r--r--src/bftw.c13
-rw-r--r--src/bftw.h18
-rw-r--r--src/bit.h43
-rw-r--r--src/color.c367
-rw-r--r--src/color.h12
-rw-r--r--src/ctx.h16
-rw-r--r--src/dir.h20
-rw-r--r--src/dstring.c21
-rw-r--r--src/dstring.h131
-rw-r--r--src/eval.c116
-rw-r--r--src/eval.h6
-rw-r--r--src/exec.h12
-rw-r--r--src/expr.c3
-rw-r--r--src/expr.h2
-rw-r--r--src/fsade.c7
-rw-r--r--src/fsade.h14
-rw-r--r--src/ioq.h46
-rw-r--r--src/list.h158
-rw-r--r--src/mtab.c14
-rw-r--r--src/mtab.h8
-rw-r--r--src/opt.c50
-rw-r--r--src/opt.h2
-rw-r--r--src/parse.h4
-rw-r--r--src/printf.h12
-rw-r--r--src/pwcache.h24
-rw-r--r--src/sanity.h14
-rw-r--r--src/sighook.c2
-rw-r--r--src/sighook.h18
-rw-r--r--src/stat.h8
-rw-r--r--src/trie.c112
-rw-r--r--src/trie.h32
-rw-r--r--src/typo.h4
-rw-r--r--src/xregex.h16
-rw-r--r--src/xspawn.h10
-rw-r--r--src/xtime.c97
-rw-r--r--src/xtime.h39
42 files changed, 1097 insertions, 700 deletions
diff --git a/src/alloc.c b/src/alloc.c
index 223d737..ef9f6ab 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -20,24 +20,22 @@
# define ALLOC_MAX (SIZE_MAX / 2)
#endif
-/** Portable aligned_alloc()/posix_memalign(). */
+/** posix_memalign() wrapper. */
static void *xmemalign(size_t align, size_t size) {
bfs_assert(has_single_bit(align));
bfs_assert(align >= sizeof(void *));
- bfs_assert(is_aligned(align, size));
-#if BFS_HAS_ALIGNED_ALLOC
- return aligned_alloc(align, size);
-#else
+ // Since https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2072.htm,
+ // aligned_alloc() doesn't require the size to be a multiple of align.
+ // But the sanitizers don't know about that yet, so always use
+ // posix_memalign().
void *ptr = NULL;
errno = posix_memalign(&ptr, align, size);
return ptr;
-#endif
}
void *alloc(size_t align, size_t size) {
bfs_assert(has_single_bit(align));
- bfs_assert(is_aligned(align, size));
if (size > ALLOC_MAX) {
errno = EOVERFLOW;
@@ -53,7 +51,6 @@ void *alloc(size_t align, size_t size) {
void *zalloc(size_t align, size_t size) {
bfs_assert(has_single_bit(align));
- bfs_assert(is_aligned(align, size));
if (size > ALLOC_MAX) {
errno = EOVERFLOW;
@@ -73,8 +70,6 @@ void *zalloc(size_t align, size_t size) {
void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size) {
bfs_assert(has_single_bit(align));
- bfs_assert(is_aligned(align, old_size));
- bfs_assert(is_aligned(align, new_size));
if (new_size == 0) {
free(ptr);
@@ -108,7 +103,7 @@ void *reserve(void *ptr, size_t align, size_t size, size_t count) {
size_t old_size = size * count;
// Capacity is doubled every power of two, from 0→1, 1→2, 2→4, etc.
- // If we stayed within the same size class, re-use ptr.
+ // If we stayed within the same size class, reuse ptr.
if (count & (count - 1)) {
// Tell sanitizers about the new array element
sanitize_alloc((char *)ptr + old_size, size);
@@ -233,6 +228,7 @@ void arena_free(struct arena *arena, void *ptr) {
union chunk *chunk = ptr;
chunk_set_next(arena, chunk, arena->chunks);
arena->chunks = chunk;
+ sanitize_uninit(chunk, arena->size);
sanitize_free(chunk, arena->size);
}
@@ -252,7 +248,7 @@ void arena_destroy(struct arena *arena) {
sanitize_uninit(arena);
}
-void varena_init(struct varena *varena, size_t align, size_t min, size_t offset, size_t size) {
+void varena_init(struct varena *varena, size_t align, size_t offset, size_t size) {
varena->align = align;
varena->offset = offset;
varena->size = size;
@@ -261,7 +257,7 @@ void varena_init(struct varena *varena, size_t align, size_t min, size_t offset,
// The smallest size class is at least as many as fit in the smallest
// aligned allocation size
- size_t min_count = (flex_size(align, min, offset, size, 1) - offset + size - 1) / size;
+ size_t min_count = (flex_size(align, offset, size, 1) - offset + size - 1) / size;
varena->shift = bit_width(min_count - 1);
}
@@ -274,7 +270,7 @@ static size_t varena_size_class(struct varena *varena, size_t count) {
/** Get the exact size of a flexible struct. */
static size_t varena_exact_size(const struct varena *varena, size_t count) {
- return flex_size(varena->align, 0, varena->offset, varena->size, count);
+ return flex_size(varena->align, varena->offset, varena->size, count);
}
/** Get the arena for the given array length. */
@@ -339,15 +335,16 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t
}
size_t old_size = old_arena->size;
- sanitize_alloc((char *)ptr + old_exact_size, old_size - old_exact_size);
+ sanitize_alloc(ptr, old_size);
size_t new_size = new_arena->size;
size_t min_size = new_size < old_size ? new_size : old_size;
memcpy(ret, ptr, min_size);
arena_free(old_arena, ptr);
- sanitize_free((char *)ret + new_exact_size, new_size - new_exact_size);
+ sanitize_free(ret, new_size);
+ sanitize_alloc(ret, new_exact_size);
return ret;
}
diff --git a/src/alloc.h b/src/alloc.h
index 7b97b8c..1fafbab 100644
--- a/src/alloc.h
+++ b/src/alloc.h
@@ -14,104 +14,119 @@
#include <stddef.h>
#include <stdlib.h>
+#define IS_ALIGNED(align, size) \
+ (((size) & ((align) - 1)) == 0)
+
/** Check if a size is properly aligned. */
static inline bool is_aligned(size_t align, size_t size) {
- return (size & (align - 1)) == 0;
+ return IS_ALIGNED(align, size);
}
+#define ALIGN_FLOOR(align, size) \
+ ((size) & ~((align) - 1))
+
/** Round down to a multiple of an alignment. */
static inline size_t align_floor(size_t align, size_t size) {
- return size & ~(align - 1);
+ return ALIGN_FLOOR(align, size);
}
+#define ALIGN_CEIL(align, size) \
+ ((((size) - 1) | ((align) - 1)) + 1)
+
/** Round up to a multiple of an alignment. */
static inline size_t align_ceil(size_t align, size_t size) {
- return align_floor(align, size + align - 1);
+ return ALIGN_CEIL(align, size);
}
/**
- * Saturating array size.
- *
- * @param align
- * Array element alignment.
- * @param size
- * Array element size.
- * @param count
- * Array element count.
- * @return
- * size * count, saturating to the maximum aligned value on overflow.
+ * Saturating size addition.
*/
-static inline size_t array_size(size_t align, size_t size, size_t count) {
+static inline size_t size_add(size_t lhs, size_t rhs) {
+ size_t ret = lhs + rhs;
+ return ret >= lhs ? ret : (size_t)-1;
+}
+
+/**
+ * Saturating size multiplication.
+ */
+static inline size_t size_mul(size_t size, size_t count) {
size_t ret = size * count;
- return ret / size == count ? ret : ~(align - 1);
+ return ret / size == count ? ret : (size_t)-1;
}
/** Saturating array sizeof. */
#define sizeof_array(type, count) \
- array_size(alignof(type), sizeof(type), count)
+ size_mul(sizeof(type), count)
/** Size of a struct/union field. */
#define sizeof_member(type, member) \
sizeof(((type *)NULL)->member)
/**
+ * @internal
+ * Our flexible struct size calculations assume that structs have the minimum
+ * trailing padding to align the type properly. A pathological ABI that adds
+ * extra padding would result in us under-allocating space for those structs,
+ * so we static_assert() that no such padding exists.
+ */
+#define ASSERT_FLEX_ABI(type, member) \
+ ASSERT_FLEX_ABI_( \
+ ALIGN_CEIL(alignof(type), offsetof(type, member)) >= sizeof(type), \
+ "Unexpected tail padding in " #type)
+
+/**
+ * @internal
+ * The contortions here allow static_assert() to be used in expressions, rather
+ * than just declarations.
+ */
+#define ASSERT_FLEX_ABI_(...) \
+ ((void)sizeof(struct { char _; static_assert(__VA_ARGS__); }))
+
+/**
* Saturating flexible struct size.
*
- * @param align
+ * @align
* Struct alignment.
- * @param min
- * Minimum struct size.
- * @param offset
+ * @offset
* Flexible array member offset.
- * @param size
+ * @size
* Flexible array element size.
- * @param count
+ * @count
* Flexible array element count.
* @return
* The size of the struct with count flexible array elements. Saturates
* to the maximum aligned value on overflow.
*/
-static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t size, size_t count) {
- size_t ret = size * count;
- size_t overflow = ret / size != count;
-
- size_t extra = offset + align - 1;
- ret += extra;
- overflow |= ret < extra;
- ret |= -overflow;
+static inline size_t flex_size(size_t align, size_t offset, size_t size, size_t count) {
+ size_t ret = size_mul(size, count);
+ ret = size_add(ret, offset + align - 1);
ret = align_floor(align, ret);
-
- // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the
- // type has more padding than necessary for alignment
- if (min > align_ceil(align, offset)) {
- ret = ret < min ? min : ret;
- }
-
return ret;
}
/**
* Computes the size of a flexible struct.
*
- * @param type
+ * @type
* The type of the struct containing the flexible array.
- * @param member
+ * @member
* The name of the flexible array member.
- * @param count
+ * @count
* The length of the flexible array.
* @return
* The size of the struct with count flexible array elements. Saturates
* to the maximum aligned value on overflow.
*/
#define sizeof_flex(type, member, count) \
- flex_size(alignof(type), sizeof(type), offsetof(type, member), sizeof_member(type, member[0]), count)
+ (ASSERT_FLEX_ABI(type, member), flex_size( \
+ alignof(type), offsetof(type, member), sizeof_member(type, member[0]), count))
/**
* General memory allocator.
*
- * @param align
+ * @align
* The required alignment.
- * @param size
+ * @size
* The size of the allocation.
* @return
* The allocated memory, or NULL on failure.
@@ -123,9 +138,9 @@ void *alloc(size_t align, size_t size);
/**
* Zero-initialized memory allocator.
*
- * @param align
+ * @align
* The required alignment.
- * @param size
+ * @size
* The size of the allocation.
* @return
* The allocated memory, or NULL on failure.
@@ -161,13 +176,13 @@ void *zalloc(size_t align, size_t size);
/**
* Alignment-aware realloc().
*
- * @param ptr
+ * @ptr
* The pointer to reallocate.
- * @param align
+ * @align
* The required alignment.
- * @param old_size
+ * @old_size
* The previous allocation size.
- * @param new_size
+ * @new_size
* The new allocation size.
* @return
* The reallocated memory, or NULL on failure.
@@ -187,11 +202,11 @@ void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size);
/**
* Reserve space for one more element in a dynamic array.
*
- * @param ptr
+ * @ptr
* The pointer to reallocate.
- * @param align
+ * @align
* The required alignment.
- * @param count
+ * @count
* The current size of the array.
* @return
* The reallocated memory, on both success *and* failure. On success,
@@ -205,11 +220,11 @@ void *reserve(void *ptr, size_t align, size_t size, size_t count);
/**
* Convenience macro to grow a dynamic array.
*
- * @param type
+ * @type
* The array element type.
- * @param type **ptr
+ * @type **ptr
* A pointer to the array.
- * @param size_t *count
+ * @size_t *count
* A pointer to the array's size.
* @return
* On success, a pointer to the newly reserved array element, i.e.
@@ -291,40 +306,39 @@ struct varena {
/**
* Initialize a varena for a struct with the given layout.
*
- * @param varena
+ * @varena
* The varena to initialize.
- * @param align
+ * @align
* alignof(type)
- * @param min
- * sizeof(type)
- * @param offset
+ * @offset
* offsetof(type, flexible_array)
- * @param size
+ * @size
* sizeof(flexible_array[i])
*/
-void varena_init(struct varena *varena, size_t align, size_t min, size_t offset, size_t size);
+void varena_init(struct varena *varena, size_t align, size_t offset, size_t size);
/**
* Initialize a varena for the given type and flexible array.
*
- * @param varena
+ * @varena
* The varena to initialize.
- * @param type
+ * @type
* A struct type containing a flexible array.
- * @param member
+ * @member
* The name of the flexible array member.
*/
#define VARENA_INIT(varena, type, member) \
- varena_init(varena, alignof(type), sizeof(type), offsetof(type, member), sizeof_member(type, member[0]))
+ (ASSERT_FLEX_ABI(type, member), varena_init( \
+ varena, alignof(type), offsetof(type, member), sizeof_member(type, member[0])))
/**
* Free an arena-allocated flexible struct.
*
- * @param varena
+ * @varena
* The that allocated the object.
- * @param ptr
+ * @ptr
* The object to free.
- * @param count
+ * @count
* The length of the flexible array.
*/
void varena_free(struct varena *varena, void *ptr, size_t count);
@@ -332,9 +346,9 @@ void varena_free(struct varena *varena, void *ptr, size_t count);
/**
* Arena-allocate a flexible struct.
*
- * @param varena
+ * @varena
* The varena to allocate from.
- * @param count
+ * @count
* The length of the flexible array.
* @return
* The allocated struct, or NULL on failure.
@@ -345,13 +359,13 @@ void *varena_alloc(struct varena *varena, size_t count);
/**
* Resize a flexible struct.
*
- * @param varena
+ * @varena
* The varena to allocate from.
- * @param ptr
+ * @ptr
* The object to resize.
- * @param old_count
- * The old array lenth.
- * @param new_count
+ * @old_count
+ * The old array length.
+ * @new_count
* The new array length.
* @return
* The resized struct, or NULL on failure.
@@ -362,11 +376,11 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t
/**
* Grow a flexible struct by an arbitrary amount.
*
- * @param varena
+ * @varena
* The varena to allocate from.
- * @param ptr
+ * @ptr
* The object to resize.
- * @param count
+ * @count
* Pointer to the flexible array length.
* @return
* The resized struct, or NULL on failure.
diff --git a/src/atomic.h b/src/atomic.h
index 2682c29..5c2826f 100644
--- a/src/atomic.h
+++ b/src/atomic.h
@@ -20,9 +20,9 @@
/**
* Shorthand for atomic_load_explicit().
*
- * @param obj
+ * @obj
* A pointer to the atomic object.
- * @param order
+ * @order
* The memory ordering to use, without the memory_order_ prefix.
* @return
* The loaded value.
diff --git a/src/bar.h b/src/bar.h
index 20d92a9..ec9e590 100644
--- a/src/bar.h
+++ b/src/bar.h
@@ -27,9 +27,9 @@ unsigned int bfs_bar_width(const struct bfs_bar *bar);
/**
* Update the status bar message.
*
- * @param bar
+ * @bar
* The status bar to update.
- * @param str
+ * @str
* The string to display.
* @return
* 0 on success, -1 on failure.
diff --git a/src/bfs.h b/src/bfs.h
index 91c6540..af4cf9f 100644
--- a/src/bfs.h
+++ b/src/bfs.h
@@ -33,10 +33,15 @@
#ifndef BFS_COMMAND
# define BFS_COMMAND "bfs"
#endif
+
#ifndef BFS_HOMEPAGE
# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html"
#endif
+#ifndef BFS_LINT
+# define BFS_LINT false
+#endif
+
// This is a symbol instead of a literal so we don't have to rebuild everything
// when the version number changes
extern const char bfs_version[];
@@ -199,6 +204,8 @@ extern const char bfs_ldlibs[];
#ifndef BFS_USE_TARGET_CLONES
# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) && !__SANITIZE_THREAD__
# define BFS_USE_TARGET_CLONES true
+# else
+# define BFS_USE_TARGET_CLONES false
# endif
#endif
diff --git a/src/bfstd.h b/src/bfstd.h
index 557f253..97867fd 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -48,9 +48,9 @@
* Check if an error code is "like" another one. For example, ENOTDIR is
* like ENOENT because they can both be triggered by non-existent paths.
*
- * @param error
+ * @error
* The error code to check.
- * @param category
+ * @category
* The category to test for. Known categories include ENOENT and
* ENAMETOOLONG.
* @return
@@ -66,7 +66,7 @@ bool errno_is_like(int category);
/**
* Apply the "negative errno" convention.
*
- * @param ret
+ * @ret
* The return value of the attempted operation.
* @return
* ret, if non-negative, otherwise -errno.
@@ -106,7 +106,7 @@ int try(int ret);
/**
* Re-entrant dirname() variant that always allocates a copy.
*
- * @param path
+ * @path
* The path in question.
* @return
* The parent directory of the path.
@@ -116,7 +116,7 @@ char *xdirname(const char *path);
/**
* Re-entrant basename() variant that always allocates a copy.
*
- * @param path
+ * @path
* The path in question.
* @return
* The final component of the path.
@@ -126,7 +126,7 @@ char *xbasename(const char *path);
/**
* Find the offset of the final component of a path.
*
- * @param path
+ * @path
* The path in question.
* @return
* The offset of the basename.
@@ -138,9 +138,9 @@ size_t xbaseoff(const char *path);
/**
* fopen() variant that takes open() style flags.
*
- * @param path
+ * @path
* The path to open.
- * @param flags
+ * @flags
* Flags to pass to open().
*/
FILE *xfopen(const char *path, int flags);
@@ -148,9 +148,9 @@ FILE *xfopen(const char *path, int flags);
/**
* Convenience wrapper for getdelim().
*
- * @param file
+ * @file
* The file to read.
- * @param delim
+ * @delim
* The delimiter character to split on.
* @return
* The read chunk (without the delimiter), allocated with malloc().
@@ -161,7 +161,7 @@ char *xgetdelim(FILE *file, char delim);
/**
* Open the controlling terminal.
*
- * @param flags
+ * @flags
* The open() flags.
* @return
* An open file descriptor, or -1 on failure.
@@ -181,14 +181,14 @@ const char *xgetprogname(void);
/**
* Wrapper for strtoll() that forbids leading spaces.
*
- * @param str
+ * @str
* The string to parse.
- * @param end
+ * @end
* If non-NULL, will hold a pointer to the first invalid character.
* If NULL, the entire string must be valid.
- * @param base
+ * @base
* The base for the conversion, or 0 to auto-detect.
- * @param value
+ * @value
* Will hold the parsed integer value, on success.
* @return
* 0 on success, -1 on failure.
@@ -212,9 +212,9 @@ size_t asciilen(const char *str);
/**
* Get the length of the pure-ASCII prefix of a string.
*
- * @param str
+ * @str
* The string to check.
- * @param n
+ * @n
* The maximum prefix length.
*/
size_t asciinlen(const char *str, size_t n);
@@ -222,9 +222,9 @@ size_t asciinlen(const char *str, size_t n);
/**
* Allocate a copy of a region of memory.
*
- * @param src
+ * @src
* The memory region to copy.
- * @param size
+ * @size
* The size of the memory region.
* @return
* A copy of the region, allocated with malloc(), or NULL on failure.
@@ -234,12 +234,12 @@ void *xmemdup(const void *src, size_t size);
/**
* A nice string copying function.
*
- * @param dest
+ * @dest
* The NUL terminator of the destination string, or `end` if it is
* already truncated.
- * @param end
+ * @end
* The end of the destination buffer.
- * @param src
+ * @src
* The string to copy from.
* @return
* The new NUL terminator of the destination, or `end` on truncation.
@@ -249,14 +249,14 @@ char *xstpecpy(char *dest, char *end, const char *src);
/**
* A nice string copying function.
*
- * @param dest
+ * @dest
* The NUL terminator of the destination string, or `end` if it is
* already truncated.
- * @param end
+ * @end
* The end of the destination buffer.
- * @param src
+ * @src
* The string to copy from.
- * @param n
+ * @n
* The maximum number of characters to copy.
* @return
* The new NUL terminator of the destination, or `end` on truncation.
@@ -266,7 +266,7 @@ char *xstpencpy(char *dest, char *end, const char *src, size_t n);
/**
* Thread-safe strerror().
*
- * @param errnum
+ * @errnum
* An error number.
* @return
* A string describing that error, which remains valid until the next
@@ -282,9 +282,9 @@ const char *errstr(void);
/**
* Format a mode like ls -l (e.g. -rw-r--r--).
*
- * @param mode
+ * @mode
* The mode to format.
- * @param str
+ * @str
* The string to hold the formatted mode.
*/
void xstrmode(mode_t mode, char str[11]);
@@ -344,7 +344,7 @@ pid_t xwaitpid(pid_t pid, int *status, int flags);
/**
* Like dup(), but set the FD_CLOEXEC flag.
*
- * @param fd
+ * @fd
* The file descriptor to duplicate.
* @return
* A duplicated file descriptor, or -1 on failure.
@@ -354,7 +354,7 @@ int dup_cloexec(int fd);
/**
* Like pipe(), but set the FD_CLOEXEC flag.
*
- * @param pipefd
+ * @pipefd
* The array to hold the two file descriptors.
* @return
* 0 on success, -1 on failure.
@@ -383,7 +383,7 @@ size_t xwrite(int fd, const void *buf, size_t nbytes);
/**
* close() variant that preserves errno.
*
- * @param fd
+ * @fd
* The file descriptor to close.
*/
void close_quietly(int fd);
@@ -391,7 +391,7 @@ void close_quietly(int fd);
/**
* close() wrapper that asserts the file descriptor is valid.
*
- * @param fd
+ * @fd
* The file descriptor to close.
* @return
* 0 on success, or -1 on error.
@@ -406,11 +406,11 @@ int xfaccessat(int fd, const char *path, int amode);
/**
* readlinkat() wrapper that dynamically allocates the result.
*
- * @param fd
+ * @fd
* The base directory descriptor.
- * @param path
+ * @path
* The path to the link, relative to fd.
- * @param size
+ * @size
* An estimate for the size of the link name (pass 0 if unknown).
* @return
* The target of the link, allocated with malloc(), or NULL on failure.
@@ -420,7 +420,7 @@ char *xreadlinkat(int fd, const char *path, size_t size);
/**
* Wrapper for confstr() that allocates with malloc().
*
- * @param name
+ * @name
* The ID of the confstr to look up.
* @return
* The value of the confstr, or NULL on failure.
@@ -430,12 +430,12 @@ char *xconfstr(int name);
/**
* Portability wrapper for strtofflags().
*
- * @param str
+ * @str
* The string to parse. The pointee will be advanced to the first
* invalid position on error.
- * @param set
+ * @set
* The flags that are set in the string.
- * @param clear
+ * @clear
* The flags that are cleared in the string.
* @return
* 0 on success, -1 on failure.
@@ -452,7 +452,7 @@ long xsysconf(int name);
*
* [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap02.html#tag_02_01_06
*
- * @param name
+ * @name
* The symbolic name of the POSIX option (e.g. SPAWN).
* @return
* The value of the option, either -1 or a date like 202405.
@@ -465,13 +465,13 @@ long xsysconf(int name);
/**
* Error-recovering mbrtowc() wrapper.
*
- * @param str
+ * @str
* The string to convert.
- * @param i
+ * @i
* The current index.
- * @param len
+ * @len
* The length of the string.
- * @param mb
+ * @mb
* The multi-byte decoding state.
* @return
* The wide character at index *i, or WEOF if decoding fails. In either
@@ -482,7 +482,7 @@ wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb);
/**
* wcswidth() variant that works on narrow strings.
*
- * @param str
+ * @str
* The string to measure.
* @return
* The likely width of that string in a terminal.
@@ -534,13 +534,13 @@ enum wesc_flags {
/**
* Escape a string as a single shell word.
*
- * @param dest
+ * @dest
* The destination string to fill.
- * @param end
+ * @end
* The end of the destination buffer.
- * @param src
+ * @src
* The string to escape.
- * @param flags
+ * @flags
* Controls which characters to escape.
* @return
* The new NUL terminator of the destination, or `end` on truncation.
@@ -550,15 +550,15 @@ char *wordesc(char *dest, char *end, const char *str, enum wesc_flags flags);
/**
* Escape a string as a single shell word.
*
- * @param dest
+ * @dest
* The destination string to fill.
- * @param end
+ * @end
* The end of the destination buffer.
- * @param src
+ * @src
* The string to escape.
- * @param n
+ * @n
* The maximum length of the string.
- * @param flags
+ * @flags
* Controls which characters to escape.
* @return
* The new NUL terminator of the destination, or `end` on truncation.
diff --git a/src/bftw.c b/src/bftw.c
index 64991b0..61193d5 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -448,7 +448,7 @@ static void bftw_queue_rebalance(struct bftw_queue *queue, bool async) {
}
}
-/** Detatch the next waiting file. */
+/** Detach the next waiting file. */
static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file, bool async) {
bfs_assert(!file->ioqueued);
@@ -1162,12 +1162,13 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons
struct bftw_list parents;
SLIST_INIT(&parents);
- struct bftw_file *cur;
- for (cur = file; cur != base; cur = cur->parent) {
+ // Reverse the chain of parents
+ for (struct bftw_file *cur = file; cur != base; cur = cur->parent) {
SLIST_PREPEND(&parents, cur);
}
- while ((cur = SLIST_POP(&parents))) {
+ // Open each component relative to its parent
+ drain_slist (struct bftw_file, cur, &parents) {
if (!cur->parent || cur->parent->fd >= 0) {
bftw_file_openat(state, cur, cur->parent, cur->name);
}
@@ -1870,8 +1871,8 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) {
}
state->direrror = 0;
- while ((file = SLIST_POP(&state->to_close, ready))) {
- bftw_unwrapdir(state, file);
+ drain_slist (struct bftw_file, dead, &state->to_close, ready) {
+ bftw_unwrapdir(state, dead);
}
enum bftw_gc_flags visit = BFTW_VISIT_FILE;
diff --git a/src/bftw.h b/src/bftw.h
index 28e0679..8b3ed7f 100644
--- a/src/bftw.h
+++ b/src/bftw.h
@@ -75,9 +75,9 @@ struct BFTW {
* Get bfs_stat() info for a file encountered during bftw(), caching the result
* whenever possible.
*
- * @param ftwbuf
+ * @ftwbuf
* bftw() data for the file to stat.
- * @param flags
+ * @flags
* flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags.
* @return
* A pointer to a bfs_stat() buffer, or NULL if the call failed.
@@ -88,9 +88,9 @@ const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags
* Get bfs_stat() info for a file encountered during bftw(), if it has already
* been cached.
*
- * @param ftwbuf
+ * @ftwbuf
* bftw() data for the file to stat.
- * @param flags
+ * @flags
* flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags.
* @return
* A pointer to a bfs_stat() buffer, or NULL if no stat info is cached.
@@ -102,9 +102,9 @@ const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat
* whether to follow links. This function will avoid calling bfs_stat() if
* possible.
*
- * @param ftwbuf
+ * @ftwbuf
* bftw() data for the file to check.
- * @param flags
+ * @flags
* flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags.
* @return
* The type of the file, or BFS_ERROR if an error occurred.
@@ -126,9 +126,9 @@ enum bftw_action {
/**
* Callback function type for bftw().
*
- * @param ftwbuf
+ * @ftwbuf
* Data about the current file.
- * @param ptr
+ * @ptr
* The pointer passed to bftw().
* @return
* An action value.
@@ -211,7 +211,7 @@ struct bftw_args {
* Like ftw(3) and nftw(3), this function walks a directory tree recursively,
* and invokes a callback for each path it encounters.
*
- * @param args
+ * @args
* The arguments that control the walk.
* @return
* 0 on success, or -1 on failure.
diff --git a/src/bit.h b/src/bit.h
index c7530f1..73a80dc 100644
--- a/src/bit.h
+++ b/src/bit.h
@@ -198,15 +198,35 @@ static inline uint8_t bswap_u8(uint8_t n) {
return n;
}
-/**
- * Reverse the byte order of an integer.
- */
-#define bswap(n) \
- _Generic((n), \
- uint8_t: bswap_u8, \
- uint16_t: bswap_u16, \
- uint32_t: bswap_u32, \
- uint64_t: bswap_u64)(n)
+#if UCHAR_WIDTH == 8
+# define bswap_uc bswap_u8
+#endif
+
+#if USHRT_WIDTH == 16
+# define bswap_us bswap_u16
+#elif USHRT_WIDTH == 32
+# define bswap_us bswap_u32
+#elif USHRT_WIDTH == 64
+# define bswap_us bswap_u64
+#endif
+
+#if UINT_WIDTH == 16
+# define bswap_ui bswap_u16
+#elif UINT_WIDTH == 32
+# define bswap_ui bswap_u32
+#elif UINT_WIDTH == 64
+# define bswap_ui bswap_u64
+#endif
+
+#if ULONG_WIDTH == 32
+# define bswap_ul bswap_u32
+#elif ULONG_WIDTH == 64
+# define bswap_ul bswap_u64
+#endif
+
+#if ULLONG_WIDTH == 64
+# define bswap_ull bswap_u64
+#endif
// Define an overload for each unsigned type
#define UINT_OVERLOADS(macro) \
@@ -225,6 +245,11 @@ static inline uint8_t bswap_u8(uint8_t n) {
unsigned long: name##_ul, \
unsigned long long: name##_ull)
+/**
+ * Reverse the byte order of an integer.
+ */
+#define bswap(n) UINT_SELECT(n, bswap)(n)
+
// C23 polyfill: bit utilities
#if __STDC_VERSION_STDBIT_H__ >= C23
diff --git a/src/color.c b/src/color.c
index 7f653b0..e7f0973 100644
--- a/src/color.c
+++ b/src/color.c
@@ -31,7 +31,7 @@
struct esc_seq {
/** The length of the escape sequence. */
size_t len;
- /** The escape sequence iteself, without a terminating NUL. */
+ /** The escape sequence itself, without a terminating NUL. */
char seq[];
};
@@ -237,6 +237,16 @@ static int insert_ext(struct trie *trie, struct ext_color *ext) {
/** Set the color for an extension. */
static int set_ext(struct colors *colors, dchar *key, dchar *value) {
size_t len = dstrlen(key);
+
+ // Embedded NUL bytes in extensions can lead to a non-prefix-free
+ // set of strings, e.g. {".gz", "\0.gz"} would be transformed to
+ // {"zg.\0", "zg.\0\0"} (showing the implicit terminating NUL).
+ // Our trie implementation only supports prefix-free key sets, but
+ // luckily '\0' cannot appear in filenames so we can ignore them.
+ if (memchr(key, '\0', len)) {
+ return 0;
+ }
+
struct ext_color *ext = varena_alloc(&colors->ext_arena, len + 1);
if (!ext) {
return -1;
@@ -277,9 +287,9 @@ fail:
/**
* The "smart case" algorithm.
*
- * @param ext
+ * @ext
* The current extension being added.
- * @param iext
+ * @iext
* The previous case-insensitive match, if any, for the same extension.
* @return
* Whether this extension should become case-sensitive.
@@ -359,9 +369,8 @@ static int build_iext_trie(struct colors *colors) {
/**
* Find a color by an extension.
*/
-static const struct esc_seq *get_ext(const struct colors *colors, const char *filename) {
+static const struct esc_seq *get_ext(const struct colors *colors, const char *filename, size_t name_len) {
size_t ext_len = colors->ext_len;
- size_t name_len = strlen(filename);
if (name_len < ext_len) {
ext_len = name_len;
}
@@ -370,7 +379,8 @@ static const struct esc_seq *get_ext(const struct colors *colors, const char *fi
char buf[256];
char *copy;
if (ext_len < sizeof(buf)) {
- copy = memcpy(buf, suffix, ext_len + 1);
+ copy = memcpy(buf, suffix, ext_len);
+ copy[ext_len] = '\0';
} else {
copy = strndup(suffix, ext_len);
if (!copy) {
@@ -418,13 +428,13 @@ static const struct esc_seq *get_ext(const struct colors *colors, const char *fi
*
* See man dir_colors.
*
- * @param str
+ * @str
* A dstring to fill with the unescaped chunk.
- * @param value
+ * @value
* The value to parse.
- * @param end
+ * @end
* The character that marks the end of the chunk.
- * @param[out] next
+ * @next[out]
* Will be set to the next chunk.
* @return
* 0 on success, -1 on failure.
@@ -771,34 +781,207 @@ int cfclose(CFILE *cfile) {
return ret;
}
+bool colors_need_stat(const struct colors *colors) {
+ return colors->setuid || colors->setgid || colors->executable || colors->multi_hard
+ || colors->sticky_other_writable || colors->other_writable || colors->sticky;
+}
+
+/** A colorable file path. */
+struct cpath {
+ /** The full path to color. */
+ const char *path;
+ /** The basename offset of the last valid component. */
+ size_t nameoff;
+ /** The end offset of the last valid component. */
+ size_t valid;
+ /** The total length of the path. */
+ size_t len;
+
+ /** The bftw() buffer. */
+ const struct BFTW *ftwbuf;
+ /** bfs_stat() flags for the final component. */
+ enum bfs_stat_flags flags;
+ /** A bfs_stat() buffer, filled in when 0 < valid < len. */
+ struct bfs_stat statbuf;
+};
+
+/** Move the valid range of a path backwards. */
+static void cpath_retreat(struct cpath *cpath) {
+ const char *path = cpath->path;
+ size_t nameoff = cpath->nameoff;
+ size_t valid = cpath->valid;
+
+ if (valid > 0 && path[valid - 1] == '/') {
+ // Try without trailing slashes, to distinguish "notdir/" from "notdir"
+ do {
+ --valid;
+ } while (valid > 0 && path[valid - 1] == '/');
+
+ nameoff = valid;
+ while (nameoff > 0 && path[nameoff - 1] != '/') {
+ --nameoff;
+ }
+ } else {
+ // Remove the last component and try again
+ valid = nameoff;
+ }
+
+ cpath->nameoff = nameoff;
+ cpath->valid = valid;
+}
+
+/** Initialize a struct cpath. */
+static int cpath_init(struct cpath *cpath, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
+ // Normally there are only two components to color:
+ //
+ // nameoff valid
+ // v v
+ // path/to/filename
+ // --------+-------
+ // ${di} ${fi}
+ //
+ // Error cases also usually have two components:
+ //
+ // valid,
+ // nameoff
+ // v
+ // path/to/nowhere
+ // --------+------
+ // ${di} ${mi}
+ //
+ // But with ENOTDIR, there may be three:
+ //
+ // nameoff valid
+ // v v
+ // path/to/filename/nowhere
+ // --------+-------+-------
+ // ${di} ${fi} ${mi}
+
+ cpath->path = path;
+ cpath->len = strlen(path);
+ cpath->ftwbuf = ftwbuf;
+ cpath->flags = flags;
+
+ cpath->valid = cpath->len;
+ if (path == ftwbuf->path) {
+ cpath->nameoff = ftwbuf->nameoff;
+ } else {
+ cpath->nameoff = xbaseoff(path);
+ }
+
+ if (bftw_type(ftwbuf, flags) != BFS_ERROR) {
+ return 0;
+ }
+
+ cpath_retreat(cpath);
+
+ // Find the base path. For symlinks like
+ //
+ // path/to/symlink -> nested/file
+ //
+ // this will be something like
+ //
+ // path/to/nested/file
+ int at_fd = AT_FDCWD;
+ dchar *at_path = NULL;
+ if (path == ftwbuf->path) {
+ if (ftwbuf->depth > 0) {
+ // The parent must have existed to get here
+ return 0;
+ }
+ } else {
+ // We're in print_link_target(), so resolve relative to the link's parent directory
+ at_fd = ftwbuf->at_fd;
+ if (at_fd == (int)AT_FDCWD && path[0] != '/') {
+ at_path = dstrxdup(ftwbuf->path, ftwbuf->nameoff);
+ if (!at_path) {
+ return -1;
+ }
+ }
+ }
+
+ if (!at_path) {
+ at_path = dstralloc(cpath->valid);
+ if (!at_path) {
+ return -1;
+ }
+ }
+ if (dstrxcat(&at_path, path, cpath->valid) != 0) {
+ dstrfree(at_path);
+ return -1;
+ }
+
+ size_t at_off = dstrlen(at_path) - cpath->valid;
+
+ // Find the longest valid path prefix
+ while (cpath->valid > 0) {
+ if (bfs_stat(at_fd, at_path, BFS_STAT_FOLLOW, &cpath->statbuf) == 0) {
+ break;
+ }
+
+ cpath_retreat(cpath);
+ dstrshrink(at_path, at_off + cpath->valid);
+ }
+
+ dstrfree(at_path);
+ return 0;
+}
+
+/** Get the bfs_stat() buffer for the last valid component. */
+static const struct bfs_stat *cpath_stat(const struct cpath *cpath) {
+ if (cpath->valid == cpath->len) {
+ return bftw_stat(cpath->ftwbuf, cpath->flags);
+ } else {
+ return &cpath->statbuf;
+ }
+}
+
+/** Check if a path has non-trivial capabilities. */
+static bool cpath_has_capabilities(const struct cpath *cpath) {
+ if (cpath->valid == cpath->len) {
+ return bfs_check_capabilities(cpath->ftwbuf) > 0;
+ } else {
+ // TODO: implement capability checks for arbitrary paths
+ return false;
+ }
+}
+
/** Check if a symlink is broken. */
-static bool is_link_broken(const struct BFTW *ftwbuf) {
+static bool cpath_is_broken(const struct cpath *cpath) {
+ if (cpath->valid < cpath->len) {
+ // A valid parent can't be a broken link
+ return false;
+ }
+
+ const struct BFTW *ftwbuf = cpath->ftwbuf;
if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) {
return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) != 0;
} else {
+ // A link encountered with BFS_STAT_TRYFOLLOW must be broken
return true;
}
}
-bool colors_need_stat(const struct colors *colors) {
- return colors->setuid || colors->setgid || colors->executable || colors->multi_hard
- || colors->sticky_other_writable || colors->other_writable || colors->sticky;
-}
-
/** Get the color for a file. */
-static const struct esc_seq *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
- enum bfs_type type = bftw_type(ftwbuf, flags);
+static const struct esc_seq *file_color(const struct colors *colors, const struct cpath *cpath) {
+ enum bfs_type type;
+ if (cpath->valid == cpath->len) {
+ type = bftw_type(cpath->ftwbuf, cpath->flags);
+ } else {
+ type = bfs_mode_to_type(cpath->statbuf.mode);
+ }
+
if (type == BFS_ERROR) {
goto error;
}
- const struct bfs_stat *statbuf = NULL;
+ const struct bfs_stat *statbuf;
const struct esc_seq *color = NULL;
switch (type) {
case BFS_REG:
if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) {
- statbuf = bftw_stat(ftwbuf, flags);
+ statbuf = cpath_stat(cpath);
if (!statbuf) {
goto error;
}
@@ -808,7 +991,7 @@ static const struct esc_seq *file_color(const struct colors *colors, const char
color = colors->setuid;
} else if (colors->setgid && (statbuf->mode & 02000)) {
color = colors->setgid;
- } else if (colors->capable && bfs_check_capabilities(ftwbuf) > 0) {
+ } else if (colors->capable && cpath_has_capabilities(cpath)) {
color = colors->capable;
} else if (colors->executable && (statbuf->mode & 00111)) {
color = colors->executable;
@@ -817,7 +1000,9 @@ static const struct esc_seq *file_color(const struct colors *colors, const char
}
if (!color) {
- color = get_ext(colors, filename);
+ const char *name = cpath->path + cpath->nameoff;
+ size_t namelen = cpath->valid - cpath->nameoff;
+ color = get_ext(colors, name, namelen);
}
if (!color) {
@@ -828,7 +1013,7 @@ static const struct esc_seq *file_color(const struct colors *colors, const char
case BFS_DIR:
if (colors->sticky_other_writable || colors->other_writable || colors->sticky) {
- statbuf = bftw_stat(ftwbuf, flags);
+ statbuf = cpath_stat(cpath);
if (!statbuf) {
goto error;
}
@@ -847,7 +1032,7 @@ static const struct esc_seq *file_color(const struct colors *colors, const char
break;
case BFS_LNK:
- if (colors->orphan && is_link_broken(ftwbuf)) {
+ if (colors->orphan && cpath_is_broken(cpath)) {
color = colors->orphan;
} else {
color = colors->link;
@@ -934,6 +1119,10 @@ static int print_wordesc(CFILE *cfile, const char *str, size_t n, enum wesc_flag
/** Print a string with an optional color. */
static int print_colored(CFILE *cfile, const struct esc_seq *esc, const char *str, size_t len) {
+ if (len == 0) {
+ return 0;
+ }
+
if (print_esc(cfile, esc) != 0) {
return -1;
}
@@ -950,112 +1139,42 @@ static int print_colored(CFILE *cfile, const struct esc_seq *esc, const char *st
return 0;
}
-/** Find the offset of the first broken path component. */
-static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, size_t max) {
- ssize_t ret = max;
- bfs_assert(ret >= 0);
-
- if (bftw_type(ftwbuf, flags) != BFS_ERROR) {
- goto out;
- }
-
- dchar *at_path;
- int at_fd;
- if (path == ftwbuf->path) {
- if (ftwbuf->depth == 0) {
- at_fd = AT_FDCWD;
- at_path = dstrxdup(path, max);
- } else {
- // The parent must have existed to get here
- goto out;
- }
- } else {
- // We're in print_link_target(), so resolve relative to the link's parent directory
- at_fd = ftwbuf->at_fd;
- if (at_fd == (int)AT_FDCWD && path[0] != '/') {
- at_path = dstrxdup(ftwbuf->path, ftwbuf->nameoff);
- if (at_path && dstrxcat(&at_path, path, max) != 0) {
- ret = -1;
- goto out_path;
- }
- } else {
- at_path = dstrxdup(path, max);
- }
- }
-
- if (!at_path) {
- ret = -1;
- goto out;
- }
-
- while (ret > 0) {
- if (xfaccessat(at_fd, at_path, F_OK) == 0) {
- break;
- }
-
- size_t len = dstrlen(at_path);
- while (ret && at_path[len - 1] == '/') {
- --len, --ret;
- }
- if (errno != ENOTDIR) {
- while (ret && at_path[len - 1] != '/') {
- --len, --ret;
- }
- }
-
- dstresize(&at_path, len);
- }
-
-out_path:
- dstrfree(at_path);
-out:
- return ret;
-}
-
/** Print a path with colors. */
static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
- size_t nameoff;
- if (path == ftwbuf->path) {
- nameoff = ftwbuf->nameoff;
- } else {
- nameoff = xbaseoff(path);
- }
-
- const char *name = path + nameoff;
- size_t pathlen = nameoff + strlen(name);
-
- ssize_t broken = first_broken_offset(path, ftwbuf, flags, nameoff);
- if (broken < 0) {
+ struct cpath cpath;
+ if (cpath_init(&cpath, path, ftwbuf, flags) != 0) {
return -1;
}
- size_t split = broken;
const struct colors *colors = cfile->colors;
const struct esc_seq *dirs_color = colors->directory;
- const struct esc_seq *name_color;
+ const struct esc_seq *name_color = NULL;
+ const struct esc_seq *err_color = colors->missing;
+ if (!err_color) {
+ err_color = colors->orphan;
+ }
- if (split < nameoff) {
- name_color = colors->missing;
- if (!name_color) {
- name_color = colors->orphan;
- }
- } else {
- name_color = file_color(cfile->colors, path + nameoff, ftwbuf, flags);
+ if (cpath.nameoff < cpath.valid) {
+ name_color = file_color(colors, &cpath);
if (name_color == dirs_color) {
- split = pathlen;
+ cpath.nameoff = cpath.valid;
}
}
- if (split > 0) {
- if (print_colored(cfile, dirs_color, path, split) != 0) {
- return -1;
- }
+ if (print_colored(cfile, dirs_color, path, cpath.nameoff) != 0) {
+ return -1;
}
- if (split < pathlen) {
- if (print_colored(cfile, name_color, path + split, pathlen - split) != 0) {
- return -1;
- }
+ const char *name = path + cpath.nameoff;
+ size_t name_len = cpath.valid - cpath.nameoff;
+ if (print_colored(cfile, name_color, name, name_len) != 0) {
+ return -1;
+ }
+
+ const char *tail = path + cpath.valid;
+ size_t tail_len = cpath.len - cpath.valid;
+ if (print_colored(cfile, err_color, tail, tail_len) != 0) {
+ return -1;
}
return 0;
@@ -1063,8 +1182,18 @@ static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW
/** Print a file name with colors. */
static int print_name_colored(CFILE *cfile, const char *name, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
- const struct esc_seq *esc = file_color(cfile->colors, name, ftwbuf, flags);
- return print_colored(cfile, esc, name, strlen(name));
+ size_t len = strlen(name);
+ const struct cpath cpath = {
+ .path = name,
+ .nameoff = 0,
+ .valid = len,
+ .len = len,
+ .ftwbuf = ftwbuf,
+ .flags = flags,
+ };
+
+ const struct esc_seq *esc = file_color(cfile->colors, &cpath);
+ return print_colored(cfile, esc, name, cpath.len);
}
/** Print the name of a file with the appropriate colors. */
@@ -1389,7 +1518,7 @@ int cvfprintf(CFILE *cfile, const char *format, va_list args) {
}
}
- dstresize(&cfile->buffer, 0);
+ dstrshrink(cfile->buffer, 0);
return ret;
}
diff --git a/src/color.h b/src/color.h
index 38f5ad7..2394af2 100644
--- a/src/color.h
+++ b/src/color.h
@@ -54,11 +54,11 @@ typedef struct CFILE {
/**
* Wrap an existing file into a colored stream.
*
- * @param file
+ * @file
* The underlying file.
- * @param colors
+ * @colors
* The color table to use if file is a TTY.
- * @param close
+ * @close
* Whether to close the underlying stream when this stream is closed.
* @return
* A colored wrapper around file.
@@ -68,7 +68,7 @@ CFILE *cfwrap(FILE *file, const struct colors *colors, bool close);
/**
* Close a colored file.
*
- * @param cfile
+ * @cfile
* The colored file to close.
* @return
* 0 on success, -1 on failure.
@@ -78,9 +78,9 @@ int cfclose(CFILE *cfile);
/**
* Colored, formatted output.
*
- * @param cfile
+ * @cfile
* The colored stream to print to.
- * @param format
+ * @format
* A printf()-style format string, supporting these format specifiers:
*
* %c: A single character
diff --git a/src/ctx.h b/src/ctx.h
index e532550..be6e2af 100644
--- a/src/ctx.h
+++ b/src/ctx.h
@@ -129,7 +129,7 @@ struct bfs_ctx *bfs_ctx_new(void);
/**
* Get the mount table.
*
- * @param ctx
+ * @ctx
* The bfs context.
* @return
* The cached mount table, or NULL on failure.
@@ -139,11 +139,11 @@ const struct bfs_mtab *bfs_ctx_mtab(const struct bfs_ctx *ctx);
/**
* Deduplicate an opened file.
*
- * @param ctx
+ * @ctx
* The bfs context.
- * @param cfile
+ * @cfile
* The opened file.
- * @param path
+ * @path
* The path to the opened file (or NULL for standard streams).
* @return
* If the same file was opened previously, that file is returned. If cfile is a new file,
@@ -154,7 +154,7 @@ struct CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, struct CFILE *cfile, const char
/**
* Flush any caches for consistency with external processes.
*
- * @param ctx
+ * @ctx
* The bfs context.
*/
void bfs_ctx_flush(const struct bfs_ctx *ctx);
@@ -162,9 +162,9 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx);
/**
* Dump the parsed command line.
*
- * @param ctx
+ * @ctx
* The bfs context.
- * @param flag
+ * @flag
* The -D flag that triggered the dump.
*/
void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag);
@@ -172,7 +172,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag);
/**
* Free a bfs context.
*
- * @param ctx
+ * @ctx
* The context to free.
* @return
* 0 on success, -1 if any errors occurred.
diff --git a/src/dir.h b/src/dir.h
index 4482003..885dac3 100644
--- a/src/dir.h
+++ b/src/dir.h
@@ -21,6 +21,8 @@
# define BFS_USE_GETDENTS true
# elif __linux__ || __FreeBSD__
# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL)
+# else
+# define BFS_USE_GETDENTS false
# endif
#endif
@@ -87,7 +89,7 @@ struct arena;
/**
* Initialize an arena for directories.
*
- * @param arena
+ * @arena
* The arena to initialize.
*/
void bfs_dir_arena(struct arena *arena);
@@ -105,14 +107,14 @@ enum bfs_dir_flags {
/**
* Open a directory.
*
- * @param dir
+ * @dir
* The allocated directory.
- * @param at_fd
+ * @at_fd
* The base directory for path resolution.
- * @param at_path
+ * @at_path
* The path of the directory to open, relative to at_fd. Pass NULL to
* open at_fd itself.
- * @param flags
+ * @flags
* Flags that control which directory entries are listed.
* @return
* 0 on success, or -1 on failure.
@@ -127,7 +129,7 @@ int bfs_dirfd(const struct bfs_dir *dir);
/**
* Performs any I/O necessary for the next bfs_readdir() call.
*
- * @param dir
+ * @dir
* The directory to poll.
* @return
* 1 on success, 0 on EOF, or -1 on failure.
@@ -137,9 +139,9 @@ int bfs_polldir(struct bfs_dir *dir);
/**
* Read a directory entry.
*
- * @param dir
+ * @dir
* The directory to read.
- * @param[out] dirent
+ * @dirent[out]
* The directory entry to populate.
* @return
* 1 on success, 0 on EOF, or -1 on failure.
@@ -165,7 +167,7 @@ int bfs_closedir(struct bfs_dir *dir);
/**
* Detach the file descriptor from an open directory.
*
- * @param dir
+ * @dir
* The directory to detach.
* @return
* The file descriptor of the directory.
diff --git a/src/dstring.c b/src/dstring.c
index af499fd..0f08679 100644
--- a/src/dstring.c
+++ b/src/dstring.c
@@ -46,6 +46,13 @@ static dchar *dstrdata(struct dstring *header) {
return (char *)header + DSTR_OFFSET;
}
+/** Set the length of a dynamic string. */
+static void dstrsetlen(struct dstring *header, size_t len) {
+ bfs_assert(len < header->cap);
+ header->len = len;
+ header->str[len] = '\0';
+}
+
/** Allocate a dstring with the given contents. */
static dchar *dstralloc_impl(size_t cap, size_t len, const char *str) {
// Avoid reallocations for small strings
@@ -59,11 +66,10 @@ static dchar *dstralloc_impl(size_t cap, size_t len, const char *str) {
}
header->cap = cap;
- header->len = len;
+ dstrsetlen(header, len);
- char *ret = dstrdata(header);
+ dchar *ret = dstrdata(header);
memcpy(ret, str, len);
- ret[len] = '\0';
return ret;
}
@@ -121,11 +127,16 @@ int dstresize(dchar **dstr, size_t len) {
}
struct dstring *header = dstrheader(*dstr);
- header->len = len;
- header->str[len] = '\0';
+ dstrsetlen(header, len);
return 0;
}
+void dstrshrink(dchar *dstr, size_t len) {
+ struct dstring *header = dstrheader(dstr);
+ bfs_assert(len <= header->len);
+ dstrsetlen(header, len);
+}
+
int dstrcat(dchar **dest, const char *src) {
return dstrxcat(dest, src, strlen(src));
}
diff --git a/src/dstring.h b/src/dstring.h
index 79458e4..ce7ef86 100644
--- a/src/dstring.h
+++ b/src/dstring.h
@@ -31,7 +31,7 @@ typedef char dchar;
/**
* Free a dynamic string.
*
- * @param dstr
+ * @dstr
* The string to free.
*/
void dstrfree(dchar *dstr);
@@ -39,7 +39,7 @@ void dstrfree(dchar *dstr);
/**
* Allocate a dynamic string.
*
- * @param cap
+ * @cap
* The initial capacity of the string.
*/
_malloc(dstrfree, 1)
@@ -48,7 +48,7 @@ dchar *dstralloc(size_t cap);
/**
* Create a dynamic copy of a string.
*
- * @param str
+ * @str
* The NUL-terminated string to copy.
*/
_malloc(dstrfree, 1)
@@ -57,9 +57,9 @@ dchar *dstrdup(const char *str);
/**
* Create a length-limited dynamic copy of a string.
*
- * @param str
+ * @str
* The string to copy.
- * @param n
+ * @n
* The maximum number of characters to copy from str.
*/
_malloc(dstrfree, 1)
@@ -68,7 +68,7 @@ dchar *dstrndup(const char *str, size_t n);
/**
* Create a dynamic copy of a dynamic string.
*
- * @param dstr
+ * @dstr
* The dynamic string to copy.
*/
_malloc(dstrfree, 1)
@@ -77,9 +77,9 @@ dchar *dstrddup(const dchar *dstr);
/**
* Create an exact-sized dynamic copy of a string.
*
- * @param str
+ * @str
* The string to copy.
- * @param len
+ * @len
* The length of the string, which may include internal NUL bytes.
*/
_malloc(dstrfree, 1)
@@ -88,7 +88,7 @@ dchar *dstrxdup(const char *str, size_t len);
/**
* Get a dynamic string's length.
*
- * @param dstr
+ * @dstr
* The string to measure.
* @return
* The length of dstr.
@@ -98,9 +98,9 @@ size_t dstrlen(const dchar *dstr);
/**
* Reserve some capacity in a dynamic string.
*
- * @param dstr
+ * @dstr
* The dynamic string to preallocate.
- * @param cap
+ * @cap
* The new capacity for the string.
* @return
* 0 on success, -1 on failure.
@@ -110,219 +110,246 @@ int dstreserve(dchar **dstr, size_t cap);
/**
* Resize a dynamic string.
*
- * @param dstr
+ * @dstr
* The dynamic string to resize.
- * @param len
+ * @len
* The new length for the dynamic string.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstresize(dchar **dstr, size_t len);
/**
+ * Shrink a dynamic string.
+ *
+ * @dstr
+ * The dynamic string to shrink.
+ * @len
+ * The new length. Must not be greater than the current length.
+ */
+void dstrshrink(dchar *dstr, size_t len);
+
+/**
* Append to a dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The string to append.
* @return 0 on success, -1 on failure.
*/
+_nodiscard
int dstrcat(dchar **dest, const char *src);
/**
* Append to a dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The string to append.
- * @param n
+ * @n
* The maximum number of characters to take from src.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrncat(dchar **dest, const char *src, size_t n);
/**
* Append a dynamic string to another dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The dynamic string to append.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrdcat(dchar **dest, const dchar *src);
/**
* Append to a dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The string to append.
- * @param len
+ * @len
* The exact number of characters to take from src.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrxcat(dchar **dest, const char *src, size_t len);
/**
* Append a single character to a dynamic string.
*
- * @param str
+ * @str
* The string to append to.
- * @param c
+ * @c
* The character to append.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrapp(dchar **str, char c);
/**
* Copy a string into a dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The string to copy.
* @returns
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrcpy(dchar **dest, const char *str);
/**
* Copy a dynamic string into another one.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The dynamic string to copy.
* @returns
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrdcpy(dchar **dest, const dchar *str);
/**
* Copy a string into a dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The dynamic string to copy.
- * @param n
+ * @n
* The maximum number of characters to take from src.
* @returns
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrncpy(dchar **dest, const char *str, size_t n);
/**
* Copy a string into a dynamic string.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param src
+ * @src
* The dynamic string to copy.
- * @param len
+ * @len
* The exact number of characters to take from src.
* @returns
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrxcpy(dchar **dest, const char *str, size_t len);
/**
* Create a dynamic string from a format string.
*
- * @param format
+ * @format
* The format string to fill in.
- * @param ...
+ * @...
* Any arguments for the format string.
* @return
* The created string, or NULL on failure.
*/
+_nodiscard
_printf(1, 2)
dchar *dstrprintf(const char *format, ...);
/**
* Create a dynamic string from a format string and a va_list.
*
- * @param format
+ * @format
* The format string to fill in.
- * @param args
+ * @args
* The arguments for the format string.
* @return
* The created string, or NULL on failure.
*/
+_nodiscard
_printf(1, 0)
dchar *dstrvprintf(const char *format, va_list args);
/**
* Format some text onto the end of a dynamic string.
*
- * @param str
+ * @str
* The destination dynamic string.
- * @param format
+ * @format
* The format string to fill in.
- * @param ...
+ * @...
* Any arguments for the format string.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
_printf(2, 3)
int dstrcatf(dchar **str, const char *format, ...);
/**
* Format some text from a va_list onto the end of a dynamic string.
*
- * @param str
+ * @str
* The destination dynamic string.
- * @param format
+ * @format
* The format string to fill in.
- * @param args
+ * @args
* The arguments for the format string.
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
_printf(2, 0)
int dstrvcatf(dchar **str, const char *format, va_list args);
/**
* Concatenate while shell-escaping.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param str
+ * @str
* The string to escape.
- * @param flags
+ * @flags
* Flags for wordesc().
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrescat(dchar **dest, const char *str, enum wesc_flags flags);
/**
* Concatenate while shell-escaping.
*
- * @param dest
+ * @dest
* The destination dynamic string.
- * @param str
+ * @str
* The string to escape.
- * @param n
+ * @n
* The maximum length of the string.
- * @param flags
+ * @flags
* Flags for wordesc().
* @return
* 0 on success, -1 on failure.
*/
+_nodiscard
int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags);
/**
* Repeat a string n times.
*/
+_nodiscard
dchar *dstrepeat(const char *str, size_t n);
#endif // BFS_DSTRING_H
diff --git a/src/eval.c b/src/eval.c
index 9789b5d..6e9fffd 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -28,6 +28,7 @@
#include "stat.h"
#include "trie.h"
#include "xregex.h"
+#include "xtime.h"
#include <errno.h>
#include <fcntl.h>
@@ -42,6 +43,7 @@
#include <string.h>
#include <strings.h>
#include <sys/resource.h>
+#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -1146,20 +1148,7 @@ bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) {
}
/** Update the status bar. */
-static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct timespec *last_status, size_t count) {
- struct timespec now;
- if (eval_gettime(state, &now) == 0) {
- struct timespec elapsed = {0};
- timespec_elapsed(&elapsed, last_status, &now);
-
- // Update every 0.1s
- if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= 100000000L) {
- *last_status = now;
- } else {
- return;
- }
- }
-
+static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, size_t count) {
size_t width = bfs_bar_width(bar);
if (width < 3) {
return;
@@ -1175,7 +1164,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time
size_t rhslen = xstrwidth(rhs);
if (3 + rhslen > width) {
- dstresize(&rhs, 0);
+ dstrshrink(rhs, 0);
rhslen = 0;
}
@@ -1219,7 +1208,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time
}
pathwidth += cwidth;
}
- dstresize(&status, lhslen);
+ dstrshrink(status, lhslen);
if (dstrcat(&status, "...") != 0) {
goto out;
@@ -1389,9 +1378,13 @@ struct callback_args {
/** The status bar. */
struct bfs_bar *bar;
- /** The time of the last status update. */
- struct timespec last_status;
- /** Flag set by SIGINFO hook. */
+ /** The SIGALRM hook. */
+ struct sighook *alrm_hook;
+ /** The interval timer. */
+ struct timer *timer;
+ /** Flag set by SIGALRM. */
+ atomic bool alrm_flag;
+ /** Flag set by SIGINFO. */
atomic bool info_flag;
/** The number of files visited so far. */
@@ -1406,6 +1399,64 @@ struct callback_args {
int ret;
};
+/** Update the status bar in response to SIGALRM. */
+static void eval_sigalrm(int sig, siginfo_t *info, void *ptr) {
+ struct callback_args *args = ptr;
+ store(&args->alrm_flag, true, relaxed);
+}
+
+/** Show/hide the bar in response to SIGINFO. */
+static void eval_siginfo(int sig, siginfo_t *info, void *ptr) {
+ struct callback_args *args = ptr;
+ store(&args->info_flag, true, relaxed);
+}
+
+/** Show the status bar. */
+static void eval_show_bar(struct callback_args *args) {
+ args->alrm_hook = sighook(SIGALRM, eval_sigalrm, args, SH_CONTINUE);
+ if (!args->alrm_hook) {
+ goto fail;
+ }
+
+ args->bar = bfs_bar_show();
+ if (!args->bar) {
+ goto fail;
+ }
+
+ // Update the bar every 0.1s
+ struct timespec ival = { .tv_nsec = 100 * 1000 * 1000 };
+ args->timer = xtimer_start(&ival);
+ if (!args->timer) {
+ goto fail;
+ }
+
+ // Update the bar immediately
+ store(&args->alrm_flag, true, relaxed);
+
+ return;
+
+fail:
+ bfs_warning(args->ctx, "Couldn't show status bar: %s.\n\n", errstr());
+
+ bfs_bar_hide(args->bar);
+ args->bar = NULL;
+
+ sigunhook(args->alrm_hook);
+ args->alrm_hook = NULL;
+}
+
+/** Hide the status bar. */
+static void eval_hide_bar(struct callback_args *args) {
+ xtimer_stop(args->timer);
+ args->timer = NULL;
+
+ sigunhook(args->alrm_hook);
+ args->alrm_hook = NULL;
+
+ bfs_bar_hide(args->bar);
+ args->bar = NULL;
+}
+
/**
* bftw() callback.
*/
@@ -1426,18 +1477,14 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
// Check whether SIGINFO was delivered and show/hide the bar
if (exchange(&args->info_flag, false, relaxed)) {
if (args->bar) {
- bfs_bar_hide(args->bar);
- args->bar = NULL;
+ eval_hide_bar(args);
} else {
- args->bar = bfs_bar_show();
- if (!args->bar) {
- bfs_warning(ctx, "Couldn't show status bar: %s.\n", errstr());
- }
+ eval_show_bar(args);
}
}
- if (args->bar) {
- eval_status(&state, args->bar, &args->last_status, args->count);
+ if (exchange(&args->alrm_flag, false, relaxed)) {
+ eval_status(&state, args->bar, args->count);
}
if (ftwbuf->type == BFS_ERROR) {
@@ -1509,12 +1556,6 @@ done:
return state.action;
}
-/** Show/hide the bar in response to SIGINFO. */
-static void eval_siginfo(int sig, siginfo_t *info, void *ptr) {
- struct callback_args *args = ptr;
- store(&args->info_flag, true, relaxed);
-}
-
/** Raise RLIMIT_NOFILE if possible, and return the new limit. */
static int raise_fdlimit(struct bfs_ctx *ctx) {
rlim_t cur = ctx->orig_nofile.rlim_cur;
@@ -1671,10 +1712,7 @@ int bfs_eval(struct bfs_ctx *ctx) {
};
if (ctx->status) {
- args.bar = bfs_bar_show();
- if (!args.bar) {
- bfs_warning(ctx, "Couldn't show status bar: %s.\n\n", errstr());
- }
+ eval_show_bar(&args);
}
#ifdef SIGINFO
@@ -1752,7 +1790,9 @@ int bfs_eval(struct bfs_ctx *ctx) {
}
sigunhook(info_hook);
- bfs_bar_hide(args.bar);
+ if (args.bar) {
+ eval_hide_bar(&args);
+ }
if (ctx->ignore_errors && args.nerrors > 0) {
bfs_warning(ctx, "Suppressed errors: %zu\n", args.nerrors);
diff --git a/src/eval.h b/src/eval.h
index 48184c7..b038740 100644
--- a/src/eval.h
+++ b/src/eval.h
@@ -20,9 +20,9 @@ struct bfs_eval;
/**
* Expression evaluation function.
*
- * @param expr
+ * @expr
* The current expression.
- * @param state
+ * @state
* The current evaluation state.
* @return
* The result of the test.
@@ -32,7 +32,7 @@ typedef bool bfs_eval_fn(const struct bfs_expr *expr, struct bfs_eval *state);
/**
* Evaluate the command line.
*
- * @param ctx
+ * @ctx
* The bfs context to evaluate.
* @return
* EXIT_SUCCESS on success, otherwise on failure.
diff --git a/src/exec.h b/src/exec.h
index 9d4192d..1d8e75f 100644
--- a/src/exec.h
+++ b/src/exec.h
@@ -67,11 +67,11 @@ struct bfs_exec {
/**
* Parse an exec action.
*
- * @param argv
+ * @argv
* The (bfs) command line argument to parse.
- * @param flags
+ * @flags
* Any flags for this exec action.
- * @param ctx
+ * @ctx
* The bfs context.
* @return
* The parsed exec action, or NULL on failure.
@@ -81,9 +81,9 @@ struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs
/**
* Execute the command for a file.
*
- * @param execbuf
+ * @execbuf
* The parsed exec action.
- * @param ftwbuf
+ * @ftwbuf
* The bftw() data for the current file.
* @return 0 if the command succeeded, -1 if it failed. If the command could
* be executed, -1 is returned, and errno will be non-zero. For
@@ -94,7 +94,7 @@ int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf);
/**
* Finish executing any commands.
*
- * @param execbuf
+ * @execbuf
* The parsed exec action.
* @return 0 on success, -1 if any errors were encountered.
*/
diff --git a/src/expr.c b/src/expr.c
index 110e9b7..ca37ffc 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -68,8 +68,7 @@ void bfs_expr_append(struct bfs_expr *expr, struct bfs_expr *child) {
}
void bfs_expr_extend(struct bfs_expr *expr, struct bfs_exprs *children) {
- while (!SLIST_EMPTY(children)) {
- struct bfs_expr *child = SLIST_POP(children);
+ drain_slist (struct bfs_expr, child, children) {
bfs_expr_append(expr, child);
}
}
diff --git a/src/expr.h b/src/expr.h
index f14b936..871b120 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -146,7 +146,7 @@ struct bfs_expr {
/** Total time spent running this predicate. */
struct timespec elapsed;
- /** Auxilliary data for the evaluation function. */
+ /** Auxiliary data for the evaluation function. */
union {
/** Child expressions. */
struct bfs_exprs children;
diff --git a/src/fsade.c b/src/fsade.c
index 30b3018..dfdf125 100644
--- a/src/fsade.c
+++ b/src/fsade.c
@@ -36,6 +36,13 @@
# define BFS_USE_XATTR true
#endif
+#ifndef BFS_USE_EXTATTR
+# define BFS_USE_EXTATTR false
+#endif
+#ifndef BFS_USE_XATTR
+# define BFS_USE_XATTR false
+#endif
+
/**
* Many of the APIs used here don't have *at() variants, but we can try to
* emulate something similar if /proc/self/fd is available.
diff --git a/src/fsade.h b/src/fsade.h
index 77cf82a..fbe02d8 100644
--- a/src/fsade.h
+++ b/src/fsade.h
@@ -19,6 +19,8 @@
#if __has_include(<sys/extattr.h>) || __has_include(<sys/xattr.h>)
# define BFS_CAN_CHECK_XATTRS true
+#else
+# define BFS_CAN_CHECK_XATTRS false
#endif
struct BFTW;
@@ -26,7 +28,7 @@ struct BFTW;
/**
* Check if a file has a non-trivial Access Control List.
*
- * @param ftwbuf
+ * @ftwbuf
* The file to check.
* @return
* 1 if it does, 0 if it doesn't, or -1 if an error occurred.
@@ -36,7 +38,7 @@ int bfs_check_acl(const struct BFTW *ftwbuf);
/**
* Check if a file has a non-trivial capability set.
*
- * @param ftwbuf
+ * @ftwbuf
* The file to check.
* @return
* 1 if it does, 0 if it doesn't, or -1 if an error occurred.
@@ -46,7 +48,7 @@ int bfs_check_capabilities(const struct BFTW *ftwbuf);
/**
* Check if a file has any extended attributes set.
*
- * @param ftwbuf
+ * @ftwbuf
* The file to check.
* @return
* 1 if it does, 0 if it doesn't, or -1 if an error occurred.
@@ -56,9 +58,9 @@ int bfs_check_xattrs(const struct BFTW *ftwbuf);
/**
* Check if a file has an extended attribute with the given name.
*
- * @param ftwbuf
+ * @ftwbuf
* The file to check.
- * @param name
+ * @name
* The name of the xattr to check.
* @return
* 1 if it does, 0 if it doesn't, or -1 if an error occurred.
@@ -68,7 +70,7 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name);
/**
* Get a file's SELinux context
*
- * @param ftwbuf
+ * @ftwbuf
* The file to check.
* @return
* The file's SELinux context, or NULL on failure.
diff --git a/src/ioq.h b/src/ioq.h
index 458c9e7..cb14be4 100644
--- a/src/ioq.h
+++ b/src/ioq.h
@@ -83,9 +83,9 @@ struct ioq_ent {
/**
* Create an I/O queue.
*
- * @param depth
+ * @depth
* The maximum depth of the queue.
- * @param nthreads
+ * @nthreads
* The maximum number of background threads.
* @return
* The new I/O queue, or NULL on failure.
@@ -100,11 +100,11 @@ size_t ioq_capacity(const struct ioq *ioq);
/**
* Asynchronous close().
*
- * @param ioq
+ * @ioq
* The I/O queue.
- * @param fd
+ * @fd
* The fd to close.
- * @param ptr
+ * @ptr
* An arbitrary pointer to associate with the request.
* @return
* 0 on success, or -1 on failure.
@@ -114,17 +114,17 @@ int ioq_close(struct ioq *ioq, int fd, void *ptr);
/**
* Asynchronous bfs_opendir().
*
- * @param ioq
+ * @ioq
* The I/O queue.
- * @param dir
+ * @dir
* The allocated directory.
- * @param dfd
+ * @dfd
* The base file descriptor.
- * @param path
+ * @path
* The path to open, relative to dfd.
- * @param flags
+ * @flags
* Flags that control which directory entries are listed.
- * @param ptr
+ * @ptr
* An arbitrary pointer to associate with the request.
* @return
* 0 on success, or -1 on failure.
@@ -134,11 +134,11 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path,
/**
* Asynchronous bfs_closedir().
*
- * @param ioq
+ * @ioq
* The I/O queue.
- * @param dir
+ * @dir
* The directory to close.
- * @param ptr
+ * @ptr
* An arbitrary pointer to associate with the request.
* @return
* 0 on success, or -1 on failure.
@@ -148,17 +148,17 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr);
/**
* Asynchronous bfs_stat().
*
- * @param ioq
+ * @ioq
* The I/O queue.
- * @param dfd
+ * @dfd
* The base file descriptor.
- * @param path
+ * @path
* The path to stat, relative to dfd.
- * @param flags
+ * @flags
* Flags that affect the lookup.
- * @param buf
+ * @buf
* A place to store the stat buffer, if successful.
- * @param ptr
+ * @ptr
* An arbitrary pointer to associate with the request.
* @return
* 0 on success, or -1 on failure.
@@ -168,7 +168,7 @@ int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags fla
/**
* Pop a response from the queue.
*
- * @param ioq
+ * @ioq
* The I/O queue.
* @return
* The next response, or NULL.
@@ -178,9 +178,9 @@ struct ioq_ent *ioq_pop(struct ioq *ioq, bool block);
/**
* Free a queue entry.
*
- * @param ioq
+ * @ioq
* The I/O queue.
- * @param ent
+ * @ent
* The entry to free.
*/
void ioq_free(struct ioq *ioq, struct ioq_ent *ent);
diff --git a/src/list.h b/src/list.h
index b30a96e..48f0d06 100644
--- a/src/list.h
+++ b/src/list.h
@@ -82,15 +82,17 @@
#ifndef BFS_LIST_H
#define BFS_LIST_H
+#include "bfs.h"
#include "diag.h"
#include <stddef.h>
+#include <stdint.h>
#include <string.h>
/**
* Initialize a singly-linked list.
*
- * @param list
+ * @list
* The list to initialize.
*
* ---
@@ -117,9 +119,9 @@
/**
* Initialize a singly-linked list item.
*
- * @param item
+ * @item
* The item to initialize.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
*
* ---
@@ -201,7 +203,7 @@
/**
* Get the head of a singly-linked list.
*
- * @param list
+ * @list
* The list in question.
* @return
* The first item in the list.
@@ -228,9 +230,9 @@
/**
* Get the tail of a singly-linked list.
*
- * @param list
+ * @list
* The list in question.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
* @return
* The last item in the list.
@@ -247,11 +249,11 @@
/**
* Check if an item is attached to a singly-linked list.
*
- * @param list
+ * @list
* The list to check.
- * @param item
+ * @item
* The item to check.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
* @return
* Whether the item is attached to the list.
@@ -268,13 +270,13 @@
/**
* Insert an item into a singly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param cursor
+ * @cursor
* A pointer to the item to insert after, e.g. &list->head or list->tail.
- * @param item
+ * @item
* The item to insert.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
* @return
* A cursor for the next item.
@@ -295,11 +297,11 @@
/**
* Add an item to the tail of a singly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param item
+ * @item
* The item to append.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
*/
#define SLIST_APPEND(list, ...) \
@@ -311,11 +313,11 @@
/**
* Add an item to the head of a singly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param item
+ * @item
* The item to prepend.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
*/
#define SLIST_PREPEND(list, ...) \
@@ -327,11 +329,11 @@
/**
* Splice a singly-linked list into another.
*
- * @param dest
+ * @dest
* The destination list.
- * @param cursor
+ * @cursor
* A pointer to the item to splice after, e.g. &list->head or list->tail.
- * @param src
+ * @src
* The source list.
*/
#define SLIST_SPLICE(dest, cursor, src) \
@@ -346,9 +348,9 @@
/**
* Add an entire singly-linked list to the tail of another.
*
- * @param dest
+ * @dest
* The destination list.
- * @param src
+ * @src
* The source list.
*/
#define SLIST_EXTEND(dest, src) \
@@ -357,11 +359,11 @@
/**
* Remove an item from a singly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param cursor
+ * @cursor
* A pointer to the item to remove, either &list->head or &prev->next.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
* @return
* The removed item.
@@ -372,26 +374,31 @@
#define SLIST_REMOVE_(list, cursor, ...) \
SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__))
-#define SLIST_REMOVE__(list, cursor, next) \
- (list->tail = (*cursor)->next ? list->tail : cursor, \
- slist_remove_impl(*cursor, cursor, &(*cursor)->next, sizeof(*cursor)))
-
-// Helper for SLIST_REMOVE()
-static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_t size) {
- // ret = *cursor;
- // *cursor = ret->next;
- memcpy(cursor, next, size);
- // ret->next = NULL;
- memset(next, 0, size);
- return ret;
+// Scratch variables for the type-safe SLIST_REMOVE() implementation.
+// Not a pointer type due to https://github.com/llvm/llvm-project/issues/109718.
+_maybe_unused
+static thread_local uintptr_t _slist_prev, _slist_next;
+
+/** Suppress -Wunused-value. */
+_maybe_unused
+static inline void *_slist_cast(uintptr_t ptr) {
+ return (void *)ptr;
}
+#define SLIST_REMOVE__(list, cursor, next) \
+ (_slist_prev = (uintptr_t)(void *)*cursor, \
+ _slist_next = (uintptr_t)(void *)(*cursor)->next, \
+ (*cursor)->next = NULL, \
+ *cursor = (void *)_slist_next, \
+ list->tail = *cursor ? list->tail : cursor, \
+ _slist_cast(_slist_prev))
+
/**
* Pop the head off a singly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param node (optional)
+ * @node (optional)
* If specified, use head->node.next rather than head->next.
* @return
* The popped item, or NULL if the list was empty.
@@ -408,13 +415,13 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Loop over the items in a singly-linked list.
*
- * @param type
+ * @type
* The list item type.
- * @param item
+ * @item
* The induction variable name.
- * @param list
+ * @list
* The list to iterate.
- * @param node (optional)
+ * @node (optional)
* If specified, use head->node.next rather than head->next.
*/
#define for_slist(type, item, ...) \
@@ -429,9 +436,24 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
item = _next)
/**
+ * Loop over a singly-linked list, popping each item.
+ *
+ * @type
+ * The list item type.
+ * @item
+ * The induction variable name.
+ * @list
+ * The list to drain.
+ * @node (optional)
+ * If specified, use head->node.next rather than head->next.
+ */
+#define drain_slist(type, item, ...) \
+ for (type *item; (item = SLIST_POP(__VA_ARGS__));)
+
+/**
* Initialize a doubly-linked list.
*
- * @param list
+ * @list
* The list to initialize.
*/
#define LIST_INIT(list) \
@@ -450,9 +472,9 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Initialize a doubly-linked list item.
*
- * @param item
+ * @item
* The item to initialize.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.next rather than item->next.
*/
#define LIST_ITEM_INIT(...) \
@@ -482,11 +504,11 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Add an item to the tail of a doubly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param item
+ * @item
* The item to append.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.{prev,next} rather than item->{prev,next}.
*/
#define LIST_APPEND(list, ...) \
@@ -495,11 +517,11 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Add an item to the head of a doubly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param item
+ * @item
* The item to prepend.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.{prev,next} rather than item->{prev,next}.
*/
#define LIST_PREPEND(list, ...) \
@@ -508,11 +530,11 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Check if an item is attached to a doubly-linked list.
*
- * @param list
+ * @list
* The list to check.
- * @param item
+ * @item
* The item to check.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.{prev,next} rather than item->{prev,next}.
* @return
* Whether the item is attached to the list.
@@ -529,13 +551,13 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Insert into a doubly-linked list after the given cursor.
*
- * @param list
+ * @list
* The list to modify.
- * @param cursor
+ * @cursor
* Insert after this element.
- * @param item
+ * @item
* The item to insert.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.{prev,next} rather than item->{prev,next}.
*/
#define LIST_INSERT(list, cursor, ...) \
@@ -554,11 +576,11 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Remove an item from a doubly-linked list.
*
- * @param list
+ * @list
* The list to modify.
- * @param item
+ * @item
* The item to remove.
- * @param node (optional)
+ * @node (optional)
* If specified, use item->node.{prev,next} rather than item->{prev,next}.
*/
#define LIST_REMOVE(list, ...) \
@@ -575,13 +597,13 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_
/**
* Loop over the items in a doubly-linked list.
*
- * @param type
+ * @type
* The list item type.
- * @param item
+ * @item
* The induction variable name.
- * @param list
+ * @list
* The list to iterate.
- * @param node (optional)
+ * @node (optional)
* If specified, use head->node.next rather than head->next.
*/
#define for_list(type, item, ...) \
diff --git a/src/mtab.c b/src/mtab.c
index 79f3100..bf9fc53 100644
--- a/src/mtab.c
+++ b/src/mtab.c
@@ -15,12 +15,14 @@
#include <string.h>
#include <sys/types.h>
-#if !defined(BFS_USE_MNTENT) && BFS_HAS_GETMNTENT_1
-# define BFS_USE_MNTENT true
-#elif !defined(BFS_USE_MNTINFO) && BFS_HAS_GETMNTINFO
-# define BFS_USE_MNTINFO true
-#elif !defined(BFS_USE_MNTTAB) && BFS_HAS_GETMNTENT_2
-# define BFS_USE_MNTTAB true
+#ifndef BFS_USE_MNTENT
+# define BFS_USE_MNTENT BFS_HAS_GETMNTENT_1
+#endif
+#ifndef BFS_USE_MNTINFO
+# define BFS_USE_MNTINFO (!BFS_USE_MNTENT && BFS_HAS_GETMNTINFO)
+#endif
+#ifndef BFS_USE_MNTTAB
+# define BFS_USE_MNTTAB (!BFS_USE_MNTINFO && BFS_HAS_GETMNTENT_2)
#endif
#if BFS_USE_MNTENT
diff --git a/src/mtab.h b/src/mtab.h
index 6c7d5cb..090392b 100644
--- a/src/mtab.h
+++ b/src/mtab.h
@@ -26,9 +26,9 @@ struct bfs_mtab *bfs_mtab_parse(void);
/**
* Determine the file system type that a file is on.
*
- * @param mtab
+ * @mtab
* The current mount table.
- * @param statbuf
+ * @statbuf
* The bfs_stat() buffer for the file in question.
* @return
* The type of file system containing this file, "unknown" if not known,
@@ -39,9 +39,9 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb
/**
* Check if a file could be a mount point.
*
- * @param mtab
+ * @mtab
* The current mount table.
- * @param name
+ * @name
* The name of the file to check.
* @return
* Whether the named file could be a mount point.
diff --git a/src/opt.c b/src/opt.c
index 9d4323d..49e8873 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -122,7 +122,7 @@ static const char *const pred_names[] = {
};
/**
- * A contrained integer range.
+ * A constrained integer range.
*/
struct df_range {
/** The (inclusive) minimum value. */
@@ -737,9 +737,7 @@ static struct bfs_expr *visit_and(struct bfs_opt *opt, struct bfs_expr *expr, co
df_init_bottom(&opt->after_false);
struct bfs_opt nested = *opt;
- while (!SLIST_EMPTY(&children)) {
- struct bfs_expr *child = SLIST_POP(&children);
-
+ drain_slist (struct bfs_expr, child, &children) {
if (SLIST_EMPTY(&children)) {
nested.ignore_result = opt->ignore_result;
} else {
@@ -771,9 +769,7 @@ static struct bfs_expr *visit_or(struct bfs_opt *opt, struct bfs_expr *expr, con
df_init_bottom(&opt->after_true);
struct bfs_opt nested = *opt;
- while (!SLIST_EMPTY(&children)) {
- struct bfs_expr *child = SLIST_POP(&children);
-
+ drain_slist (struct bfs_expr, child, &children) {
if (SLIST_EMPTY(&children)) {
nested.ignore_result = opt->ignore_result;
} else {
@@ -803,9 +799,7 @@ static struct bfs_expr *visit_comma(struct bfs_opt *opt, struct bfs_expr *expr,
struct bfs_opt nested = *opt;
- while (!SLIST_EMPTY(&children)) {
- struct bfs_expr *child = SLIST_POP(&children);
-
+ drain_slist (struct bfs_expr, child, &children) {
if (SLIST_EMPTY(&children)) {
nested.ignore_result = opt->ignore_result;
} else {
@@ -1384,8 +1378,7 @@ static struct bfs_expr *sink_not_andor(struct bfs_opt *opt, struct bfs_expr *exp
struct bfs_exprs children;
foster_children(expr, &children);
- struct bfs_expr *child;
- while ((child = SLIST_POP(&children))) {
+ drain_slist (struct bfs_expr, child, &children) {
opt_enter(opt, "%pe\n", child);
child = negate_expr(opt, child, argv);
@@ -1412,8 +1405,7 @@ static struct bfs_expr *sink_not_comma(struct bfs_opt *opt, struct bfs_expr *exp
struct bfs_exprs children;
foster_children(expr, &children);
- struct bfs_expr *child;
- while ((child = SLIST_POP(&children))) {
+ drain_slist (struct bfs_expr, child, &children) {
if (SLIST_EMPTY(&children)) {
opt_enter(opt, "%pe\n", child);
opt_debug(opt, "sink\n");
@@ -1441,7 +1433,6 @@ static struct bfs_expr *canonicalize_not(struct bfs_opt *opt, struct bfs_expr *e
if (rhs->eval_fn == eval_not) {
opt_debug(opt, "double negation\n");
- rhs = only_child(expr);
return only_child(rhs);
} else if (rhs->eval_fn == eval_and || rhs->eval_fn == eval_or) {
return sink_not_andor(opt, expr);
@@ -1463,8 +1454,7 @@ static struct bfs_expr *canonicalize_assoc(struct bfs_opt *opt, struct bfs_expr
struct bfs_exprs flat;
SLIST_INIT(&flat);
- struct bfs_expr *child;
- while ((child = SLIST_POP(&children))) {
+ drain_slist (struct bfs_expr, child, &children) {
if (child->eval_fn == expr->eval_fn) {
struct bfs_expr *head = SLIST_HEAD(&child->children);
struct bfs_expr *tail = SLIST_TAIL(&child->children);
@@ -1572,8 +1562,7 @@ static struct bfs_expr *reorder_andor(struct bfs_opt *opt, struct bfs_expr *expr
struct bfs_exprs pure;
SLIST_INIT(&pure);
- struct bfs_expr *child;
- while ((child = SLIST_POP(&children))) {
+ drain_slist (struct bfs_expr, child, &children) {
if (child->pure) {
SLIST_APPEND(&pure, child);
} else {
@@ -1975,8 +1964,7 @@ static struct bfs_expr *lift_andor_not(struct bfs_opt *opt, struct bfs_expr *exp
struct bfs_exprs children;
foster_children(expr, &children);
- struct bfs_expr *child;
- while ((child = SLIST_POP(&children))) {
+ drain_slist (struct bfs_expr, child, &children) {
opt_enter(opt, "%pe\n", child);
child = negate_expr(opt, child, &fake_not_arg);
@@ -2022,9 +2010,7 @@ static struct bfs_expr *simplify_and(struct bfs_opt *opt, struct bfs_expr *expr,
struct bfs_exprs children;
foster_children(expr, &children);
- while (!SLIST_EMPTY(&children)) {
- struct bfs_expr *child = SLIST_POP(&children);
-
+ drain_slist (struct bfs_expr, child, &children) {
if (child == ignorable) {
ignore = true;
}
@@ -2045,8 +2031,8 @@ static struct bfs_expr *simplify_and(struct bfs_opt *opt, struct bfs_expr *expr,
bfs_expr_append(expr, child);
if (child->always_false) {
- while ((child = SLIST_POP(&children))) {
- opt_delete(opt, "%pe [short-circuit]\n", child);
+ drain_slist (struct bfs_expr, dead, &children) {
+ opt_delete(opt, "%pe [short-circuit]\n", dead);
}
}
}
@@ -2071,9 +2057,7 @@ static struct bfs_expr *simplify_or(struct bfs_opt *opt, struct bfs_expr *expr,
struct bfs_exprs children;
foster_children(expr, &children);
- while (!SLIST_EMPTY(&children)) {
- struct bfs_expr *child = SLIST_POP(&children);
-
+ drain_slist (struct bfs_expr, child, &children) {
if (child == ignorable) {
ignore = true;
}
@@ -2094,8 +2078,8 @@ static struct bfs_expr *simplify_or(struct bfs_opt *opt, struct bfs_expr *expr,
bfs_expr_append(expr, child);
if (child->always_true) {
- while ((child = SLIST_POP(&children))) {
- opt_delete(opt, "%pe [short-circuit]\n", child);
+ drain_slist (struct bfs_expr, dead, &children) {
+ opt_delete(opt, "%pe [short-circuit]\n", dead);
}
}
}
@@ -2117,9 +2101,7 @@ static struct bfs_expr *simplify_comma(struct bfs_opt *opt, struct bfs_expr *exp
struct bfs_exprs children;
foster_children(expr, &children);
- while (!SLIST_EMPTY(&children)) {
- struct bfs_expr *child = SLIST_POP(&children);
-
+ drain_slist (struct bfs_expr, child, &children) {
if (opt->level >= 2 && child->pure && !SLIST_EMPTY(&children)) {
if (!opt_ignore(opt, child, true)) {
return NULL;
diff --git a/src/opt.h b/src/opt.h
index 4aac129..a5729b3 100644
--- a/src/opt.h
+++ b/src/opt.h
@@ -13,7 +13,7 @@ struct bfs_ctx;
/**
* Apply optimizations to the command line.
*
- * @param ctx
+ * @ctx
* The bfs context to optimize.
* @return
* 0 if successful, -1 on error.
diff --git a/src/parse.h b/src/parse.h
index 6895c9f..fcc8234 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -11,9 +11,9 @@
/**
* Parse the command line.
*
- * @param argc
+ * @argc
* The number of arguments.
- * @param argv
+ * @argv
* The arguments to parse.
* @return
* A new bfs context, or NULL on failure.
diff --git a/src/printf.h b/src/printf.h
index 2bff087..e8d862e 100644
--- a/src/printf.h
+++ b/src/printf.h
@@ -22,11 +22,11 @@ struct bfs_printf;
/**
* Parse a -printf format string.
*
- * @param ctx
+ * @ctx
* The bfs context.
- * @param expr
+ * @expr
* The expression to fill in.
- * @param format
+ * @format
* The format string to parse.
* @return
* 0 on success, -1 on failure.
@@ -36,11 +36,11 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha
/**
* Evaluate a parsed format string.
*
- * @param cfile
+ * @cfile
* The CFILE to print to.
- * @param format
+ * @format
* The parsed printf format.
- * @param ftwbuf
+ * @ftwbuf
* The bftw() data for the current file.
* @return
* 0 on success, -1 on failure.
diff --git a/src/pwcache.h b/src/pwcache.h
index b6c0b67..d7c602d 100644
--- a/src/pwcache.h
+++ b/src/pwcache.h
@@ -27,9 +27,9 @@ struct bfs_users *bfs_users_new(void);
/**
* Get a user entry by name.
*
- * @param users
+ * @users
* The user cache.
- * @param name
+ * @name
* The username to look up.
* @return
* The matching user, or NULL if not found (errno == 0) or an error
@@ -40,9 +40,9 @@ const struct passwd *bfs_getpwnam(struct bfs_users *users, const char *name);
/**
* Get a user entry by ID.
*
- * @param users
+ * @users
* The user cache.
- * @param uid
+ * @uid
* The ID to look up.
* @return
* The matching user, or NULL if not found (errno == 0) or an error
@@ -53,7 +53,7 @@ const struct passwd *bfs_getpwuid(struct bfs_users *users, uid_t uid);
/**
* Flush a user cache.
*
- * @param users
+ * @users
* The cache to flush.
*/
void bfs_users_flush(struct bfs_users *users);
@@ -61,7 +61,7 @@ void bfs_users_flush(struct bfs_users *users);
/**
* Free a user cache.
*
- * @param users
+ * @users
* The user cache to free.
*/
void bfs_users_free(struct bfs_users *users);
@@ -82,9 +82,9 @@ struct bfs_groups *bfs_groups_new(void);
/**
* Get a group entry by name.
*
- * @param groups
+ * @groups
* The group cache.
- * @param name
+ * @name
* The group name to look up.
* @return
* The matching group, or NULL if not found (errno == 0) or an error
@@ -95,9 +95,9 @@ const struct group *bfs_getgrnam(struct bfs_groups *groups, const char *name);
/**
* Get a group entry by ID.
*
- * @param groups
+ * @groups
* The group cache.
- * @param uid
+ * @uid
* The ID to look up.
* @return
* The matching group, or NULL if not found (errno == 0) or an error
@@ -108,7 +108,7 @@ const struct group *bfs_getgrgid(struct bfs_groups *groups, gid_t gid);
/**
* Flush a group cache.
*
- * @param groups
+ * @groups
* The cache to flush.
*/
void bfs_groups_flush(struct bfs_groups *groups);
@@ -116,7 +116,7 @@ void bfs_groups_flush(struct bfs_groups *groups);
/**
* Free a group cache.
*
- * @param groups
+ * @groups
* The group cache to free.
*/
void bfs_groups_free(struct bfs_groups *groups);
diff --git a/src/sanity.h b/src/sanity.h
index 0b770cf..3f6020b 100644
--- a/src/sanity.h
+++ b/src/sanity.h
@@ -20,6 +20,11 @@
#define SANITIZE_CALL__(macro, ptr, size, ...) \
macro(ptr, size)
+/**
+ * Squelch unused variable warnings when not sanitizing.
+ */
+#define sanitize_ignore(ptr, size) ((void)(ptr), (void)(size))
+
#if __SANITIZE_ADDRESS__
# include <sanitizer/asan_interface.h>
@@ -38,8 +43,8 @@
#define sanitize_free(...) SANITIZE_CALL(__asan_poison_memory_region, __VA_ARGS__)
#else
-# define sanitize_alloc sanitize_uninit
-# define sanitize_free sanitize_uninit
+# define sanitize_alloc(...) SANITIZE_CALL(sanitize_ignore, __VA_ARGS__)
+# define sanitize_free(...) SANITIZE_CALL(sanitize_ignore, __VA_ARGS__)
#endif
#if __SANITIZE_MEMORY__
@@ -65,11 +70,6 @@
#endif
/**
- * Squelch unused variable warnings when not sanitizing.
- */
-#define sanitize_ignore(ptr, size) ((void)(ptr), (void)(size))
-
-/**
* Initialize a variable, unless sanitizers would detect uninitialized uses.
*/
#if __SANITIZE_MEMORY__
diff --git a/src/sighook.c b/src/sighook.c
index 4356fdb..0cc81fa 100644
--- a/src/sighook.c
+++ b/src/sighook.c
@@ -291,6 +291,8 @@ static void sigpop(struct siglist *list, struct sighook *hook) {
rcu_update(hook->self, next);
if (next) {
next->self = hook->self;
+ } else {
+ list->tail = &list->head;
}
}
diff --git a/src/sighook.h b/src/sighook.h
index 74d18c0..3bece21 100644
--- a/src/sighook.h
+++ b/src/sighook.h
@@ -27,11 +27,11 @@ enum sigflags {
* A signal hook callback. Hooks are executed from a signal handler, so must
* only call async-signal-safe functions.
*
- * @param sig
+ * @sig
* The signal number.
- * @param info
+ * @info
* Additional information about the signal.
- * @param arg
+ * @arg
* An arbitrary pointer passed to the hook.
*/
typedef void sighook_fn(int sig, siginfo_t *info, void *arg);
@@ -39,13 +39,13 @@ typedef void sighook_fn(int sig, siginfo_t *info, void *arg);
/**
* Install a hook for a signal.
*
- * @param sig
+ * @sig
* The signal to hook.
- * @param fn
+ * @fn
* The function to call.
- * @param arg
+ * @arg
* An argument passed to the function.
- * @param flags
+ * @flags
* Flags for the new hook.
* @return
* The installed hook, or NULL on failure.
@@ -56,9 +56,9 @@ struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags)
* On a best-effort basis, invoke the given hook just before the program is
* abnormally terminated by a signal.
*
- * @param fn
+ * @fn
* The function to call.
- * @param arg
+ * @arg
* An argument passed to the function.
* @return
* The installed hook, or NULL on failure.
diff --git a/src/stat.h b/src/stat.h
index 8d0bd9b..50c91de 100644
--- a/src/stat.h
+++ b/src/stat.h
@@ -119,14 +119,14 @@ struct bfs_stat {
/**
* Facade over fstatat().
*
- * @param at_fd
+ * @at_fd
* The base file descriptor for the lookup.
- * @param at_path
+ * @at_path
* The path to stat, relative to at_fd. Pass NULL to fstat() at_fd
* itself.
- * @param flags
+ * @flags
* Flags that affect the lookup.
- * @param[out] buf
+ * @buf[out]
* A place to store the stat buffer, if successful.
* @return
* 0 on success, -1 on error.
diff --git a/src/trie.c b/src/trie.c
index a92950b..a7498d4 100644
--- a/src/trie.c
+++ b/src/trie.c
@@ -132,34 +132,34 @@ struct trie_node {
uintptr_t children[];
};
-/** Check if an encoded pointer is to a leaf. */
-static bool trie_is_leaf(uintptr_t ptr) {
+/** Check if an encoded pointer is to an internal node. */
+static bool trie_is_node(uintptr_t ptr) {
return ptr & 1;
}
-/** Decode a pointer to a leaf. */
-static struct trie_leaf *trie_decode_leaf(uintptr_t ptr) {
- bfs_assert(trie_is_leaf(ptr));
- return (struct trie_leaf *)(ptr ^ 1);
+/** Decode a pointer to an internal node. */
+static struct trie_node *trie_decode_node(uintptr_t ptr) {
+ bfs_assert(trie_is_node(ptr));
+ return (struct trie_node *)(ptr - 1);
}
-/** Encode a pointer to a leaf. */
-static uintptr_t trie_encode_leaf(const struct trie_leaf *leaf) {
- uintptr_t ptr = (uintptr_t)leaf ^ 1;
- bfs_assert(trie_is_leaf(ptr));
+/** Encode a pointer to an internal node. */
+static uintptr_t trie_encode_node(const struct trie_node *node) {
+ uintptr_t ptr = (uintptr_t)node + 1;
+ bfs_assert(trie_is_node(ptr));
return ptr;
}
-/** Decode a pointer to an internal node. */
-static struct trie_node *trie_decode_node(uintptr_t ptr) {
- bfs_assert(!trie_is_leaf(ptr));
- return (struct trie_node *)ptr;
+/** Decode a pointer to a leaf. */
+static struct trie_leaf *trie_decode_leaf(uintptr_t ptr) {
+ bfs_assert(!trie_is_node(ptr));
+ return (struct trie_leaf *)ptr;
}
-/** Encode a pointer to an internal node. */
-static uintptr_t trie_encode_node(const struct trie_node *node) {
- uintptr_t ptr = (uintptr_t)node;
- bfs_assert(!trie_is_leaf(ptr));
+/** Encode a pointer to a leaf. */
+static uintptr_t trie_encode_leaf(const struct trie_leaf *leaf) {
+ uintptr_t ptr = (uintptr_t)leaf;
+ bfs_assert(!trie_is_node(ptr));
return ptr;
}
@@ -171,9 +171,10 @@ void trie_init(struct trie *trie) {
}
/** Extract the nibble at a certain offset from a byte sequence. */
-static unsigned char trie_key_nibble(const void *key, size_t offset) {
+static unsigned char trie_key_nibble(const void *key, size_t length, size_t offset) {
const unsigned char *bytes = key;
size_t byte = offset >> 1;
+ bfs_assert(byte < length);
// A branchless version of
// if (offset & 1) {
@@ -185,6 +186,11 @@ static unsigned char trie_key_nibble(const void *key, size_t offset) {
return (bytes[byte] >> shift) & 0xF;
}
+/** Extract the nibble at a certain offset from a leaf. */
+static unsigned char trie_leaf_nibble(const struct trie_leaf *leaf, size_t offset) {
+ return trie_key_nibble(leaf->key, leaf->length, offset);
+}
+
/**
* Finds a leaf in the trie that matches the key at every branch. If the key
* exists in the trie, the representative will match the searched key. But
@@ -195,18 +201,15 @@ static unsigned char trie_key_nibble(const void *key, size_t offset) {
_trie_clones
static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) {
uintptr_t ptr = trie->root;
- if (!ptr) {
- return NULL;
- }
size_t offset = 0;
- while (!trie_is_leaf(ptr)) {
+ while (trie_is_node(ptr)) {
struct trie_node *node = trie_decode_node(ptr);
offset += node->offset;
unsigned int index = 0;
if ((offset >> 1) < length) {
- unsigned char nibble = trie_key_nibble(key, offset);
+ unsigned char nibble = trie_key_nibble(key, length, offset);
unsigned int bit = 1U << nibble;
// bits = bitmap & bit ? bitmap & (bit - 1) : 0
unsigned int mask = -!!(node->bitmap & bit);
@@ -263,10 +266,10 @@ static struct trie_leaf *trie_terminal_leaf(const struct trie_node *node) {
}
uintptr_t ptr = node->children[0];
- if (trie_is_leaf(ptr)) {
- return trie_decode_leaf(ptr);
- } else {
+ if (trie_is_node(ptr)) {
node = trie_decode_node(ptr);
+ } else {
+ return trie_decode_leaf(ptr);
}
}
@@ -294,7 +297,7 @@ static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const ch
size_t length = strlen(key) + 1;
size_t offset = 0;
- while (!trie_is_leaf(ptr)) {
+ while (trie_is_node(ptr)) {
struct trie_node *node = trie_decode_node(ptr);
offset += node->offset;
if ((offset >> 1) >= length) {
@@ -307,7 +310,7 @@ static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const ch
skip = offset >> 1;
}
- unsigned char nibble = trie_key_nibble(key, offset);
+ unsigned char nibble = trie_key_nibble(key, length, offset);
unsigned int bit = 1U << nibble;
if (node->bitmap & bit) {
unsigned int index = count_ones(node->bitmap & (bit - 1));
@@ -494,10 +497,10 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str
* | Y
* +--->key
*/
-static uintptr_t *trie_jump(struct trie *trie, uintptr_t *ptr, const char *key, size_t *offset) {
+static uintptr_t *trie_jump(struct trie *trie, uintptr_t *ptr, size_t *offset) {
// We only ever need to jump to leaf nodes, since internal nodes are
// guaranteed to be within OFFSET_MAX anyway
- bfs_assert(trie_is_leaf(*ptr));
+ struct trie_leaf *leaf = trie_decode_leaf(*ptr);
struct trie_node *node = trie_node_alloc(trie, 1);
if (!node) {
@@ -507,7 +510,7 @@ static uintptr_t *trie_jump(struct trie *trie, uintptr_t *ptr, const char *key,
*offset += OFFSET_MAX;
node->offset = OFFSET_MAX;
- unsigned char nibble = trie_key_nibble(key, *offset);
+ unsigned char nibble = trie_leaf_nibble(leaf, *offset);
node->bitmap = 1 << nibble;
node->children[0] = *ptr;
@@ -533,8 +536,8 @@ static uintptr_t *trie_jump(struct trie *trie, uintptr_t *ptr, const char *key,
* +--->leaf
*/
static struct trie_leaf *trie_split(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, struct trie_leaf *rep, size_t offset, size_t mismatch) {
- unsigned char key_nibble = trie_key_nibble(leaf->key, mismatch);
- unsigned char rep_nibble = trie_key_nibble(rep->key, mismatch);
+ unsigned char key_nibble = trie_leaf_nibble(leaf, mismatch);
+ unsigned char rep_nibble = trie_leaf_nibble(rep, mismatch);
bfs_assert(key_nibble != rep_nibble);
struct trie_node *node = trie_node_alloc(trie, 2);
@@ -546,7 +549,7 @@ static struct trie_leaf *trie_split(struct trie *trie, uintptr_t *ptr, struct tr
node->bitmap = (1 << key_nibble) | (1 << rep_nibble);
size_t delta = mismatch - offset;
- if (!trie_is_leaf(*ptr)) {
+ if (trie_is_node(*ptr)) {
struct trie_node *child = trie_decode_node(*ptr);
child->offset -= delta;
}
@@ -567,8 +570,14 @@ _trie_clones
static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key, size_t length) {
struct trie_leaf *rep = trie_representative(trie, key, length);
size_t mismatch = trie_mismatch(rep, key, length);
- if (mismatch >= (length << 1)) {
+ size_t misbyte = mismatch / 2;
+ if (misbyte >= length) {
+ bfs_assert(misbyte == length);
return rep;
+ } else if (rep && misbyte >= rep->length) {
+ bfs_bug("trie keys must be prefix-free");
+ errno = EINVAL;
+ return NULL;
}
struct trie_leaf *leaf = trie_leaf_alloc(trie, key, length);
@@ -583,14 +592,14 @@ static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key
size_t offset = 0;
uintptr_t *ptr = &trie->root;
- while (!trie_is_leaf(*ptr)) {
+ while (trie_is_node(*ptr)) {
struct trie_node *node = trie_decode_node(*ptr);
if (offset + node->offset > mismatch) {
break;
}
offset += node->offset;
- unsigned char nibble = trie_key_nibble(key, offset);
+ unsigned char nibble = trie_leaf_nibble(leaf, offset);
unsigned int bit = 1U << nibble;
if (node->bitmap & bit) {
bfs_assert(offset < mismatch);
@@ -603,7 +612,7 @@ static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key
}
while (mismatch - offset > OFFSET_MAX) {
- ptr = trie_jump(trie, ptr, key, &offset);
+ ptr = trie_jump(trie, ptr, &offset);
if (!ptr) {
trie_leaf_free(trie, leaf);
return NULL;
@@ -619,7 +628,7 @@ struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t len
/** Free a chain of singleton nodes. */
static void trie_free_singletons(struct trie *trie, uintptr_t ptr) {
- while (!trie_is_leaf(ptr)) {
+ while (trie_is_node(ptr)) {
struct trie_node *node = trie_decode_node(ptr);
// Make sure the bitmap is a power of two, i.e. it has just one child
@@ -651,7 +660,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) {
*/
static int trie_collapse_node(struct trie *trie, uintptr_t *parent, struct trie_node *parent_node, unsigned int child_index) {
uintptr_t other = parent_node->children[child_index ^ 1];
- if (!trie_is_leaf(other)) {
+ if (trie_is_node(other)) {
struct trie_node *other_node = trie_decode_node(other);
if (other_node->offset + parent_node->offset <= OFFSET_MAX) {
other_node->offset += parent_node->offset;
@@ -661,7 +670,7 @@ static int trie_collapse_node(struct trie *trie, uintptr_t *parent, struct trie_
}
*parent = other;
- trie_node_free(trie, parent_node, 1);
+ trie_node_free(trie, parent_node, 2);
return 0;
}
@@ -671,12 +680,11 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) {
uintptr_t *parent = NULL;
unsigned int child_bit = 0, child_index = 0;
size_t offset = 0;
- while (!trie_is_leaf(*child)) {
+ while (trie_is_node(*child)) {
struct trie_node *node = trie_decode_node(*child);
offset += node->offset;
- bfs_assert((offset >> 1) < leaf->length);
- unsigned char nibble = trie_key_nibble(leaf->key, offset);
+ unsigned char nibble = trie_leaf_nibble(leaf, offset);
unsigned int bit = 1U << nibble;
unsigned int bitmap = node->bitmap;
bfs_assert(bitmap & bit);
@@ -701,19 +709,19 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) {
}
struct trie_node *node = trie_decode_node(*parent);
- child = node->children + child_index;
- trie_free_singletons(trie, *child);
+ trie_free_singletons(trie, node->children[child_index]);
- node->bitmap ^= child_bit;
unsigned int parent_size = count_ones(node->bitmap);
- bfs_assert(parent_size > 0);
- if (parent_size == 1 && trie_collapse_node(trie, parent, node, child_index) == 0) {
+ bfs_assert(parent_size > 1);
+ if (parent_size == 2 && trie_collapse_node(trie, parent, node, child_index) == 0) {
return;
}
- if (child_index < parent_size) {
- memmove(child, child + 1, (parent_size - child_index) * sizeof(*child));
+ for (size_t i = child_index; i + 1 < parent_size; ++i) {
+ node->children[i] = node->children[i + 1];
}
+ node->bitmap &= ~child_bit;
+ --parent_size;
if (has_single_bit(parent_size)) {
node = trie_node_realloc(trie, node, 2 * parent_size, parent_size);
diff --git a/src/trie.h b/src/trie.h
index 8f7f94b..318a23b 100644
--- a/src/trie.h
+++ b/src/trie.h
@@ -46,9 +46,9 @@ void trie_init(struct trie *trie);
/**
* Find the leaf for a string key.
*
- * @param trie
+ * @trie
* The trie to search.
- * @param key
+ * @key
* The key to look up.
* @return
* The found leaf, or NULL if the key is not present.
@@ -58,11 +58,11 @@ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key);
/**
* Find the leaf for a fixed-size key.
*
- * @param trie
+ * @trie
* The trie to search.
- * @param key
+ * @key
* The key to look up.
- * @param length
+ * @length
* The length of the key in bytes.
* @return
* The found leaf, or NULL if the key is not present.
@@ -72,9 +72,9 @@ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t
/**
* Find the shortest leaf that starts with a given key.
*
- * @param trie
+ * @trie
* The trie to search.
- * @param key
+ * @key
* The key to look up.
* @return
* A leaf that starts with the given key, or NULL.
@@ -84,9 +84,9 @@ struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key);
/**
* Find the leaf that is the longest prefix of the given key.
*
- * @param trie
+ * @trie
* The trie to search.
- * @param key
+ * @key
* The key to look up.
* @return
* The longest prefix match for the given key, or NULL.
@@ -96,9 +96,9 @@ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key);
/**
* Insert a string key into the trie.
*
- * @param trie
+ * @trie
* The trie to modify.
- * @param key
+ * @key
* The key to insert.
* @return
* The inserted leaf, or NULL on failure.
@@ -108,11 +108,11 @@ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key);
/**
* Insert a fixed-size key into the trie.
*
- * @param trie
+ * @trie
* The trie to modify.
- * @param key
+ * @key
* The key to insert.
- * @param length
+ * @length
* The length of the key in bytes.
* @return
* The inserted leaf, or NULL on failure.
@@ -122,9 +122,9 @@ struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t len
/**
* Remove a leaf from a trie.
*
- * @param trie
+ * @trie
* The trie to modify.
- * @param leaf
+ * @leaf
* The leaf to remove.
*/
void trie_remove(struct trie *trie, struct trie_leaf *leaf);
diff --git a/src/typo.h b/src/typo.h
index 13eaa67..b0daaf1 100644
--- a/src/typo.h
+++ b/src/typo.h
@@ -7,9 +7,9 @@
/**
* Find the "typo" distance between two strings.
*
- * @param actual
+ * @actual
* The actual string typed by the user.
- * @param expected
+ * @expected
* The expected valid string.
* @return The distance between the two strings.
*/
diff --git a/src/xregex.h b/src/xregex.h
index 750db24..c4504ee 100644
--- a/src/xregex.h
+++ b/src/xregex.h
@@ -42,13 +42,13 @@ enum bfs_regexec_flags {
/**
* Wrapper for regcomp() that supports additional regex types.
*
- * @param[out] preg
+ * @preg[out]
* Will hold the compiled regex.
- * @param pattern
+ * @pattern
* The regular expression to compile.
- * @param type
+ * @type
* The regular expression syntax to use.
- * @param flags
+ * @flags
* Regex compilation flags.
* @return
* 0 on success, -1 on failure.
@@ -58,11 +58,11 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ
/**
* Wrapper for regexec().
*
- * @param regex
+ * @regex
* The regular expression to execute.
- * @param str
+ * @str
* The string to match against.
- * @param flags
+ * @flags
* Regex execution flags.
* @return
* 1 for a match, 0 for no match, -1 on failure.
@@ -77,7 +77,7 @@ void bfs_regfree(struct bfs_regex *regex);
/**
* Get a human-readable regex error message.
*
- * @param regex
+ * @regex
* The compiled regex.
* @return
* A human-readable description of the error, which should be free()'d.
diff --git a/src/xspawn.h b/src/xspawn.h
index 9cfbaf7..3c74ccd 100644
--- a/src/xspawn.h
+++ b/src/xspawn.h
@@ -109,13 +109,13 @@ int bfs_spawn_setrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit
/**
* Spawn a new process.
*
- * @param exe
+ * @exe
* The executable to run.
- * @param ctx
+ * @ctx
* The context for the new process.
- * @param argv
+ * @argv
* The arguments for the new process.
- * @param envp
+ * @envp
* The environment variables for the new process (NULL for the current
* environment).
* @return
@@ -127,7 +127,7 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char
* Look up an executable in the current PATH, as BFS_SPAWN_USE_PATH or execvp()
* would do.
*
- * @param exe
+ * @exe
* The name of the binary to execute. Bare names without a '/' will be
* searched on the provided PATH.
* @return
diff --git a/src/xtime.c b/src/xtime.c
index dd9b60f..49d7c36 100644
--- a/src/xtime.c
+++ b/src/xtime.c
@@ -3,9 +3,11 @@
#include "xtime.h"
+#include "alloc.h"
#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
+#include "sanity.h"
#include <errno.h>
#include <limits.h>
@@ -351,3 +353,98 @@ invalid:
error:
return -1;
}
+
+#if defined(_POSIX_TIMERS) && BFS_HAS_TIMER_CREATE
+# define BFS_POSIX_TIMERS _POSIX_TIMERS
+#else
+# define BFS_POSIX_TIMERS (-1)
+#endif
+
+struct timer {
+#if BFS_POSIX_TIMERS >= 0
+ /** The POSIX timer. */
+ timer_t timer;
+#endif
+ /** Whether to use timer_create() or setitimer(). */
+ bool legacy;
+};
+
+struct timer *xtimer_start(const struct timespec *interval) {
+ struct timer *timer = ALLOC(struct timer);
+ if (!timer) {
+ return NULL;
+ }
+
+#if BFS_POSIX_TIMERS >= 0
+ if (sysoption(TIMERS)) {
+ clockid_t clock = CLOCK_REALTIME;
+
+#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0
+ if (sysoption(MONOTONIC_CLOCK) > 0) {
+ clock = CLOCK_MONOTONIC;
+ }
+#endif
+
+ if (timer_create(clock, NULL, &timer->timer) != 0) {
+ goto fail;
+ }
+
+ // https://github.com/llvm/llvm-project/issues/111847
+ sanitize_init(&timer->timer);
+
+ struct itimerspec spec = {
+ .it_value = *interval,
+ .it_interval = *interval,
+ };
+ if (timer_settime(timer->timer, 0, &spec, NULL) != 0) {
+ timer_delete(timer->timer);
+ goto fail;
+ }
+
+ timer->legacy = false;
+ return timer;
+ }
+#endif
+
+#if BFS_POSIX_TIMERS <= 0
+ struct timeval tv = {
+ .tv_sec = interval->tv_sec,
+ .tv_usec = (interval->tv_nsec + 999) / 1000,
+ };
+ struct itimerval ival = {
+ .it_value = tv,
+ .it_interval = tv,
+ };
+ if (setitimer(ITIMER_REAL, &ival, NULL) != 0) {
+ goto fail;
+ }
+
+ timer->legacy = true;
+ return timer;
+#endif
+
+fail:
+ free(timer);
+ return NULL;
+}
+
+void xtimer_stop(struct timer *timer) {
+ if (!timer) {
+ return;
+ }
+
+ if (timer->legacy) {
+#if BFS_POSIX_TIMERS <= 0
+ struct itimerval ival = {0};
+ int ret = setitimer(ITIMER_REAL, &ival, NULL);
+ bfs_everify(ret == 0, "setitimer()");
+#endif
+ } else {
+#if BFS_POSIX_TIMERS >= 0
+ int ret = timer_delete(timer->timer);
+ bfs_everify(ret == 0, "timer_delete()");
+#endif
+ }
+
+ free(timer);
+}
diff --git a/src/xtime.h b/src/xtime.h
index a3547a7..2927a2e 100644
--- a/src/xtime.h
+++ b/src/xtime.h
@@ -13,9 +13,9 @@
/**
* mktime() wrapper that reports errors more reliably.
*
- * @param[in,out] tm
- * The struct tm to convert.
- * @param[out] timep
+ * @tm[in,out]
+ * The struct tm to convert and normalize.
+ * @timep[out]
* A pointer to the result.
* @return
* 0 on success, -1 on failure.
@@ -25,9 +25,9 @@ int xmktime(struct tm *tm, time_t *timep);
/**
* A portable timegm(), the inverse of gmtime().
*
- * @param[in,out] tm
- * The struct tm to convert.
- * @param[out] timep
+ * @tm[in,out]
+ * The struct tm to convert and normalize.
+ * @timep[out]
* A pointer to the result.
* @return
* 0 on success, -1 on failure.
@@ -37,13 +37,36 @@ int xtimegm(struct tm *tm, time_t *timep);
/**
* Parse an ISO 8601-style timestamp.
*
- * @param[in] str
+ * @str
* The string to parse.
- * @param[out] result
+ * @result[out]
* A pointer to the result.
* @return
* 0 on success, -1 on failure.
*/
int xgetdate(const char *str, struct timespec *result);
+/**
+ * A timer.
+ */
+struct timer;
+
+/**
+ * Start a timer.
+ *
+ * @interval
+ * The regular interval at which to send SIGALRM.
+ * @return
+ * The new timer on success, otherwise NULL.
+ */
+struct timer *xtimer_start(const struct timespec *interval);
+
+/**
+ * Stop a timer.
+ *
+ * @timer
+ * The timer to stop.
+ */
+void xtimer_stop(struct timer *timer);
+
#endif // BFS_XTIME_H