diff options
Diffstat (limited to 'src/basic/errno-util.h')
-rw-r--r-- | src/basic/errno-util.h | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h new file mode 100644 index 0000000..27804e6 --- /dev/null +++ b/src/basic/errno-util.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> + +#include "macro.h" + +/* strerror(3) says that glibc uses a maximum length of 1024 bytes. */ +#define ERRNO_BUF_LEN 1024 + +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 §6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks + * + * Note that we use the GNU variant of strerror_r() here. */ +#define STRERROR(errnum) strerror_r(abs(errnum), (char[ERRNO_BUF_LEN]){}, ERRNO_BUF_LEN) + +/* A helper to print an error message or message for functions that return 0 on EOF. + * Note that we can't use ({ … }) to define a temporary variable, so errnum is + * evaluated twice. */ +#define STRERROR_OR_EOF(errnum) ((errnum) != 0 ? STRERROR(errnum) : "Unexpected EOF") + +static inline void _reset_errno_(int *saved_errno) { + if (*saved_errno < 0) /* Invalidated by UNPROTECT_ERRNO? */ + return; + + errno = *saved_errno; +} + +#define PROTECT_ERRNO \ + _cleanup_(_reset_errno_) _unused_ int _saved_errno_ = errno + +#define UNPROTECT_ERRNO \ + do { \ + errno = _saved_errno_; \ + _saved_errno_ = -1; \ + } while (false) + +#define LOCAL_ERRNO(value) \ + PROTECT_ERRNO; \ + errno = abs(value) + +static inline int negative_errno(void) { + /* This helper should be used to shut up gcc if you know 'errno' is + * negative. Instead of "return -errno;", use "return negative_errno();" + * It will suppress bogus gcc warnings in case it assumes 'errno' might + * be 0 and thus the caller's error-handling might not be triggered. */ + assert_return(errno > 0, -EINVAL); + return -errno; +} + +static inline int RET_NERRNO(int ret) { + + /* Helper to wrap system calls in to make them return negative errno errors. This brings system call + * error handling in sync with how we usually handle errors in our own code, i.e. with immediate + * returning of negative errno. Usage is like this: + * + * … + * r = RET_NERRNO(unlink(t)); + * … + * + * or + * + * … + * fd = RET_NERRNO(open("/etc/fstab", O_RDONLY|O_CLOEXEC)); + * … + */ + + if (ret < 0) + return negative_errno(); + + return ret; +} + +/* Collect possible errors in <acc>, so that the first error can be returned. + * Returns (possibly updated) <acc>. */ +#define RET_GATHER(acc, err) \ + ({ \ + int *__a = &(acc), __e = (err); \ + if (*__a >= 0 && __e < 0) \ + *__a = __e; \ + *__a; \ + }) + +static inline int errno_or_else(int fallback) { + /* To be used when invoking library calls where errno handling is not defined clearly: we return + * errno if it is set, and the specified error otherwise. The idea is that the caller initializes + * errno to zero before doing an API call, and then uses this helper to retrieve a somewhat useful + * error code */ + if (errno > 0) + return -errno; + + return -abs(fallback); +} + +/* abs(3) says: Trying to take the absolute value of the most negative integer is not defined. */ +#define _DEFINE_ABS_WRAPPER(name) \ + static inline bool ERRNO_IS_##name(intmax_t r) { \ + if (r == INTMAX_MIN) \ + return false; \ + return ERRNO_IS_NEG_##name(-imaxabs(r)); \ + } + +assert_cc(INT_MAX <= INTMAX_MAX); + +/* For send()/recv() or read()/write(). */ +static inline bool ERRNO_IS_NEG_TRANSIENT(intmax_t r) { + return IN_SET(r, + -EAGAIN, + -EINTR); +} +_DEFINE_ABS_WRAPPER(TRANSIENT); + +/* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5. + * + * Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases. See the + * icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources. + * + * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet, + * kernel tells us that with ETIMEDOUT, see tcp(7). */ +static inline bool ERRNO_IS_NEG_DISCONNECT(intmax_t r) { + return IN_SET(r, + -ECONNABORTED, + -ECONNREFUSED, + -ECONNRESET, + -EHOSTDOWN, + -EHOSTUNREACH, + -ENETDOWN, + -ENETRESET, + -ENETUNREACH, + -ENONET, + -ENOPROTOOPT, + -ENOTCONN, + -EPIPE, + -EPROTO, + -ESHUTDOWN, + -ETIMEDOUT); +} +_DEFINE_ABS_WRAPPER(DISCONNECT); + +/* Transient errors we might get on accept() that we should ignore. As per error handling comment in + * the accept(2) man page. */ +static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(intmax_t r) { + return ERRNO_IS_NEG_DISCONNECT(r) || + ERRNO_IS_NEG_TRANSIENT(r) || + r == -EOPNOTSUPP; +} +_DEFINE_ABS_WRAPPER(ACCEPT_AGAIN); + +/* Resource exhaustion, could be our fault or general system trouble */ +static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) { + return IN_SET(r, + -EMFILE, + -ENFILE, + -ENOMEM); +} +_DEFINE_ABS_WRAPPER(RESOURCE); + +/* Seven different errors for "operation/system call/ioctl/socket feature not supported" */ +static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { + return IN_SET(r, + -EOPNOTSUPP, + -ENOTTY, + -ENOSYS, + -EAFNOSUPPORT, + -EPFNOSUPPORT, + -EPROTONOSUPPORT, + -ESOCKTNOSUPPORT); +} +_DEFINE_ABS_WRAPPER(NOT_SUPPORTED); + +/* Two different errors for access problems */ +static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) { + return IN_SET(r, + -EACCES, + -EPERM); +} +_DEFINE_ABS_WRAPPER(PRIVILEGE); + +/* Three different errors for "not enough disk space" */ +static inline bool ERRNO_IS_NEG_DISK_SPACE(intmax_t r) { + return IN_SET(r, + -ENOSPC, + -EDQUOT, + -EFBIG); +} +_DEFINE_ABS_WRAPPER(DISK_SPACE); + +/* Three different errors for "this device does not quite exist" */ +static inline bool ERRNO_IS_NEG_DEVICE_ABSENT(intmax_t r) { + return IN_SET(r, + -ENODEV, + -ENXIO, + -ENOENT); +} +_DEFINE_ABS_WRAPPER(DEVICE_ABSENT); + +/* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and + * where it simply doesn't have the requested xattr the same way */ +static inline bool ERRNO_IS_NEG_XATTR_ABSENT(intmax_t r) { + return r == -ENODATA || + ERRNO_IS_NEG_NOT_SUPPORTED(r); +} +_DEFINE_ABS_WRAPPER(XATTR_ABSENT); |