#define _GNU_SOURCE
#include "backtrace.h"
#include <execinfo.h>              // for backtrace, backtrace_symbols
#include <fcntl.h>                 // for open, O_CLOEXEC, O_RDONLY
#include <pthread.h>               // for pthread_mutex_lock, pthread_mute...
#include <spawn.h>                 // for posix_spawn_file_actions_t, posi...
#include <stddef.h>                // for ptrdiff_t
#include <stdio.h>                 // for snprintf, sscanf
#include <stdlib.h>                // for size_t, NULL, abort, free, strtoull
#include <string.h>                // for memset, strrchr
#include <sys/wait.h>              // for waitpid
#include <unistd.h>                // for close, read, pipe2, ssize_t, rea...
#include "simplest-defs.h"  // for NELEM, UNUSED
#include "sparse.h"         // for __force
#include "spew.h"           // for spew, SPEW_DEBUG, SPEW_INFO, SPE...

/* DO NOT USE ASSERT() IN THIS FILE, TO AVOID INFINITE RECURSION */
#ifdef ASSERT
#error CANNOT_USE_ASSERT_IN_THIS_FILE
#endif

// I'm using pthread_mutex_t rather than xthread_mutex_t because I cannot
// think of a better way to do this than with a static variable.  And with a
// static variable, I want to use PTHREAD_MUTEX_INITIALIZER, which there
// doesn't seem to have a xthread_mutex analogue.

static pthread_mutex_t backtrace_mutex = PTHREAD_MUTEX_INITIALIZER;

// Backtracer wants to fork an "addr2line" process to be able to translate
// code offsets into human-readable strings.  Back in the dark ages, we would
// do the fork whenever we wanted a backtrace.  That turned out to be too
// expensive, since fork() would copy huge page table (covering many gigabytes
// of memory), and it was silly because the exec() would just throw it away.
//
// Our next solution used a constructor/destructor.  At the beginning of the
// program we would start up the backtracer.  At that point in time the page
// table was small, and we didn't really care if it was expensive since
// nothing was really happening yet.  However we don't really like the
// constructor/destructor stuff, since it's fragile.  (And Matteo doesn't like
// it because it's a desugared version of C++ :-)
//
// This solution uses posix_spawnp() to do a fork/exec operation.
// posix_spawnp() lets you do some housekeeping (with dup2 and close
// operations) between the fork and the spawn.  Because the kernel gets all
// the information about the fork/dup/close/spawn sequence at once, it can
// avoid copying the page tables.  The performance of this sequence is about
// 200us to 500us.

struct backtracer {
    int    broken;          // true if something went wrong and the backtracer
                            // is failing.  Fall back to not printing
                            // backtraces, and try to keep running.
    pid_t  child;           // The addr2line process
    int    pipe_to_child;   // The write end of a pipe that goes the
                            // addr2line's stdin.
    int    pipe_from_child; // The read end of a pipe that comes from
                            // addr2line's stdout.
    size_t va_base;         // The offset to be subtracted from addresses to
                            // to ASLR.
};

static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
sinit(posix_spawn_file_actions_t *file_actions)
// Effect: Initialize the file_actions.  If anything goes wrong return -1.
{
    int r = posix_spawn_file_actions_init(file_actions);
    if (r != 0) {
        spew(SPEW_DEBUG, "Cannot spawn_file_actions_init: r=%d", r);
        return -1;
    } else {
        return 0;
    }
 }

static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
sdup2(posix_spawn_file_actions_t *file_actions, int from, int to)
// Effect: Record a dup2() operation to be done when posix_spawn runs.  If
// anything goes wrong, spew a diagnostic and return -1;
{
    int r = posix_spawn_file_actions_adddup2(file_actions, from, to);
    if (r != 0) {
        spew(SPEW_DEBUG, "Cannot spawn_fileactions_adddup2: r=%d", r);
        return -1;
    } else {
        return 0;
    }
}

static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
sclose(posix_spawn_file_actions_t *file_actions, int fd)
// Effect: Record a close() operation to be cone when posix_spawn runs.  If
// anything goes wrong, spew a diagnostic and return -1;
{
    int r = posix_spawn_file_actions_addclose(file_actions, fd);
    if (r != 0) {
        spew(SPEW_DEBUG, "Cannot spawn_fileactions_addclose: r=%d", r);
        return -1;
    } else {
        return 0;
    }
}

static const struct backtracer errresult = {
    .pipe_to_child=-1, .pipe_from_child=-1, .child=0, .broken=1, .va_base = 0};

static struct backtracer
mk_backtracer(void) {
    // Only one backtracer may exist at a time (to prevent forkbombing if all
    // threads start backtracing.)  This mutex is unlocked when you destroy
    // the backtracer.
    pthread_mutex_lock(&backtrace_mutex);

    // Plumbing
    int tochildpipe[2];
    int fromchildpipe[2];
    {
        int r = pipe2(tochildpipe, O_CLOEXEC);
        if (r) {
            spew(SPEW_ERROR, "pipe2 failed");
        }
    }
    {
        int r = pipe2(fromchildpipe, O_CLOEXEC);
        if (r) {
            spew(SPEW_ERROR, "pipe2 failed");
        }
    }

    // Find the path to the current executable.  It's a symlink in
    // /proc/self/exe.

    char      exec_path[204];
    // readlink doesn't null terminate.  Fill it with zeros and tell it to
    // only use sizeof(exec_path)-1 to make sure there is a null at the
    // end.
    memset(exec_path, 0, sizeof(exec_path));
    ssize_t rc = readlink("/proc/self/exe", exec_path,
                          sizeof(exec_path)-1);
    UNUSED(rc);
    spew(SPEW_INFO, "exec path is %s", exec_path);

    // Set up to do the plumbing after the spawn.
    posix_spawn_file_actions_t file_actions;
    if (0
        || sinit(&file_actions)
        || sdup2(&file_actions, tochildpipe[0], 0)
        || sdup2(&file_actions, fromchildpipe[1], 1)
        || sclose(&file_actions, tochildpipe[0])
        || sclose(&file_actions, tochildpipe[1])
        || sclose(&file_actions, fromchildpipe[0])
        || sclose(&file_actions, fromchildpipe[1]))
    {
        return errresult;
    }
    pid_t child;
    {
        // Hack around because posix_spawnp doesn't have const's in quite the right place.
        char addr2line_s[] = "addr2line";
        char _e_s[]        = "-e";
        char _f_s[]        = "-f";
        char * const argv[] = {addr2line_s, _e_s, exec_path, _f_s, NULL};
        int r = posix_spawnp(&child, "addr2line",
                             &file_actions, NULL,
                             argv, environ);
        if (r != 0) {
            spew(SPEW_DEBUG, "Could not posix_spawnp r=%d", r);
            return errresult;
        }
    }
    {
        int r = posix_spawn_file_actions_destroy(&file_actions);
        if (r) spew(SPEW_FATAL, "Cannot destroy fail_actions r=%d", r);
    }

    // Plumbing back here the main program.
    struct backtracer result = {.broken = 0,
                                .child = child,
                                .pipe_to_child = tochildpipe[1],
                                .pipe_from_child = fromchildpipe[0],
                                .va_base         = 0};
    close(tochildpipe[0]);
    close(fromchildpipe[1]);

    // Find the va_base
    {
        int fd = open("/proc/self/maps", O_RDONLY);
        if (fd >= 0) {
            char addr_buffer[256];
            // HACK: we're going to assume that the first line in the
            // file contains the mapping for our executable.  Dynamic
            // libraries will be mapped in different locations, but we
            // don't really use any of them so our traceback should be
            // mostly correct.
            ssize_t bytes_read = read(fd, &addr_buffer, NELEM(addr_buffer)-1);
            if (bytes_read > 0) {
                addr_buffer[bytes_read] = 0;
                int rval = sscanf(addr_buffer, "%zx-", &result.va_base);
                if (rval != 1) {
                    result.va_base = 0;
                }
            }
        }
        close(fd);
    }
    spew(SPEW_INFO, "va_base is %zx", result.va_base);
    if (result.va_base < 0x800000) {
        // If ASLR is off, subtracting va_base makes offsets that addr2line
        // doesn't understand.  The best heuristic I've (jmp) found is that if
        // va_base is "low", then ASLR is off.  I haven't been able to figure
        // out if there's a "low" address under which ASLR is disabled, but
        // I've seen 4 MiB in many systems, so I'm going to claim any va_base
        // under 8 MiB is in a non-ASLR system.
        result.va_base = 0;
    }

    return result;
}

static void
backtracer_destroy(struct backtracer *bt) {
    close(bt->pipe_to_child);
    close(bt->pipe_from_child);
    spew(SPEW_INFO, "waitpid(%d)", bt->child);
    int wstatus;
    waitpid(bt->child, &wstatus, 0);
    spew(SPEW_INFO, "waitpid(%d) done wstatus=%d", bt->child, wstatus);
    *bt = errresult;
    pthread_mutex_unlock(&backtrace_mutex);
}

static void read_tail_of_line(int fd, char *line, size_t linelen)
// Effect: Read a line of data from fd into line.  If the line is too long to
//  fit, then put the last characters in so that if we read
//  "longfilename/fun.c:25" and linelen is 11 we end up with "e/fun.c:25"
//  rather than "longfilena".
//  line will always end NUL-terminated.
// Requires: linelen>0.  This function will fail unpredictably if linelen==0.
{
    for (size_t i = 0; i+1 < linelen; i++) {
        char c;
        ssize_t rr = read(fd, &c, 1);
        if (0 == rr || '\n' == c) {
            line[i] = 0;
            return;
        } else {
            line[i] = c;
        }
    }
    while (1) {
        char c;
        ssize_t rr = read(fd, &c, 1);
        if (0 == rr || '\n' == c) {
            line[linelen-1] = 0;
            return;
        } else {
            line[linelen-1] = c;
            // Shift the string left by 1.o
            for (size_t j = 0; j+1 < linelen; j++) {
                line[j] = line[j+1];
            }
        }
    }
}

static void spew_backtrace_symbol(struct backtracer *bt,
                                  const void *addr,
                                  const char *from_backtrace_symbol,
                                  size_t      spew_level)
{
    void *addr_from_base =  (void*)((__force size_t)addr
                                    - bt->va_base);
    char str_of_ptr[20];
    char fun_line[300] = "";
    char file_line[300] = "";
    if (!bt->broken) {
        int len = snprintf(str_of_ptr, sizeof(str_of_ptr),
                           "%p\n", addr_from_base);
        ssize_t wr = write(bt->pipe_to_child, str_of_ptr,
                           (__force size_t)(ptrdiff_t)len);
        int did_write = wr == len;
        if (!did_write) {
            bt->broken = 1;
        } else {
            read_tail_of_line(bt->pipe_from_child, fun_line, sizeof(fun_line));
            read_tail_of_line(bt->pipe_from_child, file_line, sizeof(file_line));
        }
    }
    if (!from_backtrace_symbol) {
        from_backtrace_symbol = "(unknown offset)";
    }
    const char* function = &fun_line[0];
    if (!fun_line[0]) {
        function = "(unknown function)";
    }
    size_t line_number = 0;
    const char* filename = &file_line[0];
    if (*filename) {
        // Find line number by looking for last : in file_line.
        char* line_number_start = strrchr(file_line, ':');
        if (line_number_start != NULL) {
            // Parse the line number
            line_number = strtoull(line_number_start + 1, NULL, 10);
            if (line_number > 0) {
                // Parsed, remove the line number and : from file_line.
                *line_number_start = 0;
            }
        }
    } else {
        // No filename, substitute backtrace symbol for filename.
        filename = from_backtrace_symbol;
        from_backtrace_symbol = "";
    }
    spew_for_backtrace(
        spew_level, filename, line_number, function, from_backtrace_symbol);
}

void spew_stored_backtrace(void *const*ptrs, int nptrs, size_t spew_level)
{
    char **strings = backtrace_symbols(ptrs, nptrs);

    struct backtracer bt = mk_backtracer();

    for (int j = 0; j < nptrs; j++) {
        spew_backtrace_symbol(&bt,
                              ptrs[(__force ptrdiff_t)j],
                              strings ? strings[(__force ptrdiff_t)j] : NULL,
                              spew_level);
    }

    backtracer_destroy(&bt);

    free(strings);
}

void spew_backtrace(size_t spew_level)
{
    if (spew_level < spew_get_level()) return;
    void *buffer[100];
    int nptrs = backtrace(buffer, (__force int)NELEM(buffer));

    /* cannot assert in this file */
    if (nptrs >= 0 && nptrs <= (__force int)NELEM(buffer)) {
        spew_stored_backtrace(buffer, nptrs, spew_level);
    } else {
        abort(); // can't happen
    }
}
