/*
 * Created by jiaqiang.ye@lnxall.com
 * Created by Artificial Intelligence
 *
 * Refer to https://www.tapd.cn/32297576/bugtrace/bugs/view/1132297576001008397
 *          https://www.tapd.cn/32297576/bugtrace/bugs/view/1132297576001008662
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <dirent.h>
#include <time.h>
#include <sys/reboot.h>
#include <getopt.h>

#define KMSG_PATH "/dev/kmsg"
#define TARGET_STRING "mmc1: Timeout waiting for hardware interrupt"
#define WATCHDOG_NAME "watchdog"
#define REBOOT_DELAY 60 // seconds

// Define LINUX_REBOOT_CMD_RESTART if not already defined
#ifndef LINUX_REBOOT_CMD_RESTART
#define LINUX_REBOOT_CMD_RESTART 0x01234567
#endif

static pid_t watchdog_pid = -1;
static struct timespec last_reboot_time = {0, 0};

// Function to find the PID of the watchdog process by scanning /proc
pid_t find_watchdog_pid() {
    DIR *dir;
    struct dirent *entry;
    char path[256];
    char comm[20]; // Maximum length of `comm` is 16, but we use a larger buffer for safety
    FILE *fp;

    dir = opendir("/proc");
    if (!dir) {
        fprintf(stderr, "Failed to open /proc\n");
        return -1;
    }

    while ((entry = readdir(dir)) != NULL) {
        // Check if the entry is a PID directory
        if (entry->d_type == DT_DIR) {
            pid_t pid = atoi(entry->d_name);
            if (pid > 0) {
                // Construct the path to the comm file
                snprintf(path, sizeof(path), "/proc/%ld/comm", (long)pid);

                // Open the comm file
                fp = fopen(path, "rb");
                if (fp) {
                    memset(comm, 0, sizeof(comm));
                    if (fgets(comm, sizeof(comm) - 1, fp) != NULL) {
                        // Remove trailing newline character (if present)
                        char *p = strchr(comm, '\n');
                        if (p != NULL) {
                            *p = '\0';
                        }

                        // Check if the process name matches "watchdog"
                        if (strcmp(comm, WATCHDOG_NAME) == 0) {
                            fclose(fp);
                            closedir(dir);
                            return pid;
                        }
                    }
                    fclose(fp);
                }
            }
        }
    }

    closedir(dir);
    return -1;
}

// Function to check if enough time has passed since the last reboot
int can_reboot() {
    struct timespec current_time;
    if (clock_gettime(CLOCK_BOOTTIME, &current_time) == -1) {
        fprintf(stderr, "Failed to get boot time\n");
        return 0;
    }

    // Convert to seconds
    time_t elapsed_time = current_time.tv_sec - last_reboot_time.tv_sec;

    if (elapsed_time >= REBOOT_DELAY) {
        last_reboot_time = current_time;
        return 1;
    }
    return 0;
}

static int sysup_time(int * upt)
{
    int ret;
    struct timespec ts;
    ret = clock_gettime(CLOCK_BOOTTIME, &ts);
    if (ret < 0)
        ts.tv_sec = time(NULL);
    if (upt != NULL)
        *upt = (int) ts.tv_sec;
    return (int) ts.tv_sec;
}

int main(int argc, char *argv[]) {
    int fd, first_enc = 0;
    int attached = 0;
    char buffer[4096];
    ssize_t bytes_read;
    pid_t pid = 0;

    // Parse command-line options
    int opt;
    int debug_enabled = 0; // Default: no debugging
    while ((opt = getopt(argc, argv, "d")) != -1) {
        switch (opt) {
            case 'd':
                debug_enabled = 1;
                break;
            default:
                fprintf(stderr, "Usage: %s [-d]\n", argv[0]);
                return EXIT_FAILURE;
        }
    }

    setenv("PATH", "/opt/lnxall_app/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);

    // do not run if not IMX6UL device
    {
        char * p, * p0;
        FILE * fp = fopen("/proc/cpuinfo", "rb");

        memset(buffer, 0, sizeof(buffer));
        if (fp != NULL) {
            fread(buffer, 1, sizeof(buffer) - 1, fp);
            fclose(fp);
        }

        if (strstr(buffer, "i.MX6 Ultralite") == NULL) {
            fputs("Not imx6ul device, do not continue.\n", stdout);
            fflush(stdout);
            pause();
        }

        // read gateway SN
        fp = popen("factory get", "r");
        memset(buffer, 0, sizeof(buffer));
        if (fp != NULL) {
            sleep(1);
            fread(buffer, 1, sizeof(buffer) - 1, fp);
            pclose(fp);
        }

        p = strstr(buffer, "SN=");
        if (p != NULL) {
            p += 3;
            p0 = strchr(p, '\n');
            if (p0 && p0 > p) {
                *p0 = '\0';
                setenv("GW_SN", p, 1);
                fprintf(stdout, "Setting GW_SN to %s\n", p);
                fflush(stdout);
            }
        }

    }

    if (access("/root/emmc_files", F_OK) == 0)
        system("rm -rf /tmp/emmc_files && cp -rf /root/emmc_files /tmp/");

    // Redirect stdout and stderr to /dev/null unless debugging is enabled
    if (!debug_enabled) {
        int null_fd = open("/dev/null", O_WRONLY);
        if (null_fd >= 0) {
            dup2(null_fd, STDOUT_FILENO);
            dup2(null_fd, STDERR_FILENO);
            close(null_fd);
        } else {
            perror("Failed to open /dev/null");
            return EXIT_FAILURE;
        }
    }

    // Open /dev/kmsg in read-only mode
    fd = open(KMSG_PATH, O_RDONLY);
    if (fd < 0) {
        perror("Failed to open /dev/kmsg");
        return EXIT_FAILURE;
    }

    while (1) {
        // Read from /dev/kmsg in block mode
        if (fd >= 0) {
            bytes_read = read(fd, buffer, sizeof(buffer) - 1);
        } else {
            struct timespec ts;
            bytes_read = 0;
            ts.tv_sec = 2;
            ts.tv_nsec = 0;
            nanosleep(&ts, NULL);
        }
        if (bytes_read < 0) {
            perror("Error reading from /dev/kmsg");
            close(fd);
            return EXIT_FAILURE;
        }

        // Null-terminate the buffer
        buffer[bytes_read] = '\0';

        // Check if the target string is present
        if (first_enc || strstr(buffer, TARGET_STRING) != NULL || strstr(buffer, "error -110 requesting status")) {
            int should_reboot = 1, have_dash;
            const char * dash = "/tmp/emmc_files/dash";

            if (fd >= 0) {
                close(fd);
                fd = -1;
            }

            if (pid > 0) {
                int stats = 0;
                if (waitpid(pid, &stats, WNOHANG) > 0) {
                    fprintf(stdout, "Process has terminated with: 0x%x\n", (unsigned int) stats);
                    fflush(stdout);
                    pid = 0;
                }
            }

            have_dash = access(dash, F_OK) == 0;
            if (first_enc == 0) {
                sysup_time(&first_enc);
                if (have_dash != 0) {
                    pid = fork();
                    if (pid < 0)
                        goto next;

                    if (pid == 0) {
                        setenv("LD_LIBRARY_PATH", "/tmp/emmc_files/lib", 1);
                        execl(dash, "dash", "/tmp/emmc_files/inform.sh", NULL);
                        _exit(5);
                    }
                }
                goto next;
            } else {
                int sysup = sysup_time(NULL);
                int delay = have_dash ? 1800 : 10;
                if ((sysup - first_enc) < delay)
                    goto next;
            }

            fprintf(stderr, "Target string detected: %s\n", TARGET_STRING);

            // Step 1: Find and attach to the watchdog process
            if (watchdog_pid == -1) {
                pid_t wdog_pid = find_watchdog_pid();
                if (wdog_pid > 0) {
                    // Skip reboot for the first time when watchdog is found
                    should_reboot = 0;
                    watchdog_pid = wdog_pid;
                }
            }

            // Only attach if not already attached
            if (!attached && watchdog_pid > 0) {
                fprintf(stderr, "Attaching to watchdog process (PID: %d)\n", watchdog_pid);
                if (ptrace(PTRACE_ATTACH, watchdog_pid, NULL, NULL) == -1) {
                    perror("Failed to attach to watchdog process");
                } else {
                    attached = 1;
                    waitpid(watchdog_pid, NULL, 0); // Wait for the process to stop
                    fprintf(stderr, "Successfully attached to watchdog process\n");
                }
            } else {
                fprintf(stderr, "Watchdog process not found\n");
            }

            // Step 2: Reboot the system based on policy
            if (can_reboot() && should_reboot) {
                fprintf(stderr, "Rebooting the system...\n");
                if (reboot(LINUX_REBOOT_CMD_RESTART) == -1) {
                    perror("Failed to reboot");
                }
            } else {
                fprintf(stderr, "Skipping reboot: for whatever the reason\n");
            }

next:
            ; /* Nothing important TODO */
        }
    }

    close(fd);
    return EXIT_SUCCESS;
}
