
#define _GNU_SOURCE

#include <getopt.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <time.h>
#include <stdarg.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <link.h>

#define BUFFER_SIZE 256

#ifdef INTERCEPT_PRELOAD
#define load(name) \
    if (((__real_ ## name) = dlsym(RTLD_NEXT, #name)) == NULL) { \
        fprintf(stderr, "intercept: unable to load symbol '%s': %s", #name, strerror(errno)); \
        return; \
    }
#define sym(name) name
#define func_def(ret, name) static ret (*__real_ ## name)
#else
#define sym(name) __wrap_ ## name
#define func_def(ret, name) extern ret __real_ ## name
#endif

func_def(void *, malloc)(size_t);
func_def(void *, calloc)(size_t, size_t);
func_def(void *, realloc)(void *, size_t);
func_def(void *, reallocarray)(void *, size_t, size_t);
func_def(void, free)(void *);
func_def(int, getopt)(int, char *const [], const char *);
func_def(void, exit)(int);
func_def(ssize_t, read)(int, void *, size_t);
func_def(ssize_t, pread)(int, void *, size_t, off_t);
func_def(ssize_t, write)(int, const void *, size_t);
func_def(ssize_t, pwrite)(int, const void *, size_t, off_t);
func_def(int, close)(int);
func_def(int, sigaction)(int, const struct sigaction *, struct sigaction *);
func_def(int, sem_init)(sem_t *, int, unsigned int);
func_def(sem_t *, sem_open)(const char *, int, ...);
func_def(int, sem_post)(sem_t *);
func_def(int, sem_wait)(sem_t *);
func_def(int, sem_trywait)(sem_t *);
func_def(int, sem_timedwait)(sem_t *restrict, const struct timespec *restrict);
func_def(int, sem_getvalue)(sem_t *restrict, int *restrict);
func_def(int, sem_close)(sem_t *);
func_def(int, sem_unlink)(const char *);
func_def(int, sem_destroy)(sem_t *);
func_def(int, shm_open)(const char *, int, mode_t);
func_def(int, shm_unlink)(const char *);
func_def(void *, mmap)(void *, size_t, int, int, int, off_t);
func_def(int, munmap)(void *, size_t);
func_def(int, ftruncate)(int, off_t);
func_def(pid_t, fork)(void);
func_def(pid_t, wait)(int *);
func_def(pid_t, waitpid)(pid_t, int *, int);
func_def(int, execl)(const char *, const char *, ...);
func_def(int, execlp)(const char *, const char *, ...);
func_def(int, execle)(const char *, const char *, ...);
func_def(int, execv)(const char *, char *const[]);
func_def(int, execvp)(const char *, char *const[]);
func_def(int, execvpe)(const char *, char *const[], char *const[]);
func_def(int, execve)(const char *, char *const[], char *const[]);
func_def(int, fexecve)(int, char *const[], char *const[]);
func_def(int, pipe)(int[2]);
func_def(int, dup)(int);
func_def(int, dup2)(int, int);
func_def(int, dup3)(int, int, int);
func_def(int, socket)(int, int, int);
func_def(int, bind)(int, const struct sockaddr *, socklen_t);
func_def(int, listen)(int, int);
func_def(int, accept)(int, struct sockaddr *, socklen_t *);
func_def(int, connect)(int, const struct sockaddr *, socklen_t);
func_def(int, getaddrinfo)(const char *, const char *, const struct addrinfo *, struct addrinfo **);
func_def(void, freeaddrinfo)(struct addrinfo *);
func_def(ssize_t, send)(int, const void *, size_t, int);
func_def(ssize_t, sendto)(int, const void *, size_t, int, const struct sockaddr *, socklen_t);
func_def(ssize_t, sendmsg)(int, const struct msghdr *, int);
func_def(ssize_t, recv)(int, void *, size_t, int);
func_def(ssize_t, recvfrom)(int, void *, size_t, int, struct sockaddr *, socklen_t *);
func_def(ssize_t, recvmsg)(int, struct msghdr *, int);
func_def(ssize_t, getline)(char **, size_t *, FILE *);
func_def(ssize_t, getdelim)(char **, size_t *, int, FILE *);

#define func_idx_malloc 0
#define func_idx_calloc 1
#define func_idx_realloc 2
#define func_idx_reallocarray 3
#define func_idx_free 4
#define func_idx_getopt 5
#define func_idx_exit 6
#define func_idx_read 7
#define func_idx_pread 8
#define func_idx_write 9
#define func_idx_pwrite 10
#define func_idx_close 11
#define func_idx_sigaction 12
#define func_idx_sem_init 13
#define func_idx_sem_open 14
#define func_idx_sem_post 15
#define func_idx_sem_wait 16
#define func_idx_sem_trywait 17
#define func_idx_sem_timedwait 18
#define func_idx_sem_getvalue 19
#define func_idx_sem_close 20
#define func_idx_sem_unlink 21
#define func_idx_sem_destroy 22
#define func_idx_shm_open 23
#define func_idx_shm_unlink 24
#define func_idx_mmap 25
#define func_idx_munmap 26
#define func_idx_ftruncate 27
#define func_idx_fork 28
#define func_idx_wait 29
#define func_idx_waitpid 30
#define func_idx_execl 31
#define func_idx_execlp 32
#define func_idx_execle 33
#define func_idx_execv 34
#define func_idx_execvp 35
#define func_idx_execvpe 36
#define func_idx_execve 37
#define func_idx_fexecve 38
#define func_idx_pipe 39
#define func_idx_dup 40
#define func_idx_dup2 41
#define func_idx_dup3 42
#define func_idx_socket 43
#define func_idx_bind 44
#define func_idx_listen 45
#define func_idx_accept 46
#define func_idx_connect 47
#define func_idx_getaddrinfo 48
#define func_idx_freeaddrinfo 49
#define func_idx_send 50
#define func_idx_sendto 51
#define func_idx_sendmsg 52
#define func_idx_recv 53
#define func_idx_recvfrom 54
#define func_idx_recvmsg 55
#define func_idx_getline 56
#define func_idx_getdelim 57

#define FUNCTIONS \
    X(malloc) \
    X(calloc) \
    X(realloc) \
    X(reallocarray) \
    X(free) \
    X(getopt) \
    X(exit) \
    X(read) \
    X(pread) \
    X(write) \
    X(pwrite) \
    X(close) \
    X(sigaction) \
    X(sem_init) \
    X(sem_open) \
    X(sem_post) \
    X(sem_wait) \
    X(sem_trywait) \
    X(sem_timedwait) \
    X(sem_getvalue) \
    X(sem_close) \
    X(sem_unlink) \
    X(sem_destroy) \
    X(shm_open) \
    X(shm_unlink) \
    X(mmap) \
    X(munmap) \
    X(ftruncate) \
    X(fork) \
    X(wait) \
    X(waitpid) \
    X(execl) \
    X(execlp) \
    X(execle) \
    X(execv) \
    X(execvp) \
    X(execvpe) \
    X(execve) \
    X(fexecve) \
    X(pipe) \
    X(dup) \
    X(dup2) \
    X(dup3) \
    X(socket) \
    X(bind) \
    X(listen) \
    X(accept) \
    X(connect) \
    X(getaddrinfo) \
    X(freeaddrinfo) \
    X(send) \
    X(sendto) \
    X(sendmsg) \
    X(recv) \
    X(recvfrom) \
    X(recvmsg) \
    X(getline) \
    X(getdelim)

#define case_const(name) case name: return #name

#define flag_str(flags, name, buf) if (flags & name) strcat(buf, #name "|")

#define ret_addr __builtin_return_address(0)
#define ret_str ": %p (%s+%p%s%s%s%s)"
#define ret_data ret_addr, info.dli_fname, (long)ret_addr - (long)info.dli_fbase, info.dli_sname ? ", " : "", info.dli_sname ? info.dli_sname : "", get_file_line(src_file, (long)ret_addr - (long)info.dli_fbase, &info) != -1 ? ", " : "", src_file
#define lib_idx (((long)info.dli_fbase >> 12) & 0xFFF)

#define if_fail if (strncmp(msg_buf, "fail ", 5) == 0)

#define if_fail_int_errno(name) if_fail { set_errno(#name, msg_buf); return -1; }

#define if_fail_ptr_errno(name) if_fail { set_errno(#name, msg_buf); return NULL; }

#define if_invalid(name) if (strcmp(msg_buf, "ok") != 0) { fprintf(stderr, "intercept: %s: invalid command: '%s'\n", #name, msg_buf); }

#define if_modify_int(name, type_1, var_1) if (strncmp(msg_buf, "modify ", 7) == 0) { \
    char *end_ptr = NULL; \
    long val = strtol(msg_buf + 7, &end_ptr, 0); \
    if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) { \
        var_1 = (type_1)val; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_modify_int_int(name, type_1, var_1, type_2, var_2)  if (strncmp(msg_buf, "modify ", 7) == 0) { \
    char *end_ptr_1 = NULL, *end_ptr_2 = NULL; \
    long val_1 = strtol(msg_buf + 7, &end_ptr_1, 0); \
    long val_2 = 0; \
    if (end_ptr_1 != NULL) val_2 = strtol(end_ptr_1 + 1, &end_ptr_2, 0); \
    if (end_ptr_1 != NULL && end_ptr_1[0] == ',' && end_ptr_1 != msg_buf + 7 && \
        end_ptr_2 != NULL && end_ptr_2[0] == 0 && end_ptr_2 != end_ptr_1 + 1) \
    { \
        var_1 = (type_1)val_1; \
        var_2 = (type_2)val_2; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_modify_int_int_int(name, type_1, var_1, type_2, var_2, type_3, var_3) if (strncmp(msg_buf, "modify ", 7) == 0) { \
    char *end_ptr_1 = NULL, *end_ptr_2 = NULL, *end_ptr_3; \
    long val_1 = strtol(msg_buf + 7, &end_ptr_1, 0); \
    long val_2 = 0, val_3 = 0; \
    if (end_ptr_1 != NULL) val_2 = strtol(end_ptr_1 + 1, &end_ptr_2, 0); \
    if (end_ptr_2 != NULL) val_3 = strtol(end_ptr_2 + 1, &end_ptr_3, 0); \
    if (end_ptr_1 != NULL && end_ptr_1[0] == ',' && end_ptr_1 != msg_buf + 7 && \
        end_ptr_2 != NULL && end_ptr_2[0] == ',' && end_ptr_2 != end_ptr_1 + 1 && \
        end_ptr_3 != NULL && end_ptr_3[0] == 0 && end_ptr_3 != end_ptr_2 + 1) \
    { \
        var_1 = (type_1)val_1; \
        var_2 = (type_2)val_2; \
        var_3 = (type_3)val_3; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_return if (strcmp(msg_buf, "return") == 0) { msg("return"); return; }

#define if_return_int(name) if (strncmp(msg_buf, "return ", 7) == 0) { \
    char *end_ptr = NULL; \
    long val = strtol(msg_buf + 7, &end_ptr, 0); \
    if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) { \
        msg("return %i", val); \
        return val; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in return command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_return_int_errno(name) if (strncmp(msg_buf, "return ", 7) == 0) { \
    char *end_ptr = NULL; \
    long val = strtol(msg_buf + 7, &end_ptr, 0); \
    if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) { \
        msg("return %i; errno %s", val, strerrorname_np(errno)); \
        return val; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in return command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_return_int_errno_int(name, type_1, var_1, name_1) if (strncmp(msg_buf, "return ", 7) == 0) { \
    char *end_ptr_1 = NULL, *end_ptr_2 = NULL; \
    long val_1 = strtol(msg_buf + 7, &end_ptr_1, 0); \
    long val_2 = 0; \
    if (end_ptr_1 != NULL) val_2 = strtol(end_ptr_1 + 1, &end_ptr_2, 0); \
    if (end_ptr_1 != NULL && end_ptr_1[0] == ',' && end_ptr_1 != msg_buf + 7 && \
        end_ptr_2 != NULL && end_ptr_2[0] == 0 && end_ptr_2 != end_ptr_1 + 1) \
    { \
        var_1 = (type_1)val_2; \
        msg("return %i; errno %s; %s=%i", val_1, strerrorname_np(errno), name_1, var_1); \
        return val_1; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in return command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_return_ptr_errno(name) if (strncmp(msg_buf, "return ", 7) == 0) { \
    char *end_ptr = NULL; \
    long val = strtol(msg_buf + 7, &end_ptr, 0); \
    if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) { \
        msg("return %p; errno %s", val, strerrorname_np(errno)); \
        return (void *)val; \
    } else { \
        fprintf(stderr, "intercept: %s: invalid args in return command: '%s'\n", #name, msg_buf + 7); \
    } }

#define if_error(err) if (strcmp(msg_buf + 5, #err) == 0) { errno = err; }

struct line_debug_info {
    long addr;
    int line_num;
    char filename[64];
};

struct obj_debug_info {
    unsigned long addr;
    int n_entries;
    struct line_debug_info *entries;
};

static void set_errno(const char *name, const char *msg_buf) {
    char *end_ptr = NULL;
    long val = strtol(msg_buf + 5, &end_ptr, 0);
    if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 5) {
        errno = (int)val;
    } else if_error(EPERM)
    else if_error(ENOENT)
    else if_error(ESRCH)
    else if_error(EINTR)
    else if_error(EIO)
    else if_error(ENXIO)
    else if_error(E2BIG)
    else if_error(ENOEXEC)
    else if_error(EBADF)
    else if_error(ECHILD)
    else if_error(EAGAIN)
    else if_error(ENOMEM)
    else if_error(EACCES)
    else if_error(EFAULT)
    else if_error(ENOTBLK)
    else if_error(EBUSY)
    else if_error(EEXIST)
    else if_error(EXDEV)
    else if_error(ENODEV)
    else if_error(ENOTDIR)
    else if_error(EISDIR)
    else if_error(EINVAL)
    else if_error(ENFILE)
    else if_error(EMFILE)
    else if_error(ENOTTY)
    else if_error(ETXTBSY)
    else if_error(EFBIG)
    else if_error(ENOSPC)
    else if_error(ESPIPE)
    else if_error(EROFS)
    else if_error(EMLINK)
    else if_error(EPIPE)
    else if_error(EDOM)
    else if_error(ERANGE)
    else if_error(EDEADLK)
    else if_error(ENAMETOOLONG)
    else if_error(ENOLCK)
    else if_error(ENOSYS)
    else if_error(ENOTEMPTY)
    else if_error(ELOOP)
    else if_error(EWOULDBLOCK)
    else if_error(ENOMSG)
    else if_error(EIDRM)
    else if_error(ECHRNG)
    else if_error(EL2NSYNC)
    else if_error(EL3HLT)
    else if_error(EL3RST)
    else if_error(ELNRNG)
    else if_error(EUNATCH)
    else if_error(ENOCSI)
    else if_error(EL2HLT)
    else if_error(EBADE)
    else if_error(EBADR)
    else if_error(EXFULL)
    else if_error(ENOANO)
    else if_error(EBADRQC)
    else if_error(EBADSLT)
    else if_error(EDEADLOCK)
    else if_error(EBFONT)
    else if_error(ENOSTR)
    else if_error(ENODATA)
    else if_error(ETIME)
    else if_error(ENOSR)
    else if_error(ENONET)
    else if_error(ENOPKG)
    else if_error(EREMOTE)
    else if_error(ENOLINK)
    else if_error(EADV)
    else if_error(ESRMNT)
    else if_error(ECOMM)
    else if_error(EPROTO)
    else if_error(EMULTIHOP)
    else if_error(EDOTDOT)
    else if_error(EBADMSG)
    else if_error(EOVERFLOW)
    else if_error(ENOTUNIQ)
    else if_error(EBADFD)
    else if_error(EREMCHG)
    else if_error(ELIBACC)
    else if_error(ELIBBAD)
    else if_error(ELIBSCN)
    else if_error(ELIBMAX)
    else if_error(ELIBEXEC)
    else if_error(EILSEQ)
    else if_error(ERESTART)
    else if_error(ESTRPIPE)
    else if_error(EUSERS)
    else if_error(ENOTSOCK)
    else if_error(EDESTADDRREQ)
    else if_error(EMSGSIZE)
    else if_error(EPROTOTYPE)
    else if_error(ENOPROTOOPT)
    else if_error(EPROTONOSUPPORT)
    else if_error(ESOCKTNOSUPPORT)
    else if_error(EOPNOTSUPP)
    else if_error(EPFNOSUPPORT)
    else if_error(EAFNOSUPPORT)
    else if_error(EADDRINUSE)
    else if_error(EADDRNOTAVAIL)
    else if_error(ENETDOWN)
    else if_error(ENETRESET)
    else if_error(ECONNABORTED)
    else if_error(ECONNRESET)
    else if_error(ENOBUFS)
    else if_error(EISCONN)
    else if_error(ENOTCONN)
    else if_error(ESHUTDOWN)
    else if_error(ETOOMANYREFS)
    else if_error(ETIMEDOUT)
    else if_error(ECONNREFUSED)
    else if_error(EHOSTDOWN)
    else if_error(EHOSTUNREACH)
    else if_error(EALREADY)
    else if_error(EINPROGRESS)
    else if_error(ESTALE)
    else if_error(EUCLEAN)
    else if_error(ENOTNAM)
    else if_error(ENAVAIL)
    else if_error(EISNAM)
    else if_error(EREMOTEIO)
    else if_error(EDQUOT)
    else if_error(ENOMEDIUM)
    else if_error(EMEDIUMTYPE)
    else if_error(ECANCELED)
    else if_error(ENOKEY)
    else if_error(EKEYEXPIRED)
    else if_error(EKEYREVOKED)
    else if_error(EKEYREJECTED)
    else if_error(EOWNERDEAD)
    else if_error(ENOTRECOVERABLE)
    else if_error(ERFKILL)
    else if_error(EHWPOISON)
    else {
        errno = 0;
        fprintf(stderr, "intercept: %s: invalid error code in fail command: '%s'\n", name, msg_buf + 5);
    }
}

static const char *getdomainstr(const int domain) {
    switch (domain) {
        case_const(AF_UNSPEC);
        case_const(AF_UNIX);
        case_const(AF_INET);
        case_const(AF_INET6);
        default: return "?";
    }
}

static const char *getsocktype(const int type) {
    switch (type) {
        case_const(SOCK_STREAM);
        case_const(SOCK_DGRAM);
        case_const(SOCK_RAW);
        case_const(SOCK_RDM);
        case_const(SOCK_SEQPACKET);
        case_const(SOCK_DCCP);
        case_const(SOCK_PACKET);
        default: return "?";
    }
}

static const char *getaddrinfoerror(const int ret) {
    switch (ret) {
        case 0: return "SUCCESS";
        case_const(EAI_BADFLAGS);
        case_const(EAI_NONAME);
        case_const(EAI_AGAIN);
        case_const(EAI_FAIL);
        case_const(EAI_NODATA);
        case_const(EAI_FAMILY);
        case_const(EAI_SOCKTYPE);
        case_const(EAI_SERVICE);
        case_const(EAI_ADDRFAMILY);
        case_const(EAI_MEMORY);
        case_const(EAI_SYSTEM);
        case_const(EAI_OVERFLOW);
        case_const(EAI_INPROGRESS);
        case_const(EAI_CANCELED);
        case_const(EAI_NOTCANCELED);
        case_const(EAI_ALLDONE);
        case_const(EAI_INTR);
        case_const(EAI_IDN_ENCODE);
        default: return "?";
    }
}

static char *strtostr(char *ptr, char **end_ptr, char *buf) {
    if (ptr[0] != '"') return NULL;
    int i = 0, esc = 0, hex = 0;
    for (*end_ptr = ptr + 1; *end_ptr[0] != 0; (*end_ptr)++) {
        char ch = *end_ptr[0];
        if (esc) {
            if (esc == 2) {
                if (ch >= '0' && ch <= '9') {
                    hex = (ch - '0') << 4;
                } else if (ch >= 'A' && ch <= 'F') {
                    hex = (ch - 'A' + 10) << 4;
                } else if (ch >= 'a' && ch <= 'f') {
                    hex = (ch - 'a' + 10) << 4;
                } else {
                    return NULL;
                }
                esc = 3;
                continue;
            } else if (esc == 3) {
                if (ch >= '0' && ch <= '9') {
                    hex |= (ch - '0');
                } else if (ch >= 'A' && ch <= 'F') {
                    hex |= (ch - 'A' + 10);
                } else if (ch >= 'a' && ch <= 'f') {
                    hex |= (ch - 'a' + 10);
                } else {
                    return NULL;
                }
                buf[i++] = (char)hex;
                esc = 0;
                continue;
            }
            switch (ch) {
                case 'a': ch = '\a'; break;
                case 'b': ch = '\b'; break;
                case 't': ch = '\t'; break;
                case 'n': ch = '\n'; break;
                case 'v': ch = '\v'; break;
                case 'f': ch = '\f'; break;
                case 'r': ch = '\r'; break;
                case '0': ch = 0; break;
                case 'x': esc = 2; continue;
                case '"':
                case '\\':
                    break;
                default: return NULL;
            }
            buf[i++] = ch;
            esc = 0;
        } else if (ch == '\\') {
            esc = 1;
        } else if (ch == '"') {
            (*end_ptr)++;
            return buf;
        } else {
            buf[i++] = ch;
        }
    }
    return NULL;
}

static int mode = 0;
static int intercept = 0;
static int verbosity = 0;
static uint8_t func_flags[256];
static uint8_t lib_flags[0x1000];
static struct obj_debug_info line_info[16];

static size_t msg_bytes(char *buf, size_t maxlen, size_t len, const char *str, int flags) {
    size_t offset = flags & 1 ? snprintf(buf, maxlen, "\"") : snprintf(buf, maxlen, "%p:\"", str);
    for (int i = 0; i < len && offset <= maxlen - 2; i++) {
        const uint8_t ch = str[i];
        if (ch == '\\' || ch == '"') {
            buf[offset++] = '\\';
            buf[offset++] = (char)ch;
        } else if (ch == '\t') {
            buf[offset++] = '\\';
            buf[offset++] = 't';
        } else if (ch == '\n') {
            buf[offset++] = '\\';
            buf[offset++] = 'n';
        } else if (ch == '\r') {
            buf[offset++] = '\\';
            buf[offset++] = 'r';
        } else if (ch < 0x20 || ch >= 0x7F) {
            offset += snprintf(buf + offset, maxlen - offset, "\\x%02x", ch);
        } else {
            buf[offset++] = (char)ch;
        }
    }
    if (offset <= maxlen - 2) {
        buf[offset++] = '"';
        buf[offset] = 0;
    }
    return offset;
}

static size_t msg_str(char *buf, size_t maxlen, const char *str, int flags) {
    return msg_bytes(buf, maxlen, str == NULL ? 0 : strlen(str), str, flags);
}

static size_t msg_array_str(char *buf, size_t maxlen, char *const array[], int n) {
    size_t offset = snprintf(buf, maxlen, "%p:[", (void *)array);
    for (int i = 0; i < n && offset <= maxlen - 2; i++) {
        if (i > 0) {
            buf[offset++] = ',';
            buf[offset++] = ' ';
        }
        offset += msg_str(buf + offset, maxlen - offset, array[i], 0);
    }
    if (offset <= maxlen - 2) {
        buf[offset++] = ']';
        buf[offset] = 0;
    }
    return offset;
}

static size_t msg_sockaddr(char *buf, const size_t size, const struct sockaddr *addr, const socklen_t len) {
    size_t offset = 0;
    if (addr == NULL) {
        offset += snprintf(buf + offset, size - offset, "%p:{}", (void *)addr);
    } else if (addr->sa_family == AF_UNIX) {
        // 0x0:{sa_family: 1:AF_UNIX, sun_path: "/path/"}
        const struct sockaddr_un *un = (const struct sockaddr_un *)addr;
        offset += snprintf(buf + offset, size - offset, "%p:{sa_family: %i:AF_UNIX, sun_Path: ", (void *)un, AF_UNIX);
        offset += msg_str(buf + offset, size - offset, un->sun_path, 1);
        offset += snprintf(buf + offset, size - offset, "}");
    } else if (addr->sa_family == AF_INET && len == sizeof(struct sockaddr_in)) {
        // 0x0:{sa_family: 2:AF_INET, sin_addr: "192.168.0.1", sin_port: 80}
        const struct sockaddr_in *in = (const struct sockaddr_in *)addr;
        char addr_buf[INET_ADDRSTRLEN];
        offset += snprintf(buf + offset, size - offset, "%p:{sa_family: %i:AF_INET, sin_addr: \"%s\", sin_port: %i}", (void *)in, AF_INET, inet_ntop(AF_INET, &in->sin_addr, addr_buf, sizeof(addr_buf)), ntohs(in->sin_port));
    } else if (addr->sa_family == AF_INET6 && len == sizeof(struct sockaddr_in6)) {
        // 0x0:{sa_family: 10:AF_INET6, sin6_addr: "::1", sin6_port: 80, sin6_scope_id: 0}
        const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)addr;
        char addr_buf[INET6_ADDRSTRLEN];
        offset += snprintf(buf + offset, size - offset, "%p:{sa_family: %i:AF_INET6, sin6_addr: \"%s\", sin6_port: %i, sin6_scope_id: %i}", (void *)in6, AF_INET6, inet_ntop(AF_INET6, &in6->sin6_addr, addr_buf, sizeof(addr_buf)), ntohs(in6->sin6_port), in6->sin6_scope_id);
    } else {
        // 0x0:{sa_family: 7:?, sa_data: "\x01\x02\x03\x04"}
        offset += snprintf(buf + offset, size - offset, "%p:{sa_family: %i:%s, sa_data: ", (void *)addr, addr->sa_family, getdomainstr(addr->sa_family));
        offset += msg_bytes(buf + offset, size - offset, len - sizeof(addr->sa_family), addr->sa_data, 1);
        offset += snprintf(buf + offset, size - offset, "}");
    }
    return offset;
}

static void msg(const char *fmt, ...) {
    if (!intercept || mode == -1) return;
    char buf[8192], sub_fmt[16];
    int sub_fmt_p = 0;

    va_list args;
    va_start(args, fmt);

    struct timespec spec;
    clock_gettime(CLOCK_REALTIME, &spec);
    size_t offset = snprintf(buf, sizeof(buf), "%li.%09li %i %i ", spec.tv_sec, spec.tv_nsec, getpid(), gettid());
    for (char ch, state = 0; (ch = *fmt) != 0 && offset < sizeof(buf); fmt++) {
        if (state == '%') {
            if (ch == '%') {
                buf[offset++] = ch;
                state = 0;
                continue;
            } else if (ch == 'e') {
                state = 'e';
                continue;
            } else if (ch == 'q') {
                state = 'q';
                continue;
            } else if (ch == 'a') {
                state = 'a';
                continue;
            } else {
                sub_fmt_p = 0;
                state = '_';
                sub_fmt[sub_fmt_p++] = '%';
                sub_fmt[sub_fmt_p] = 0;
            }
        }
        if (state == '_') {
            sub_fmt[sub_fmt_p++] = ch;
            sub_fmt[sub_fmt_p] = 0;
            if (ch == 's' || ch == 'i' || ch == 'd' || ch == 'o' || ch == 'u' || ch == 'x' || ch == 'X' || ch == 'f' || ch == 'p') {
                state = 0;
                offset += snprintf(buf + offset, sizeof(buf) - offset, sub_fmt, va_arg(args, long int));
            }
        } else if (state == 'e') {
            if (ch == 's') {
                // escaped string
                const char *str = va_arg(args, const char *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:\"\"", (void *)str);
                } else {
                    offset += msg_str(buf + offset, sizeof(buf) - offset, str, 0);
                }
            } else if (ch == 'b') {
                // escaped byte sequence
                const int len = va_arg(args, int);
                const char *str = va_arg(args, const char *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:\"\"", (void *)str);
                } else {
                    offset += msg_bytes(buf + offset, sizeof(buf) - offset, len, str, 0);
                }
            }
            state = 0;
        } else if (state == 'q') {
            if (ch == 'a') {
                // address, struct sockaddr
                const int len = va_arg(args, int);
                const struct sockaddr *addr = va_arg(args, struct sockaddr *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:{}", (void *)addr);
                } else {
                    offset += msg_sockaddr(buf + offset, sizeof(buf) - offset, addr, len);
                }
            } else if (ch == 'm') {
                // struct msghdr
                const struct msghdr *msg = va_arg(args, struct msghdr *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:{}", (void *)msg);
                } else {
                    // TODO format struct msghdr
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:{}", (void *)msg);
                }
            } else if (ch == 't') {
                // struct timespec
                const struct timespec *tv = va_arg(args, struct timespec *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:{}", (void *)tv);
                } else {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:{tv_sec: %li, tv_nsec: %li}", (void *)tv, tv->tv_sec, tv->tv_nsec);
                }
            } else if (ch == 'i') {
                // struct addrinfo
                const struct addrinfo *ai = va_arg(args, struct addrinfo *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:[]", (void *)ai);
                } else {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:[", (void *)ai);
                    for (; ai != NULL; ai = ai->ai_next) {
                        char fstr[256] = "|";
                        flag_str(ai->ai_flags, AI_PASSIVE, fstr);
                        flag_str(ai->ai_flags, AI_CANONNAME, fstr);
                        flag_str(ai->ai_flags, AI_NUMERICHOST, fstr);
                        flag_str(ai->ai_flags, AI_V4MAPPED, fstr);
                        flag_str(ai->ai_flags, AI_ALL, fstr);
                        flag_str(ai->ai_flags, AI_ADDRCONFIG, fstr);
                        flag_str(ai->ai_flags, AI_CANONIDN, fstr);
                        flag_str(ai->ai_flags, AI_NUMERICSERV, fstr);
                        if (ai->ai_flags & ~(AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_V4MAPPED | AI_ALL | AI_ADDRCONFIG | AI_CANONIDN | AI_NUMERICSERV)) strcat(fstr, "?|");
                        offset += snprintf(buf + offset, sizeof(buf) - offset, "{ai_flags: 0x%x:%s, ai_family: %i:%s, ai_socktype: %i:%s, ai_protocol: %i, ai_addrlen: %i, ai_addr: ", ai->ai_flags, fstr, ai->ai_family, getdomainstr(ai->ai_family), ai->ai_socktype, getsocktype(ai->ai_socktype), ai->ai_protocol, ai->ai_addrlen);
                        offset += msg_sockaddr(buf + offset, sizeof(buf) - offset, ai->ai_addr, ai->ai_addrlen);
                        offset += snprintf(buf + offset, sizeof(buf) - offset, ", ai_canonname: ");
                        offset += msg_str(buf + offset, sizeof(buf) - offset, ai->ai_canonname, 0);
                        offset += snprintf(buf + offset, sizeof(buf) - offset, ", ai_next: %p}%s", (void *)ai->ai_next, ai->ai_next ? ", " : "");
                    }
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "]");
                }
            } else {
                // error
                va_end(args);
                return;
            }
            state = 0;
        } else if (state == 'a') {
            if (ch == 's') {
                // string array
                const int len = va_arg(args, int);
                char *const *array = va_arg(args, char *const *);
                if (!verbosity) {
                    offset += snprintf(buf + offset, sizeof(buf) - offset, "%p:[]", (void *)array);
                } else {
                    offset += msg_array_str(buf + offset, sizeof(buf) - offset, array, len);
                }
            } else {
                // error
                va_end(args);
                return;
            }
            state = 0;
        } else if (ch == '%') {
            state = '%';
        } else {
            buf[offset++] = ch;
        }
    }
    if (offset <= sizeof(buf) - 2) {
        buf[offset++] = '\n';
        buf[offset] = 0;
    }
    const size_t size = offset >= sizeof(buf) ? sizeof(buf) - 1 : offset;

    ssize_t ret;
    for (size_t written = 0; written < size; written += ret) {
        if ((ret = __real_write(intercept, buf, size)) == -1) {
            if (errno == EINTR) {
                ret = 0;
                continue;
            }
            va_end(args);
            return;
        }
    }

    va_end(args);
}

static void rcv(char *buf, const int size) {
    if (!intercept) return;
    size_t num = 0;
    for (ssize_t ret; num == 0 || buf[num - 1] != '\n'; num += ret) {
        if ((ret = __real_read(intercept, buf, size)) == -1) {
            if (errno == EINTR) {
                ret = 0;
                continue;
            }
            buf[0] = 0;
            return;
        }
    }
    buf[num - 1] = 0;
}

static void fin(void) {
    if (intercept && mode > 2)
        __real_close(intercept);
    if (mode > 0)
        fprintf(stderr, "intercept: stopped\n");
    mode = 0, intercept = 0;
}

static int get_file_line(char *buf, long rel_ret, Dl_info *info) {
    buf[0] = 0;
    const long idx = ((long)info->dli_fbase >> 12) & 0xFFF;
    if (!(lib_flags[idx] & 2))
        return -1;

    for (int i = 0; i < 16; i++) {
        if (line_info[i].addr != (long)info->dli_fbase)
            continue;
        for (int j = line_info[i].n_entries - 1; j >= 0; j--) {
            struct line_debug_info *entry = &line_info[i].entries[j];
            if (entry->addr < rel_ret) {
                sprintf(buf, "%s:%i", entry->filename, entry->line_num);
                return entry->line_num;
            }
        }
    }

    return -1;
}

static int iter_lib_debug(struct dl_phdr_info *info, size_t size, void *data) {
    const long idx = ((long)info->dlpi_addr >> 12) & 0xFFF;
    if (!lib_flags[idx]) return 0;

    char name_buf[256] = "";
    const char *name = info->dlpi_name;
    if (name[0] == 0) {
        if (readlink("/proc/self/exe", name_buf, sizeof(name_buf)) == -1)
            return 0;
        name = name_buf;
    }

    char cmd_buf[512];
    snprintf(cmd_buf, sizeof(cmd_buf), "readelf --debug-dump=decodedline '%s' | grep 0x | sort -k3", name_buf);

    int fds[2];
    __real_pipe(fds);
    pid_t pid = __real_fork();
    switch (pid) {
        case -1:
            return 0;
        case 0:
            // child
            __real_close(fds[0]);  // close read end
            __real_close(STDERR_FILENO);
            __real_dup2(fds[1], STDOUT_FILENO);
            __real_close(fds[1]);
            __real_execle("/bin/sh", "sh", "-c", cmd_buf, NULL, NULL);
        default:
            // parent
            __real_close(fds[1]);  // close write end
    }

    char state = 'A';
    char buf[1024];
    char line[3][256] = {0};
    int n = 0, n_entries = 0;
    struct line_debug_info *entries = NULL;
    for (ssize_t num = -2; (num != -1 && num != 0) || (num == -1 && errno == EINTR); num = __real_read(fds[0], buf, sizeof(buf))) {
        for (int i = 0; i < num; i++) {
            char ch = buf[i];
            if (state == 'A' || state == 'B' || state == 'C') {
                // file name | line number | starting address
                if (ch == ' ') {
                    if (n == 0)
                        continue;
                    state++;
                    n = 0;
                    continue;
                }
                line[state - 'A'][n++] = ch;
                line[state - 'A'][n] = 0;
            } else if (state == 'D' && ch == '\n') {
                state = 'A';
                long addr = strtol(line[2], NULL, 0);
                long line_num = strtol(line[1], NULL, 0);
                entries = __real_realloc(entries, ++n_entries * sizeof(struct line_debug_info));
                entries[n_entries - 1].addr = addr;
                entries[n_entries - 1].line_num = (int)line_num;
                strncpy(entries[n_entries - 1].filename, line[0], sizeof(entries[n_entries - 1].filename));
            }
        }
    }

    while (__real_waitpid(pid, NULL, 0) == -1) {
        if (errno != EINTR) return 0;
    }

    if (n_entries > 0) {
        lib_flags[idx] |= 2;
        for (int i = 0; i < 16; i++) {
            if (line_info[i].addr != 0)
                continue;
            line_info[i].addr = info->dlpi_addr;
            line_info[i].n_entries = n_entries;
            line_info[i].entries = entries;
        }
    }

    return 0;
}

static int iter_lib(struct dl_phdr_info *info, size_t size, void *data) {
    char *name1 = (char *)data;
    const char *name2 = info->dlpi_name;
    const long idx = ((long)info->dlpi_addr >> 12) & 0xFFF;

    const int wildcard = name1[strlen(name1) - 1] == '*';
    int not = 0;
    if (name1[0] == '-') {
        name1++;
        not = 1;
    }

    if (name2[0] == 0) {
        lib_flags[idx] |= 1;
        return 0;
    }

    if (strcmp(name1, name2) == 0 || (wildcard && strncmp(name1, name2, strlen(name1) - 1) == 0)) {
        if (not) {
            lib_flags[idx] &= ~1;
        } else {
            lib_flags[idx] |= 1;
        }
    }
    return 0;
}

static void init(void) {
    if (mode) return;
    mode = -1;
#ifdef INTERCEPT_PRELOAD
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#define X(name) load(name);
    FUNCTIONS
#undef X
#pragma GCC diagnostic pop
#endif
    atexit(fin);
    const char *val = getenv("INTERCEPT");
    if (val && strcmp(val, "stdout") == 0) {
        fprintf(stderr, "intercept: intercepting function/system calls and logging to stdout\n");
        intercept = STDOUT_FILENO;
        mode = 1;
    } else if (val && strcmp(val, "stderr") == 0) {
        fprintf(stderr, "intercept: intercepting function/system calls and logging to stderr\n");
        intercept = STDERR_FILENO;
        mode = 2;
    } else if (val && strncmp(val, "file:", 5) == 0) {
        if ((intercept = open(val + 5, O_CREAT | O_APPEND | O_WRONLY, 0644)) == -1) {
            fprintf(stderr, "intercept: unable to open log file '%s': %s\n" "intercept: not logging or manipulating function/system calls\n", val + 5, strerror(errno));
            errno = 0;
            mode = -1;
            return;
        }
        fprintf(stderr, "intercept: intercepting function/system calls and logging to file\n");
        mode = 3;
    } else if (val && strncmp(val, "unix:", 5) == 0) {
        if ((intercept = __real_socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
            fprintf(stderr, "intercept: unable to open unix socket '%s': %s\n" "intercept: not logging or manipulating function/system calls\n", val + 5, strerror(errno));
            errno = 0;
            mode = -1;
            return;
        }
        struct sockaddr_un addr = {.sun_family = AF_UNIX};
        strcpy(addr.sun_path, val + 5);
        if (__real_connect(intercept, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
            fprintf(stderr, "intercept: unable to connect to unix socket '%s': %s\n" "intercept: not logging or manipulating function/system calls\n", val + 5, strerror(errno));
            __real_close(intercept);
            errno = 0;
            mode = -1;
            return;
        }
        fprintf(stderr, "intercept: intercepting function/system calls and logging to unix socket\n");
        char buf[256] = "";
        const ssize_t ret = readlink("/proc/self/exe", buf, sizeof(buf));
        mode = 4;
        msg("PID:%li%s%s", getpid(), ret != -1 ? ";PATH:" : "", buf);
    } else if (val && strncmp(val, "tcp://", 6) == 0) {
        // TODO socket/tcp mode
        mode = 5;
    } else {
        fprintf(stderr, "intercept: not logging or manipulating function/system calls\n");
    }

    char *verbose = getenv("INTERCEPT_VERBOSE");
    if (!verbose || verbose[0] == 0 || strcmp(verbose, "0") == 0) {
        verbosity = 0;
    } else {
        verbosity = 1;
    }

    char *fncts = getenv("INTERCEPT_FUNCTIONS");
    if (!fncts) {
        memset(func_flags, 1, sizeof(func_flags));
    } else {
        memset(func_flags, 0, sizeof(func_flags));
        const char *func_names_ordered[] = {
            #define X(name) #name,
                FUNCTIONS
            #undef X
        };
        char *func_names[256] = {fncts, NULL};
        const size_t len = strlen(fncts);
        for (int i = 0, p = 0; i < len; i++) {
            const char ch = fncts[i];
            if (ch == ',' || ch == ';' || ch == ':' || ch == 0) {
                fncts[i] = 0;
                func_names[++p] = fncts + i + 1;
            }
        }
        for (int i = 0; func_names[i] != NULL; i++) {
            const char *name1 = func_names[i];
            const int wildcard = name1[strlen(name1) - 1] == '*';
            int not = 0;
            if (name1[0] == '-') {
                name1++;
                not = 1;
            }
            for (int j = 0; j < sizeof(func_names_ordered) / sizeof(char *); j++) {
                const char *name2 = func_names_ordered[j];
                if (strcmp(name1, name2) == 0 || (wildcard && strncmp(name1, name2, strlen(name1) - 1) == 0)) {
                    if (not) {
                        func_flags[j] &= ~1;
                    } else {
                        func_flags[j] |= 1;
                    }
                }
            }
        }
    }

    char *libs = getenv("INTERCEPT_LIBRARIES");
    if (!libs) {
        memset(lib_flags, 1, sizeof(lib_flags));
    } else {
        memset(lib_flags, 0, sizeof(lib_flags));
        char *lib_names[0x1000] = {libs, NULL};
        const size_t len = strlen(libs);
        for (int i = 0, p = 0; i < len; i++) {
            const char ch = libs[i];
            if (ch == ',' || ch == ';' || ch == ':' || ch == 0) {
                libs[i] = 0;
                lib_names[++p] = libs + i + 1;
            }
        }

        for (int i = 0; lib_names[i] != NULL; i++) {
            dl_iterate_phdr(iter_lib, lib_names[i]);
        }
    }

    memset(line_info, 0, sizeof(line_info));
    dl_iterate_phdr(iter_lib_debug, NULL);
}

void *sym(malloc)(size_t size) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_malloc] || !lib_flags[lib_idx]) return __real_malloc(size);
    char src_file[256];
    msg("malloc(%li)" ret_str, size, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(malloc, size_t, size)
        else if_return_ptr_errno(malloc)
        else if_fail_ptr_errno(malloc)
        else if_invalid(malloc)
    }
    void *ret = __real_malloc(size);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

void *sym(calloc)(size_t nmemb, size_t size) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_calloc] || !lib_flags[lib_idx]) return __real_calloc(nmemb, size);
    char src_file[256];
    msg("calloc(%li, %li)" ret_str, nmemb, size, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(calloc, size_t, nmemb, size_t, size)
        else if_return_ptr_errno(calloc)
        else if_fail_ptr_errno(calloc)
        else if_invalid(calloc)
    }
    void *ret = __real_calloc(nmemb, size);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

void *sym(realloc)(void *ptr, size_t size) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_realloc] || !lib_flags[lib_idx]) return __real_realloc(ptr, size);
    char src_file[256];
    msg("realloc(%p, %li)" ret_str, ptr, size, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(realloc, void *, ptr, size_t, size)
        else if_return_ptr_errno(realloc)
        else if_fail_ptr_errno(realloc)
        else if_invalid(realloc)
    }
    void *ret = __real_realloc(ptr, size);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

void *sym(reallocarray)(void *ptr, size_t nmemb, size_t size) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_reallocarray] || !lib_flags[lib_idx]) return __real_reallocarray(ptr, nmemb, size);
    char src_file[256];
    msg("reallocarray(%p, %li)" ret_str, ptr, size, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int_int(reallocarray, void *, ptr, size_t, nmemb, size_t, size)
        else if_return_ptr_errno(reallocarray)
        else if_fail_ptr_errno(reallocarray)
        else if_invalid(reallocarray)
    }
    void *ret = __real_reallocarray(ptr, nmemb, size);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

void sym(free)(void *ptr) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_free] || !lib_flags[lib_idx]) { __real_free(ptr); return; }
    char src_file[256];
    msg("free(%p)" ret_str, ptr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(free, void *, ptr)
        else if_return
        else if_invalid(free)
    }
    __real_free(ptr);
    msg("return");
}

int sym(getopt)(const int argc, char *const argv[], const char *shortopts) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_getopt] || !lib_flags[lib_idx]) return __real_getopt(argc, argv, shortopts);
    char src_file[256];
    msg("getopt(%i, %as, %es)" ret_str, argc, argc, argv, shortopts, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // modify <argc>,<argv>,<shortopts>
            // TODO getopt modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "getopt");
        } else if_return_int(getopt)
        else if_invalid(getopt)
    }
    const int ret = __real_getopt(argc, argv, shortopts);
    msg("return %i; optind %i", ret, optind);
    return ret;
}

void sym(exit)(int status) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_exit] || !lib_flags[lib_idx]) __real_exit(status);
    char src_file[256];
    msg("exit(%i)" ret_str, status, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int("exit", int, status)
        else if_invalid(exit)
    }
    __real_exit(status);
}

ssize_t sym(read)(int fildes, void *buf, size_t nbyte) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_read] || !lib_flags[lib_idx]) return __real_read(fildes, buf, nbyte);
    char src_file[256];
    msg("read(%i, %p, %i)" ret_str, fildes, buf, nbyte, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO read modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "read");
        } else if_return_int_errno(read)
        else if_fail_int_errno(read)
        else if_invalid(read)
    }
    const ssize_t ret = __real_read(fildes, buf, nbyte);
    if (ret >= 0) {
        msg("return %i; errno %s; buf=%eb", ret, strerrorname_np(errno), ret, buf);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

ssize_t sym(pread)(int fildes, void *buf, size_t nbyte, off_t offset) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_pread] || !lib_flags[lib_idx]) return __real_pread(fildes, buf, nbyte, offset);
    char src_file[256];
    msg("pread(%i, %p, %i, %i)" ret_str, fildes, buf, nbyte, offset, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO pread modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "pread");
        } else if_return_int_errno(pread)
        else if_fail_int_errno(pread)
        else if_invalid(pread)
    }
    const ssize_t ret = __real_pread(fildes, buf, nbyte, offset);
    if (ret >= 0) {
        msg("return %i; errno %s; buf=%eb", ret, strerrorname_np(errno), ret, buf);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

ssize_t sym(write)(int fildes, const void *buf, size_t nbyte) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_write] || !lib_flags[lib_idx]) return __real_write(fildes, buf, nbyte);
    char src_file[256];
    msg("write(%i, %eb, %i)" ret_str, fildes, nbyte, buf, nbyte, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO write modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "write");
        } else if_return_int_errno(write)
        else if_fail_int_errno(write)
        else if_invalid(write)
    }
    const ssize_t ret = __real_write(fildes, buf, nbyte);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

ssize_t sym(pwrite)(int fildes, const void *buf, size_t nbyte, off_t offset) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_pwrite] || !lib_flags[lib_idx]) return __real_pwrite(fildes, buf, nbyte, offset);
    char src_file[256];
    msg("pwrite(%i, %eb, %i, %i)" ret_str, fildes, nbyte, buf, nbyte, offset, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO pwrite modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "pwrite");
        } else if_return_int_errno(pwrite)
        else if_fail_int_errno(pwrite)
        else if_invalid(pwrite)
    }
    const ssize_t ret = __real_pwrite(fildes, buf, nbyte, offset);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(close)(int fildes) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_close] || !lib_flags[lib_idx]) return __real_close(fildes);
    char src_file[256];
    msg("close(%i)" ret_str, fildes, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(close, int, fildes)
        else if_return_int_errno(close)
        else if_fail_int_errno(close)
        else if_invalid(close)
    }
    const int ret = __real_close(fildes);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

static const char *getsigstr(int sig) {
    switch (sig) {
        case_const(SIGINT);
        case_const(SIGILL);
        case_const(SIGABRT);
        case_const(SIGFPE);
        case_const(SIGSEGV);
        case_const(SIGTERM);
        case_const(SIGHUP);
        case_const(SIGQUIT);
        case_const(SIGTRAP);
        case_const(SIGKILL);
        case_const(SIGPIPE);
        case_const(SIGALRM);
        case_const(SIGSTKFLT);
        case_const(SIGPWR);
        case_const(SIGBUS);
        case_const(SIGSYS);
        case_const(SIGURG);
        case_const(SIGSTOP);
        case_const(SIGTSTP);
        case_const(SIGCONT);
        case_const(SIGCHLD);
        case_const(SIGTTIN);
        case_const(SIGTTOU);
        case_const(SIGPOLL);
        case_const(SIGXFSZ);
        case_const(SIGXCPU);
        case_const(SIGVTALRM);
        case_const(SIGPROF);
        case_const(SIGUSR1);
        case_const(SIGUSR2);
        case_const(SIGWINCH);
        default: return "?";
    }
}

int sym(sigaction)(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sigaction] || !lib_flags[lib_idx]) return __real_sigaction(sig, act, oact);
    const char *sigstr = getsigstr(sig);
    char src_file[256];
    if (act != NULL) {
        char *name = "sa_handler";
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
        void *ptr = (void *)act->sa_handler;
        if (act->sa_flags & SA_SIGINFO) {
            name = "sa_sigaction";
            ptr = (void *)act->sa_sigaction;
        }
#pragma GCC diagnostic pop
        char flgstr[64] = "|";
        flag_str(act->sa_flags, SA_NOCLDSTOP, flgstr);
        flag_str(act->sa_flags, SA_NOCLDWAIT, flgstr);
        flag_str(act->sa_flags, SA_SIGINFO, flgstr);
        if (act->sa_flags & ~(SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO)) strcat(flgstr, "?|");
        char maskstr[512] = "";
        for (int i = 0; i < 64; i++) {
            if (sigismember(&act->sa_mask, i) != 1)
                continue;
            if (maskstr[0] != 0) strcat(maskstr, ",");
            sprintf(maskstr + strlen(maskstr), "%i:%s", i, getsigstr(i));
        }
        if (!verbosity) {
            msg("sigaction(%i:%s, %p:{}, %p)" ret_str, sig, sigstr, act, oact, ret_data);
        } else {
            msg("sigaction(%i:%s, %p:{sa_flags: 0x%x:%s, %s: %p, sa_mask: [%s]}, %p)" ret_str, sig, sigstr, act, act->sa_flags, flgstr, name, ptr, maskstr, oact, ret_data);
        }
    } else {
        msg("sigaction(%i:%s, %p:{}, %p)" ret_str, sig, sigstr, act, oact, ret_data);
    }
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO sigaction modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "sigaction");
        } else if (strncmp(msg_buf, "return ", 7) == 0) {
            // TODO sigaction return
            fprintf(stderr, "intercept: %s: return command not implemented\n", "sigaction");
        } else if_fail_int_errno(sigaction)
        else if_invalid(sigaction)
    }
    const int ret = __real_sigaction(sig, act, oact);
    if (oact != NULL) {
        char *name = "sa_handler";
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
        void *ptr = (void *)oact->sa_handler;
        if (oact->sa_flags & SA_SIGINFO) {
            name = "sa_sigaction";
            ptr = (void *)oact->sa_sigaction;
        }
#pragma GCC diagnostic pop
        char flgstr[64] = "|";
        flag_str(oact->sa_flags, SA_NOCLDSTOP, flgstr);
        flag_str(oact->sa_flags, SA_NOCLDWAIT, flgstr);
        flag_str(oact->sa_flags, SA_SIGINFO, flgstr);
        if (oact->sa_flags & ~(SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO)) strcat(flgstr, "?|");
        char maskstr[512] = "";
        for (int i = 0; i < 64; i++) {
            if (sigismember(&oact->sa_mask, i) != 1)
                continue;
            if (maskstr[0] != 0) strcat(maskstr, ",");
            sprintf(maskstr + strlen(maskstr), "%i:%s", i, getsigstr(i));
        }
        msg("return %i; errno %s; oact={sa_flags: 0x%x:%s, %s: %p, sa_mask: [%s]}", ret, strerrorname_np(errno), oact->sa_flags, flgstr, name, ptr, maskstr);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

int sym(sem_init)(sem_t *sem, int pshared, unsigned int value) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_init] || !lib_flags[lib_idx]) return __real_sem_init(sem, pshared, value);
    char src_file[256];
    msg("sem_init(%p, %i, %u)" ret_str, sem, pshared, value, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int_int(sem_init, sem_t *, sem, int, pshared, unsigned int, value)
        else if_return_int_errno(sem_init)
        else if_fail_int_errno(sem_init)
        else if_invalid(sem_init)
    }
    const int ret = __real_sem_init(sem, pshared, value);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

sem_t *sym(sem_open)(const char *name, int oflag, ...) {
    init();
    Dl_info info;
    char ostr[32] = "|";
    flag_str(oflag, O_CREAT, ostr);
    flag_str(oflag, O_EXCL, ostr);
    if (oflag & ~(O_CREAT | O_EXCL)) strcat(ostr, "?|");

    mode_t mode_arg = 0;
    unsigned int value = 0;
    if (oflag & O_CREAT) {
        va_list args;
        va_start(args, oflag);
        mode_arg = va_arg(args, mode_t);
        value = va_arg(args, unsigned int);
        va_end(args);
        if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_open] || !lib_flags[lib_idx]) return __real_sem_open(name, oflag, mode_arg, value);
        char src_file[256];
        msg("sem_open(%es, 0%o:%s, 0%03o, %u)" ret_str, name, oflag, ostr, mode_arg, value, ret_data);
    } else {
        if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_open] || !lib_flags[lib_idx]) return __real_sem_open(name, oflag);
        char src_file[256];
        msg("sem_open(%es, 0%o:|%s)" ret_str, name, oflag, ostr, ret_data);
    }

    char overwrite[BUFFER_SIZE];
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // modify <name>,<oflag>
            // modify <name>,<oflag>,<mode>,<value>
            char *val_1 = NULL, *end_ptr_1 = NULL, *end_ptr_2 = NULL, *end_ptr_3 = NULL, *end_ptr_4 = NULL;
            long val_2 = 0, val_3 = 0, val_4 = 0;
            if (msg_buf[7] == '"') {
                // modify "<name>",...
                val_1 = strtostr(msg_buf + 7, &end_ptr_1, overwrite);
                if (val_1 == NULL) end_ptr_1 = NULL;
            } else {
                // modify <name_ptr>,...
                val_1 = (char *)strtol(msg_buf + 7, &end_ptr_1, 0);
            }
            if (end_ptr_1 != NULL && end_ptr_1[0] != 0) {
                if (end_ptr_1[1] == '|') {
                    val_2 = 0;
                    end_ptr_2 = end_ptr_1 + 1;
                    if (strncmp(end_ptr_2, "O_CREAT|", 8) == 0) {
                        val_2 |= O_CREAT;
                        end_ptr_2 += 8;
                    }
                    if (strncmp(end_ptr_2, "O_EXCL|", 7) == 0) {
                        val_2 |= O_EXCL;
                        end_ptr_2 += 7;
                    }
                } else {
                    val_2 = strtol(end_ptr_1 + 1, &end_ptr_2, 0);
                }
            }
            if (end_ptr_2 != NULL && end_ptr_2[0] != 0) val_3 = strtol(end_ptr_2 + 1, &end_ptr_3, 0);
            if (end_ptr_3 != NULL && end_ptr_3[0] != 0) val_4 = strtol(end_ptr_3 + 1, &end_ptr_4, 0);

            if (end_ptr_1 != NULL && end_ptr_1[0] == ',' && end_ptr_1 != msg_buf + 7 &&
                end_ptr_2 != NULL && (end_ptr_2[0] == 0 || end_ptr_2[0] == ',') && end_ptr_2 != end_ptr_1 + 1 &&
                (end_ptr_2[0] == 0 || (end_ptr_3 != NULL && end_ptr_3[0] == ',' && end_ptr_3 != end_ptr_2 + 1 &&
                                       end_ptr_4 != NULL && end_ptr_4[0] == 0 && end_ptr_4 != end_ptr_3 + 1)))
            {
                name = val_1;
                oflag = (int)val_2;
                if (end_ptr_2[0] != 0) {
                    mode_arg = val_3;
                    value = val_4;
                }
            } else {
                fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", "sem_open", msg_buf + 7);
            }
        } else if_return_ptr_errno(sem_open)
        else if_fail_ptr_errno(sem_open)
        else if_invalid(sem_open)
    }

    sem_t *ret;
    if (oflag & O_CREAT) {
        ret = __real_sem_open(name, oflag, mode_arg, value);
    } else {
        ret = __real_sem_open(name, oflag);
    }
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_post)(sem_t *sem) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_post] || !lib_flags[lib_idx]) return __real_sem_post(sem);
    char src_file[256];
    msg("sem_post(%p)" ret_str, sem, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(sem_post, sem_t *, sem)
        else if_return_int_errno(sem_post)
        else if_fail_int_errno(sem_post)
        else if_invalid(sem_post)
    }
    const int ret = __real_sem_post(sem);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_wait)(sem_t *sem) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_wait] || !lib_flags[lib_idx]) return __real_sem_wait(sem);
    char src_file[256];
    msg("sem_wait(%p)" ret_str, sem, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(sem_wait, sem_t *, sem)
        else if_return_int_errno(sem_wait)
        else if_fail_int_errno(sem_wait)
        else if_invalid(sem_wait)
    }
    const int ret = __real_sem_wait(sem);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_trywait)(sem_t *sem) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_trywait] || !lib_flags[lib_idx]) return __real_sem_trywait(sem);
    char src_file[256];
    msg("sem_trywait(%p)" ret_str, sem, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(sem_trywait, sem_t *, sem)
        else if_return_int_errno(sem_trywait)
        else if_fail_int_errno(sem_trywait)
        else if_invalid(sem_trywait)
    }
    const int ret = __real_sem_trywait(sem);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_timedwait)(sem_t *restrict sem, const struct timespec *restrict abs_timeout) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_timedwait] || !lib_flags[lib_idx]) return __real_sem_timedwait(sem, abs_timeout);
    char src_file[256];
    msg("sem_timedwait(%p, %qt)" ret_str, sem, abs_timeout, ret_data);
    struct timespec overwrite;
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // modfiy <sem>,<abs_timeout_ptr>
            // modify <sem>,{<tv_sec>,<tv_nsec>}
            char *end_ptr_1 = NULL, *end_ptr_2 = NULL, *end_ptr_3 = NULL;
            long val_1 = strtol(msg_buf + 7, &end_ptr_1, 0);
            struct timespec *val_2 = 0;
            if (end_ptr_1 != NULL && end_ptr_1[0] != 0) {
                if (end_ptr_1[1] == '{') {
                    long sec = 0, nsec = 0;
                    sec = strtol(end_ptr_1 + 2, &end_ptr_2, 0);
                    if (end_ptr_2 != NULL && end_ptr_2[0] == ',') nsec = strtol(end_ptr_2 + 1, &end_ptr_3, 0);
                    overwrite.tv_sec = sec;
                    overwrite.tv_nsec = nsec;
                    if (end_ptr_3 != NULL && end_ptr_3[0] == '}' && end_ptr_3[1] == 0) {
                        val_2 = &overwrite;
                        end_ptr_2 = end_ptr_3 + 1;
                    }
                } else {
                    val_2 = (void *)strtol(end_ptr_1 + 1, &end_ptr_2, 0);
                }
            }
            if (end_ptr_1 != NULL && end_ptr_1[0] == ',' && end_ptr_1 != msg_buf + 7 &&
                end_ptr_2 != NULL && end_ptr_2[0] == 0 && end_ptr_2 != end_ptr_1 + 1)
            {
                sem = (sem_t *)val_1;
                abs_timeout = val_2;
            } else {
                fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", "sem_timedwait", msg_buf + 7);
            }
        } else if_return_int_errno(sem_timedwait)
        else if_fail_int_errno(sem_timedwait)
        else if_invalid(sem_timedwait)
    }
    const int ret = __real_sem_timedwait(sem, abs_timeout);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_getvalue)(sem_t *restrict sem, int *restrict value) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_getvalue] || !lib_flags[lib_idx]) return __real_sem_getvalue(sem, value);
    char src_file[256];
    msg("sem_getvalue(%p, %p)" ret_str, sem, value, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(sem_getvalue, sem_t *, sem, int *, value)
        else if_return_int_errno_int(sem_getvalue, int, *value, "value")
        else if_fail_int_errno(sem_getvalue)
        else if_invalid(sem_getvalue)
    }
    const int ret = __real_sem_getvalue(sem, value);
    if (value != NULL) {
        msg("return %i; errno %s; value=%i", ret, strerrorname_np(errno), *value);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

int sym(sem_close)(sem_t *sem) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_close] || !lib_flags[lib_idx]) return __real_sem_close(sem);
    char src_file[256];
    msg("sem_close(%p)" ret_str, sem, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(sem_close, sem_t *, sem)
        else if_return_int_errno(sem_close)
        else if_fail_int_errno(sem_close)
        else if_invalid(sem_close)
    }
    const int ret = __real_sem_close(sem);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_unlink)(const char *name) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_unlink] || !lib_flags[lib_idx]) return __real_sem_unlink(name);
    char src_file[256];
    msg("sem_unlink(%es)" ret_str, name, ret_data);
    char overwrite[BUFFER_SIZE];
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            char *end_ptr = NULL, *val = NULL;
            if (msg_buf[7] == '"') {
                // modify "<name>"
                val = strtostr(msg_buf + 7, &end_ptr, overwrite);
                if (val == NULL) end_ptr = NULL;
            } else {
                // modify <name_ptr>
                val = (char *)strtol(msg_buf + 7, &end_ptr, 0);
            }
            if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) {
                name = val;
            } else {
                fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", "sem_unlink", msg_buf + 7);
            }
        } else if_return_int_errno(sem_unlink)
        else if_fail_int_errno(sem_unlink)
        else if_invalid(sem_unlink)
    }
    const int ret = __real_sem_unlink(name);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(sem_destroy)(sem_t *sem) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sem_destroy] || !lib_flags[lib_idx]) return __real_sem_destroy(sem);
    char src_file[256];
    msg("sem_destroy(%p)" ret_str, sem, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(sem_destroy, sem_t *, sem)
        else if_return_int_errno(sem_destroy)
        else if_fail_int_errno(sem_destroy)
        else if_invalid(sem_destroy)
    }
    const int ret = __real_sem_destroy(sem);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(shm_open)(const char *name, int oflag, mode_t mode_arg) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_shm_open] || !lib_flags[lib_idx]) return __real_shm_open(name, oflag, mode);
    char ostr[64] = "|";
    flag_str(oflag, O_RDONLY, ostr);
    flag_str(oflag, O_RDWR, ostr);
    flag_str(oflag, O_CREAT, ostr);
    flag_str(oflag, O_EXCL, ostr);
    flag_str(oflag, O_TRUNC, ostr);
    if (oflag & ~(O_RDONLY | O_RDWR | O_CREAT | O_EXCL | O_TRUNC)) strcat(ostr, "?|");

    char src_file[256];
    msg("shm_open(%es, 0%o:%s, 0%03o)" ret_str, name, oflag, ostr, mode_arg, ret_data);
    char overwrite[BUFFER_SIZE];
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // modify <name>,<oflag>,<mode>
            char *val_1 = NULL, *end_ptr_1 = NULL, *end_ptr_2 = NULL, *end_ptr_3 = NULL;
            long val_2 = 0, val_3 = 0;
            if (msg_buf[7] == '"') {
                // modify "<name>",...
                val_1 = strtostr(msg_buf + 7, &end_ptr_1, overwrite);
                if (val_1 == NULL) end_ptr_1 = NULL;
            } else {
                // modify <name_ptr>,...
                val_1 = (char *)strtol(msg_buf + 7, &end_ptr_1, 0);
            }
            if (end_ptr_1 != NULL && end_ptr_1[0] != 0) {
                if (end_ptr_1[1] == '|') {
                    val_2 = 0;
                    end_ptr_2 = end_ptr_1 + 1;
                    if (strncmp(end_ptr_2, "O_RDONLY|", 8) == 0) {
                        val_2 |= O_RDONLY;
                        end_ptr_2 += 9;
                    }
                    if (strncmp(end_ptr_2, "O_RDWR|", 8) == 0) {
                        val_2 |= O_RDWR;
                        end_ptr_2 += 7;
                    }
                    if (strncmp(end_ptr_2, "O_CREAT|", 8) == 0) {
                        val_2 |= O_CREAT;
                        end_ptr_2 += 8;
                    }
                    if (strncmp(end_ptr_2, "O_EXCL|", 7) == 0) {
                        val_2 |= O_EXCL;
                        end_ptr_2 += 7;
                    }
                    if (strncmp(end_ptr_2, "O_TRUNC|", 7) == 0) {
                        val_2 |= O_TRUNC;
                        end_ptr_2 += 8;
                    }
                } else {
                    val_2 = strtol(end_ptr_1 + 1, &end_ptr_2, 0);
                }
            }
            if (end_ptr_2 != NULL && end_ptr_2[0] != 0) val_3 = strtol(end_ptr_2 + 1, &end_ptr_3, 0);

            if (end_ptr_1 != NULL && end_ptr_1[0] == ',' && end_ptr_1 != msg_buf + 7 &&
                end_ptr_2 != NULL && end_ptr_2[0] == ',' && end_ptr_2 != end_ptr_1 + 1 &&
                end_ptr_3 != NULL && end_ptr_3[0] == 0 && end_ptr_3 != end_ptr_2 + 1)
            {
                name = val_1;
                oflag = (int)val_2;
                mode_arg = val_3;
            } else {
                fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", "shm_open", msg_buf + 7);
            }
        } else if_return_int_errno(shm_open)
        else if_fail_int_errno(shm_open)
        else if_invalid(shm_open)
    }
    const int ret = __real_shm_open(name, oflag, mode);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(shm_unlink)(const char *name) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_shm_unlink] || !lib_flags[lib_idx]) return __real_shm_unlink(name);
    char src_file[256];
    msg("shm_unlink(%es)" ret_str, name, ret_data);
    char overwrite[BUFFER_SIZE];
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            char *end_ptr = NULL, *val = NULL;
            if (msg_buf[7] == '"') {
                // modify "<name>"
                val = strtostr(msg_buf + 7, &end_ptr, overwrite);
                if (val == NULL) end_ptr = NULL;
            } else {
                // modify <name_ptr>
                val = (char *)strtol(msg_buf + 7, &end_ptr, 0);
            }
            if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) {
                name = val;
            } else {
                fprintf(stderr, "intercept: %s: invalid args in modify command: '%s'\n", "shm_unlink", msg_buf + 7);
            }
        } else if_return_int_errno(shm_unlink)
        else if_fail_int_errno(shm_unlink)
        else if_invalid(shm_unlink)
    }
    const int ret = __real_shm_unlink(name);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

void *sym(mmap)(void *addr, size_t len, int prot, int flags, int fildes, off_t off) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_mmap] || !lib_flags[lib_idx]) return __real_mmap(addr, len, prot, flags, fildes, off);
    char pstr[64] = "|";
    if (prot == PROT_NONE) strcat(pstr, "PROT_NONE|");
    flag_str(prot, PROT_READ, pstr);
    flag_str(prot, PROT_WRITE, pstr);
    flag_str(prot, PROT_EXEC, pstr);
    if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) strcat(pstr, "?|");
    char fstr[64] = "|";
    flag_str(prot, MAP_SHARED, fstr);
    flag_str(prot, MAP_PRIVATE, fstr);
    flag_str(prot, MAP_FIXED, fstr);
    flag_str(prot, MAP_ANONYMOUS, fstr);
    if (prot & ~(MAP_SHARED | MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS)) strcat(fstr, "?|");

    char src_file[256];
    msg("mmap(%p, %i, 0x%x:%s, %0x%x:%s, %i, %i)" ret_str, addr, len, prot, pstr, flags, fstr, fildes, off, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO mmap modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "mmap");
        } else if_return_ptr_errno(mmap)
        else if_fail {
            set_errno("mmap", msg_buf);
            return MAP_FAILED;
        } else if_invalid(mmap)
    }
    void *ret = __real_mmap(addr, len, prot, flags, fildes, off);
    msg("return %p; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(munmap)(void *addr, size_t len) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_munmap] || !lib_flags[lib_idx]) return __real_munmap(addr, len);
    char src_file[256];
    msg("munmap(%p, %i)" ret_str, addr, len, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(munmap, void *, addr, size_t, len)
        else if_return_int_errno(munmap)
        else if_fail_int_errno(munmap)
        else if_invalid(munmap)
    }
    const int ret = __real_munmap(addr, len);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(ftruncate)(int fildes, off_t length) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_ftruncate] || !lib_flags[lib_idx]) return __real_ftruncate(fildes, length);
    char src_file[256];
    msg("ftruncate(%i, %i)" ret_str, fildes, length, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(ftruncate, int, fildes, off_t, length)
        else if_return_int_errno(ftruncate)
        else if_fail_int_errno(ftruncate)
        else if_invalid(ftruncate)
    }
    const int ret = __real_ftruncate(fildes, length);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

pid_t sym(fork)(void) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_fork] || !lib_flags[lib_idx]) return __real_fork();
    char src_file[256];
    msg("fork()" ret_str, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_return_int_errno(fork)
        else if_fail_int_errno(fork)
        else if_invalid(fork)
    }
    const pid_t ret = __real_fork();
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

pid_t sym(wait)(int *stat_loc) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_wait] || !lib_flags[lib_idx]) return __real_wait(stat_loc);
    char src_file[256];
    msg("wait(%p)" ret_str, stat_loc, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(wait, int *, stat_loc)
        else if_return_int_errno_int(wait, int, *stat_loc, "stat_loc")
        else if_fail_int_errno(wait)
        else if_invalid(wait)
    }
    const pid_t ret = __real_wait(stat_loc);
    if (stat_loc != NULL) {
        msg("return %i; errno %s; stat=%i", ret, strerrorname_np(errno), *stat_loc);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

pid_t sym(waitpid)(pid_t pid, int *stat_loc, int options) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_waitpid] || !lib_flags[lib_idx]) return __real_waitpid(pid, stat_loc, options);
    char ostr[64] = "|";
    flag_str(options, WCONTINUED, ostr);
    flag_str(options, WNOHANG, ostr);
    flag_str(options, WUNTRACED, ostr);
    if (options & ~(WCONTINUED | WNOHANG | WUNTRACED)) strcat(ostr, "?|");

    char src_file[256];
    msg("waitpid(%i, %p, 0x%x:%s)" ret_str, pid, stat_loc, options, ostr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        // TODO waitpid modify options
        if_modify_int_int_int(waitpid, pid_t, pid, int *, stat_loc, int, options)
        else if_return_int_errno_int(waitpid, int, *stat_loc, "stat_loc")
        else if_fail_int_errno(waitpid)
        else if_invalid(waitpid)
    }
    const pid_t ret = __real_waitpid(pid, stat_loc, options);
    if (stat_loc != NULL) {
        msg("return %i; errno %s; stat=%i", ret, strerrorname_np(errno), *stat_loc);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

int sym(execl)(const char *pathname, const char *arg, ...) {
    init();
    Dl_info info;
    va_list args;
    va_start(args, arg);
    const char *arg_vec[64];
    arg_vec[0] = arg;
    for (int i = 1; i < 64 && arg_vec[i - 1] != NULL; i++) {
        arg_vec[i] = va_arg(args, const char *);
    }
    va_end(args);
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execl] || !lib_flags[lib_idx]) return __real_execv(pathname, (char *const *)arg_vec);
    char arg_buf[4096] = "", *arg_buf_ptr = arg_buf;
    for (int i = 1; i < 64; i++) {
        arg_buf_ptr[0] = ',';
        arg_buf_ptr[1] = ' ';
        arg_buf_ptr[2] = 0;
        arg_buf_ptr += 2;
        if (arg_vec[i] == NULL) {
            strcat(arg_buf_ptr, "NULL");
            break;
        }
        arg_buf_ptr += msg_str(arg_buf_ptr, sizeof(arg_buf) - (arg_buf_ptr - arg_buf), arg_vec[i], 0);
    }

    char src_file[256];
    msg("execl(%es, %es%s)" ret_str, pathname, arg, arg_buf, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execl modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execl");
        } else if_return_int_errno(execl)
        else if_fail_int_errno(execl)
        else if_invalid(execl)
    }
    const int ret = __real_execv(pathname, (char *const *)arg_vec);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(execlp)(const char *file, const char *arg, ...) {
    init();
    Dl_info info;
    va_list args;
    va_start(args, arg);
    const char *arg_vec[64];
    arg_vec[0] = arg;
    for (int i = 1; i < 64 && arg_vec[i - 1] != NULL; i++) {
        arg_vec[i] = va_arg(args, const char *);
    }
    va_end(args);
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execlp] || !lib_flags[lib_idx]) return __real_execvp(file, (char *const *)arg_vec);
    char arg_buf[4096] = "", *arg_buf_ptr = arg_buf;
    for (int i = 1; i < 64; i++) {
        arg_buf_ptr[0] = ',';
        arg_buf_ptr[1] = ' ';
        arg_buf_ptr[2] = 0;
        arg_buf_ptr += 2;
        if (arg_vec[i] == NULL) {
            strcat(arg_buf_ptr, "NULL");
            break;
        }
        arg_buf_ptr += msg_str(arg_buf_ptr, sizeof(arg_buf) - (arg_buf_ptr - arg_buf), arg_vec[i], 0);
    }

    char src_file[256];
    msg("execlp(%es, %es%s)" ret_str, file, arg, arg_buf, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execlp modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execlp");
        } else if_return_int_errno(execlp)
        else if_fail_int_errno(execlp)
        else if_invalid(execlp)
    }
    const int ret = __real_execvp(file, (char *const *)arg_vec);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(execle)(const char *pathname, const char *arg, ...) {
    init();
    Dl_info info;
    va_list args;
    va_start(args, arg);
    const char *arg_vec[64];
    char *const *envp = NULL;
    arg_vec[0] = arg;
    for (int i = 1; i < 64; i++) {
        arg_vec[i] = va_arg(args, const char *);
        if (arg_vec[i] == NULL) {
            envp = va_arg(args, char *const *);
            break;
        }
    }
    va_end(args);
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execle] || !lib_flags[lib_idx]) return __real_execvpe(pathname, (char *const *)arg_vec, envp);
    char arg_buf[4096] = "", *arg_buf_ptr = arg_buf;
    for (int i = 1; i < 64; i++) {
        arg_buf_ptr[0] = ',';
        arg_buf_ptr[1] = ' ';
        arg_buf_ptr[2] = 0;
        arg_buf_ptr += 2;
        if (arg_vec[i] == NULL) {
            strcat(arg_buf_ptr, "NULL");
            break;
        }
        arg_buf_ptr += msg_str(arg_buf_ptr, sizeof(arg_buf) - (arg_buf_ptr - arg_buf), arg_vec[i], 0);
    }

    int len = 0; for (len = 0; envp[len] != NULL; len++) {}
    char src_file[256];
    msg("execle(%es, %es%s, %as)" ret_str, pathname, arg, arg_buf, len, envp, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execle modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execle");
        } else if_return_int_errno(execle)
        else if_fail_int_errno(execle)
        else if_invalid(execle)
    }
    const int ret = __real_execvpe(pathname, (char *const *)arg_vec, envp);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(execv)(const char *pathname, char *const argv[]) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execv] || !lib_flags[lib_idx]) return __real_execv(pathname, argv);
    int len = 0; for (len = 0; argv[len] != NULL; len++) {}
    char src_file[256];
    msg("execv(%es, %as)" ret_str, pathname, len, argv, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execv modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execv");
        } else if_return_int_errno(execv)
        else if_fail_int_errno(execv)
        else if_invalid(execv)
    }
    const int ret = __real_execv(pathname, argv);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(execvp)(const char *file, char *const argv[]) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execvp] || !lib_flags[lib_idx]) return __real_execvp(file, argv);
    int len = 0; for (len = 0; argv[len] != NULL; len++) {}
    char src_file[256];
    msg("execvp(%es, %as)" ret_str, file, len, argv, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execvp modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execvp");
        } else if_return_int_errno(execvp)
        else if_fail_int_errno(execvp)
        else if_invalid(execvp)
    }
    const int ret = __real_execvp(file, argv);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(execvpe)(const char *file, char *const argv[], char *const envp[]) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execvpe] || !lib_flags[lib_idx]) return __real_execvpe(file, argv, envp);
    int len1 = 0; for (len1 = 0; argv[len1] != NULL; len1++) {}
    int len2 = 0; for (len2 = 0; envp[len2] != NULL; len2++) {}
    char src_file[256];
    msg("execvpe(%es, %as, %as)" ret_str, file, len1, argv, len2, envp, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execvpe modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execvpe");
        } else if_return_int_errno(execvpe)
        else if_fail_int_errno(execvpe)
        else if_invalid(execvpe)
    }
    const int ret = __real_execvpe(file, argv, envp);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(execve)(const char *pathname, char *const argv[], char *const envp[]) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_execve] || !lib_flags[lib_idx]) return __real_execve(pathname, argv, envp);
    int len1 = 0; for (len1 = 0; argv[len1] != NULL; len1++) {}
    int len2 = 0; for (len2 = 0; envp[len2] != NULL; len2++) {}
    char src_file[256];
    msg("execve(%es, %as, %as)" ret_str, pathname, len1, argv, len2, envp, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO execve modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "execve");
        } else if_return_int_errno(execve)
        else if_fail_int_errno(execve)
        else if_invalid(execve)
    }
    const int ret = __real_execve(pathname, argv, envp);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(fexecve)(int fd, char *const argv[], char *const envp[]) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_fexecve] || !lib_flags[lib_idx]) return __real_fexecve(fd, argv, envp);
    int len1 = 0; for (len1 = 0; argv[len1] != NULL; len1++) {}
    int len2 = 0; for (len2 = 0; envp[len2] != NULL; len2++) {}
    char src_file[256];
    msg("fexecve(%i, %as, %as)" ret_str, fd, len1, argv, len2, envp, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO fexecve modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "fexecve");
        } else if_return_int_errno(fexecve)
        else if_fail_int_errno(fexecve)
        else if_invalid(fexecve)
    }
    const int ret = __real_fexecve(fd, argv, envp);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(pipe)(int fildes[2]) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_pipe] || !lib_flags[lib_idx]) return __real_pipe(fildes);
    char src_file[256];
    msg("pipe(%p)" ret_str, fildes, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO pipe modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "pipe");
        } else if_return_int_errno(pipe)
        else if_fail_int_errno(pipe)
        else if_invalid(pipe)
    }
    const int ret = __real_pipe(fildes);
    msg("return %i; errno %s; fildes=[%i,%i]", ret, strerrorname_np(errno), fildes[0], fildes[1]);
    return ret;
}

int sym(dup)(int oldfd) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_dup] || !lib_flags[lib_idx]) return __real_dup(oldfd);
    char src_file[256];
    msg("dup(%i)" ret_str, oldfd, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(dup, int, oldfd)
        else if_return_int_errno(dup)
        else if_fail_int_errno(dup)
        else if_invalid(dup)
    }
    const int ret = __real_dup(oldfd);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(dup2)(int oldfd, int newfd) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_dup2] || !lib_flags[lib_idx]) return __real_dup2(oldfd, newfd);
    char src_file[256];
    msg("dup2(%i, %i)" ret_str, oldfd, newfd, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(dup2, int, oldfd, int, newfd)
        else if_return_int_errno(dup2)
        else if_fail_int_errno(dup2)
        else if_invalid(dup2)
    }
    const int ret = __real_dup2(oldfd, newfd);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(dup3)(int oldfd, int newfd, int flags) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_dup3] || !lib_flags[lib_idx]) return __real_dup3(oldfd, newfd, flags);
    char fstr[16] = "|";
    flag_str(flags, O_CLOEXEC, fstr);
    if (flags & ~(O_CLOEXEC)) strcat(fstr, "?|");
    char src_file[256];
    msg("dup3(%i, %i, 0%o:%s)" ret_str, oldfd, newfd, flags, fstr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int_int(dup3, int, oldfd, int, newfd, int, flags)
        else if_return_int_errno(dup3)
        else if_fail_int_errno(dup3)
        else if_invalid(dup3)
    }
    const int ret = __real_dup3(oldfd, newfd, flags);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(socket)(int domain, int type, int protocol) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_socket] || !lib_flags[lib_idx]) return __real_socket(domain, type, protocol);
    char src_file[256];
    msg("socket(%i:%s, %i:%s, %i)" ret_str, domain, getdomainstr(domain), type, getsocktype(type), protocol, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO socket modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "socket");
        } else if_return_int_errno(socket)
        else if_fail_int_errno(socket)
        else if_invalid(socket)
    }
    const int ret = __real_socket(domain, type, protocol);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(bind)(int sockfd, const struct sockaddr *address, socklen_t address_len) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_bind] || !lib_flags[lib_idx]) return __real_bind(sockfd, address, address_len);
    char src_file[256];
    msg("bind(%i, %qa, %i)" ret_str, sockfd, address_len, address, address_len, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO bind modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "bind");
        } else if_return_int_errno(bind)
        else if_fail_int_errno(bind)
        else if_invalid(bind)
    }
    const int ret = __real_bind(sockfd, address, address_len);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(listen)(int sockfd, int backlog) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_listen] || !lib_flags[lib_idx]) return __real_listen(sockfd, backlog);
    char src_file[256];
    msg("listen(%i, %i)" ret_str, sockfd, backlog, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int_int(listen, int, sockfd, int, backlog)
        else if_return_int_errno(listen)
        else if_fail_int_errno(listen)
        else if_invalid(listen)
    }
    const int ret = __real_listen(sockfd, backlog);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(accept)(int sockfd, struct sockaddr *restrict address, socklen_t *restrict address_len) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_accept] || !lib_flags[lib_idx]) return __real_accept(sockfd, address, address_len);
    char src_file[256];
    msg("accept(%i, %p, %p)" ret_str, sockfd, address, address_len, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO accept modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "accept");
        } else if_return_int_errno(accept)
        else if_fail_int_errno(accept)
        else if_invalid(accept)
    }
    const int ret = __real_accept(sockfd, address, address_len);
    if (ret >= 0) {
        if (address != NULL && address_len != NULL) {
            msg("return %i; errno %s; address=%qa; address_len=%i", ret, strerrorname_np(errno), *address_len, address, *address_len);
        } else {
            msg("return %i; errno %s", ret, strerrorname_np(errno));
        }
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

int sym(connect)(int sockfd, const struct sockaddr *address, socklen_t address_len) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_connect] || !lib_flags[lib_idx]) return __real_connect(sockfd, address, address_len);
    char src_file[256];
    msg("connect(%i, %qa, %i)" ret_str, sockfd, address_len, address, address_len, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO connect modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "connect");
        } else if_return_int_errno(connect)
        else if_fail_int_errno(connect)
        else if_invalid(connect)
    }
    const int ret = __real_connect(sockfd, address, address_len);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

int sym(getaddrinfo)(const char *restrict node, const char *restrict service, const struct addrinfo *restrict hints, struct addrinfo **restrict res) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_getaddrinfo] || !lib_flags[lib_idx]) return __real_getaddrinfo(node, service, hints, res);
    char src_file[256];
    msg("getaddrinfo(%es, %es, %qi, %p)" ret_str, node, service, hints, res, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO getaddrinfo modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "getaddrinfo");
        } else if (strncmp(msg_buf, "return ", 7) == 0) {
            char *end_ptr = NULL;
            long val = strtol(msg_buf + 7, &end_ptr, 0);
            if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != msg_buf + 7) {
                msg("return %i:%s; errno %s", val, getaddrinfoerror(val), strerrorname_np(errno));
                return val;
            } else {
                fprintf(stderr, "intercept: %s: invalid args in return command: '%s'\n", "getaddrinfo", msg_buf + 7);
            }
        } else if_fail {
            int ret = -1;
            if (strcmp(msg_buf + 5, "EAI_ADDRFAMILY") == 0) {
                ret = EAI_ADDRFAMILY;
            } else if (strcmp(msg_buf + 5, "EAI_AGAIN") == 0) {
                ret = EAI_AGAIN;
            } else if (strcmp(msg_buf + 5, "EAI_BADFLAGS") == 0) {
                ret = EAI_BADFLAGS;
            } else if (strcmp(msg_buf + 5, "EAI_FAIL") == 0) {
                ret = EAI_FAIL;
            } else if (strcmp(msg_buf + 5, "EAI_FAMILY") == 0) {
                ret = EAI_FAMILY;
            } else if (strcmp(msg_buf + 5, "EAI_MEMORY") == 0) {
                ret = EAI_MEMORY;
            } else if (strcmp(msg_buf + 5, "EAI_NODATA") == 0) {
                ret = EAI_NODATA;
            } else if (strcmp(msg_buf + 5, "EAI_NONAME") == 0) {
                ret = EAI_NONAME;
            } else if (strcmp(msg_buf + 5, "EAI_SERVICE") == 0) {
                ret = EAI_SERVICE;
            } else if (strcmp(msg_buf + 5, "EAI_SOCKTYPE") == 0) {
                ret = EAI_SOCKTYPE;
            } else if (strcmp(msg_buf + 5, "EAI_SYSTEM") == 0) {
                ret = EAI_SYSTEM;
            } else {
                ret = EAI_SYSTEM;
                set_errno("getaddrinfo", msg_buf);
            }
            msg("return %i:%s; errno %s", ret, getaddrinfoerror(ret), strerrorname_np(errno));
            return ret;
        } else if_invalid(getaddrinfo)
    }
    const int ret = __real_getaddrinfo(node, service, hints, res);
    if (res != NULL) {
        msg("return %i:%s; errno %s; res=%qi", ret, getaddrinfoerror(ret), strerrorname_np(errno), *res);
    } else {
        msg("return %i:%s; errno %s", ret, getaddrinfoerror(ret), strerrorname_np(errno));
    }
    return ret;
}

void sym(freeaddrinfo)(struct addrinfo *res) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_freeaddrinfo] || !lib_flags[lib_idx]) { __real_freeaddrinfo(res); return; }
    char src_file[256];
    msg("freeaddrinfo(%p)" ret_str, res, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if_modify_int(freeaddrinfo, struct addrinfo *, res)
        else if_return
        else if_invalid(freeaddrinfo)
    }
    __real_freeaddrinfo(res);
    msg("return");
}

ssize_t sym(send)(int sockfd, const void *buf, size_t length, int flags) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_send] || !lib_flags[lib_idx]) return __real_send(sockfd, buf, length, flags);
    char fstr[256] = "|";
    flag_str(flags, MSG_CONFIRM, fstr);
    flag_str(flags, MSG_DONTROUTE, fstr);
    flag_str(flags, MSG_DONTWAIT, fstr);
    flag_str(flags, MSG_EOR, fstr);
    flag_str(flags, MSG_MORE, fstr);
    flag_str(flags, MSG_NOSIGNAL, fstr);
    flag_str(flags, MSG_OOB, fstr);
    flag_str(flags, MSG_FASTOPEN, fstr);
    if (flags & ~(MSG_CONFIRM | MSG_DONTROUTE | MSG_DONTWAIT | MSG_EOR | MSG_MORE | MSG_NOSIGNAL | MSG_OOB | MSG_FASTOPEN)) strcat(fstr, "?|");

    char src_file[256];
    msg("send(%i, %eb, %i, 0x%x:%s)" ret_str, sockfd, length, buf, length, flags, fstr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO send modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "send");
        } else if_return_int_errno(send)
        else if_fail_int_errno(send)
        else if_invalid(send)
    }
    const ssize_t ret = __real_send(sockfd, buf, length, flags);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

ssize_t sym(sendto)(int sockfd, const void *buf, size_t size, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sendto] || !lib_flags[lib_idx]) return __real_sendto(sockfd, buf, size, flags, dest_addr, addrlen);
    char fstr[256] = "|";
    flag_str(flags, MSG_CONFIRM, fstr);
    flag_str(flags, MSG_DONTROUTE, fstr);
    flag_str(flags, MSG_DONTWAIT, fstr);
    flag_str(flags, MSG_EOR, fstr);
    flag_str(flags, MSG_MORE, fstr);
    flag_str(flags, MSG_NOSIGNAL, fstr);
    flag_str(flags, MSG_OOB, fstr);
    flag_str(flags, MSG_FASTOPEN, fstr);
    if (flags & ~(MSG_CONFIRM | MSG_DONTROUTE | MSG_DONTWAIT | MSG_EOR | MSG_MORE | MSG_NOSIGNAL | MSG_OOB | MSG_FASTOPEN)) strcat(fstr, "?|");

    char src_file[256];
    msg("sendto(%i, %eb, %i, 0x%x:%s, %qa, %i)" ret_str, sockfd, size, buf, size, flags, fstr, addrlen, dest_addr, addrlen, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO sendto modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "sendto");
        } else if_return_int_errno(sendto)
        else if_fail_int_errno(sendto)
        else if_invalid(sendto)
    }
    const ssize_t ret = __real_sendto(sockfd, buf, size, flags, dest_addr, addrlen);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

ssize_t sym(sendmsg)(int sockfd, const struct msghdr *message, int flags) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_sendmsg] || !lib_flags[lib_idx]) return __real_sendmsg(sockfd, message, flags);
    char fstr[256] = "|";
    flag_str(flags, MSG_CONFIRM, fstr);
    flag_str(flags, MSG_DONTROUTE, fstr);
    flag_str(flags, MSG_DONTWAIT, fstr);
    flag_str(flags, MSG_EOR, fstr);
    flag_str(flags, MSG_MORE, fstr);
    flag_str(flags, MSG_NOSIGNAL, fstr);
    flag_str(flags, MSG_OOB, fstr);
    flag_str(flags, MSG_FASTOPEN, fstr);
    if (flags & ~(MSG_CONFIRM | MSG_DONTROUTE | MSG_DONTWAIT | MSG_EOR | MSG_MORE | MSG_NOSIGNAL | MSG_OOB | MSG_FASTOPEN)) strcat(fstr, "?|");

    char src_file[256];
    msg("sendmsg(%i, %qm, 0x%x:%s)" ret_str, sockfd, message, flags, fstr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO sendmsg modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "sendmsg");
        } else if_return_int_errno(sendmsg)
        else if_fail_int_errno(sendmsg)
        else if_invalid(sendmsg)
    }
    const ssize_t ret = __real_sendmsg(sockfd, message, flags);
    msg("return %i; errno %s", ret, strerrorname_np(errno));
    return ret;
}

ssize_t sym(recv)(int sockfd, void *buf, size_t size, int flags) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_recv] || !lib_flags[lib_idx]) return __real_recv(sockfd, buf, size, flags);
    char fstr[256] = "|";
    flag_str(flags, MSG_DONTWAIT, fstr);
    flag_str(flags, MSG_ERRQUEUE, fstr);
    flag_str(flags, MSG_OOB, fstr);
    flag_str(flags, MSG_PEEK, fstr);
    flag_str(flags, MSG_TRUNC, fstr);
    flag_str(flags, MSG_WAITALL, fstr);
    if (flags & ~(MSG_DONTWAIT | MSG_ERRQUEUE | MSG_OOB | MSG_PEEK | MSG_TRUNC | MSG_WAITALL)) strcat(fstr, "?|");

    char src_file[256];
    msg("recv(%i, %p, %i, 0x%x:%s)" ret_str, sockfd, buf, size, flags, fstr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO recv modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "recv");
        } else if_return_int_errno(recv)
        else if_fail_int_errno(recv)
        else if_invalid(recv)
    }
    const ssize_t ret = __real_recv(sockfd, buf, size, flags);
    if (ret >= 0) {
        msg("return %i; errno %s; buf=%eb", ret, strerrorname_np(errno), ret, buf);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

ssize_t sym(recvfrom)(int sockfd, void *restrict buf, size_t size, int flags, struct sockaddr *restrict src_addr, socklen_t *restrict addrlen) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_recvfrom] || !lib_flags[lib_idx]) return __real_recvfrom(sockfd, buf, size, flags, src_addr, addrlen);
    char fstr[256] = "|";
    flag_str(flags, MSG_DONTWAIT, fstr);
    flag_str(flags, MSG_ERRQUEUE, fstr);
    flag_str(flags, MSG_OOB, fstr);
    flag_str(flags, MSG_PEEK, fstr);
    flag_str(flags, MSG_TRUNC, fstr);
    flag_str(flags, MSG_WAITALL, fstr);
    if (flags & ~(MSG_DONTWAIT | MSG_ERRQUEUE | MSG_OOB | MSG_PEEK | MSG_TRUNC | MSG_WAITALL)) strcat(fstr, "?|");

    char src_file[256];
    msg("recvfrom(%i, %p, %i, 0x%x:%s, %p, %p)" ret_str, sockfd, buf, size, flags, fstr, addrlen, src_addr, addrlen, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO recvfrom modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "recvfrom");
        } else if_return_int_errno(recvfrom)
        else if_fail_int_errno(recvfrom)
        else if_invalid(recvfrom)
    }
    const ssize_t ret = __real_recvfrom(sockfd, buf, size, flags, src_addr, addrlen);
    if (ret >= 0) {
        if (src_addr != NULL && addrlen != NULL) {
            msg("return %i; errno %s; buf=%eb; src_addr=%qa; addrlen=%i", ret, strerrorname_np(errno), ret, buf, *addrlen, src_addr, *addrlen);
        } else {
            msg("return %i; errno %s; buf=%eb", ret, strerrorname_np(errno), ret, buf);
        }
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

ssize_t sym(recvmsg)(int sockfd, struct msghdr *message, int flags) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_recvmsg] || !lib_flags[lib_idx]) return __real_recvmsg(sockfd, message, flags);
    char fstr[256] = "|";
    flag_str(flags, MSG_CMSG_CLOEXEC, fstr);
    flag_str(flags, MSG_DONTWAIT, fstr);
    flag_str(flags, MSG_ERRQUEUE, fstr);
    flag_str(flags, MSG_OOB, fstr);
    flag_str(flags, MSG_PEEK, fstr);
    flag_str(flags, MSG_TRUNC, fstr);
    flag_str(flags, MSG_WAITALL, fstr);
    if (flags & ~(MSG_CMSG_CLOEXEC | MSG_DONTWAIT | MSG_ERRQUEUE | MSG_OOB | MSG_PEEK | MSG_TRUNC | MSG_WAITALL)) strcat(fstr, "?|");

    char src_file[256];
    msg("recvmsg(%i, %qm, 0x%x:%s)" ret_str, sockfd, msg, flags, fstr, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO recvmsg modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "recvmsg");
        } else if_return_int_errno(recvmsg)
        else if_fail_int_errno(recvmsg)
        else if_invalid(recvmsg)
    }
    const ssize_t ret = __real_recvmsg(sockfd, message, flags);
    if (ret >= 0) {
        msg("return %i; errno %s; message=%eb", ret, strerrorname_np(errno), sizeof(*message), message);
    } else {
        msg("return %i; errno %s", ret, strerrorname_np(errno));
    }
    return ret;
}

ssize_t sym(getline)(char **restrict lineptr, size_t *restrict n, FILE *restrict stream) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_getline] || !lib_flags[lib_idx]) return __real_getline(lineptr, n, stream);
    char src_file[256];
    msg("getline(%p:%p, %p:%li, %p)" ret_str, lineptr, lineptr != NULL ? *lineptr : NULL, n, n != NULL ? *n : 0, stream, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO getline modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "getline");
        } else if_return_int_errno(getline)
        else if_fail_int_errno(getline)
        else if_invalid(getline)
    }
    ssize_t ret = __real_getline(lineptr, n, stream);
    if (ret >= 0) {
        msg("return %li; errno %s; n=%li, line=%eb", ret, strerrorname_np(errno), *n, ret, *lineptr);
    } else {
        msg("return %li; errno %s; n=%li", ret, strerrorname_np(errno), *n);
    }
    return ret;
}

ssize_t sym(getdelim)(char **restrict lineptr, size_t *restrict n, int delim, FILE *restrict stream) {
    init();
    Dl_info info;
    if (!dladdr(ret_addr, &info) || !func_flags[func_idx_getdelim] || !lib_flags[lib_idx]) return __real_getdelim(lineptr, n, delim, stream);
    char src_file[256];
    msg("getdelim(%p:%p, %p:%li, %p)" ret_str, lineptr, lineptr != NULL ? *lineptr : NULL, n, n != NULL ? *n : 0, stream, ret_data);
    if (mode >= 4) {
        char msg_buf[BUFFER_SIZE];
        rcv(msg_buf, sizeof(msg_buf));
        if (strncmp(msg_buf, "modify ", 7) == 0) {
            // TODO getdelim modify
            fprintf(stderr, "intercept: %s: modify command not implemented\n", "getdelim");
        } else if_return_int_errno(getdelim)
        else if_fail_int_errno(getdelim)
        else if_invalid(getdelim)
    }
    ssize_t ret = __real_getdelim(lineptr, n, delim, stream);
    if (ret >= 0) {
        msg("return %li; errno %s; n=%li, line=%eb", ret, strerrorname_np(errno), *n, ret, *lineptr);
    } else {
        msg("return %li; errno %s; n=%li", ret, strerrorname_np(errno), *n);
    }
    return ret;
}

