/* package ndk/jni/crash_hook.c */

/*********************************************************************
 * crash_hook.c
 * Created at: Wed 20 May 2026 14:31:55 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 130 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	3ae2b4ff82863a890f6b5a592d9dd3d93bd28afbf93817a4ecae9e5c2cf03811
**********************************************************************/
#include <jni.h>
#include <android/log.h>
#include <dlfcn.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <unwind.h>

#define LOG_TAG "androidcast_crash"
#define REPORT_DIR_MAX 512
#define MAX_FRAMES 64

static char g_report_dir[REPORT_DIR_MAX];
static volatile sig_atomic_t g_handling;

typedef struct {
    void **frames;
    int max;
    int count;
} backtrace_state_t;

static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg) {
    backtrace_state_t *state = (backtrace_state_t *) arg;
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc == 0) {
        return _URC_NO_REASON;
    }
    if (state->count < state->max) {
        state->frames[state->count++] = (void *) pc;
    }
    return _URC_NO_REASON;
}

static int capture_backtrace(void **frames, int max) {
    backtrace_state_t state = {frames, max, 0};
    _Unwind_Backtrace(unwind_callback, &state);
    return state.count;
}

static void write_native_report(const char *signal_name) {
    if (g_report_dir[0] == '\0') {
        return;
    }
    char path[REPORT_DIR_MAX + 64];
    snprintf(path, sizeof(path), "%s/native_%d_%ld.txt",
            g_report_dir, (int) getpid(), (long) time(NULL));
    FILE *f = fopen(path, "w");
    if (!f) {
        return;
    }
    fprintf(f, "signal=%s\n", signal_name);
    fprintf(f, "pid=%d\n", (int) getpid());
    void *frames[MAX_FRAMES];
    int n = capture_backtrace(frames, MAX_FRAMES);
    for (int i = 0; i < n; i++) {
        Dl_info info;
        memset(&info, 0, sizeof(info));
        if (dladdr(frames[i], &info) && info.dli_fname) {
            uintptr_t offset = (uintptr_t) frames[i] - (uintptr_t) info.dli_fbase;
            fprintf(f, "#%02d %p %s", i, frames[i], info.dli_fname);
            if (info.dli_sname) {
                fprintf(f, " %s+0x%lx", info.dli_sname,
                        (unsigned long) ((uintptr_t) frames[i] - (uintptr_t) info.dli_saddr));
            } else {
                fprintf(f, "+0x%lx", (unsigned long) offset);
            }
            fprintf(f, "\n");
        } else {
            fprintf(f, "#%02d %p\n", i, frames[i]);
        }
    }
    fclose(f);
}

static void crash_signal_handler(int sig) {
    if (g_handling) {
        _exit(128 + sig);
    }
    g_handling = 1;
    const char *name = "UNKNOWN";
    switch (sig) {
        case SIGSEGV: name = "SIGSEGV"; break;
        case SIGABRT: name = "SIGABRT"; break;
        case SIGBUS:  name = "SIGBUS";  break;
        case SIGFPE:  name = "SIGFPE";  break;
        case SIGILL:  name = "SIGILL";  break;
    }
    write_native_report(name);
    signal(sig, SIG_DFL);
    raise(sig);
}

static void install_handlers(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = crash_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESETHAND;
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
    sigaction(SIGBUS, &sa, NULL);
    sigaction(SIGFPE, &sa, NULL);
    sigaction(SIGILL, &sa, NULL);
}

JNIEXPORT void JNICALL
Java_com_foxx_androidcast_crash_CrashNativeBridge_nativeInstallCrashHook(
        JNIEnv *env, jclass clazz, jstring reportDir) {
    (void) clazz;
    const char *dir = (*env)->GetStringUTFChars(env, reportDir, NULL);
    if (!dir) {
        return;
    }
    strncpy(g_report_dir, dir, REPORT_DIR_MAX - 1);
    g_report_dir[REPORT_DIR_MAX - 1] = '\0';
    (*env)->ReleaseStringUTFChars(env, reportDir, dir);
    install_handlers();
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "native crash hook installed: %s", g_report_dir);
}
