/*
 * task53.c — 关键文件内容监控守护进程
 *
 * 功能: 每隔5分钟读取自身文件内容并计算hash值(数字指纹),
 *       若hash值发生变化(文件被篡改), 则将检测时间和新hash值
 *       写入日志文件 log。
 *
 * 用法:
 *   ./task53           以守护进程方式后台运行(每5分钟检查一次)
 *   ./task53 -f <秒>   前台运行, 可自定义检查间隔(便于测试)
 *   ./task53 test      自检模式: 验证hash算法正确性
 *
 * hash算法: 将文件按4字节切分, 每段视为一个无符号整数(小端序),
 *           累加各段得到hash值。不足4字节的剩余部分按单字节累加。
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define INTERVAL_SEC (5 * 60)   /* 默认检查间隔: 5分钟 */
#define LOG_FILE "log"          /* 日志文件名 */

/*
 * 计算数据的hash值(数字指纹)
 * data: 文件内容指针, len: 数据长度(字节)
 * 返回: 32位无符号hash值
 */
static unsigned int compute_hash(const char *data, size_t len)
{
    unsigned int hash = 0;
    const unsigned char *p = (const unsigned char *)data;
    size_t i, n = len / 4;   /* 完整的4字节段个数 */

    /* 每4个字节拼成一个无符号整数(小端序: 低地址为低字节) */
    for (i = 0; i < n; i++) {
        unsigned int val = (unsigned int)p[i * 4]
                         | ((unsigned int)p[i * 4 + 1] << 8)
                         | ((unsigned int)p[i * 4 + 2] << 16)
                         | ((unsigned int)p[i * 4 + 3] << 24);
        hash += val;          /* 累加到hash值 */
    }

    /* 处理末尾不足4字节的剩余部分, 逐字节累加 */
    size_t rem = len % 4, base = n * 4;
    for (i = 0; i < rem; i++)
        hash += (unsigned int)p[base + i];

    return hash;
}

/*
 * 读取整个文件到内存
 * path: 文件路径, out_len: 输出参数, 返回文件长度
 * 返回: 动态分配的缓冲区指针(调用者负责free), 失败返回NULL
 */
static char *read_file(const char *path, size_t *out_len)
{
    int fd = open(path, O_RDONLY);
    if (fd < 0) return NULL;                /* 文件打开失败 */

    /* 通过lseek获取文件大小 */
    off_t size = lseek(fd, 0, SEEK_END);
    if (size < 0) { close(fd); return NULL; }
    lseek(fd, 0, SEEK_SET);                 /* 回到文件开头 */

    char *buf = malloc((size_t)size + 1);    /* 多分配1字节放'\0' */
    if (!buf) { close(fd); return NULL; }

    ssize_t n = read(fd, buf, (size_t)size);
    if (n < 0) { free(buf); close(fd); return NULL; }

    buf[n] = '\0';                           /* 字符串结尾 */
    *out_len = (size_t)n;
    close(fd);
    return buf;
}

/*
 * 将篡改检测记录写入日志文件
 * hash: 检测到的新hash值
 * 日志格式: YYYY-MM-DD HH:MM:SS  hash=<值>
 */
static void write_log(unsigned int hash)
{
    FILE *fp = fopen(LOG_FILE, "a");        /* 追加模式打开日志 */
    if (!fp) return;

    time_t now = time(NULL);                 /* 获取当前时间 */
    char ts[64];
    strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", localtime(&now));

    fprintf(fp, "%s  hash=%u\n", ts, hash);  /* 写入时间和hash */
    fclose(fp);
}

/*
 * 将进程转为守护进程(daemon)
 * 步骤: fork子进程并退出父进程 -> 创建新会话 -> 关闭标准IO
 */
static void daemonize(void)
{
    pid_t pid = fork();
    if (pid < 0) exit(1);                   /* fork失败 */
    if (pid > 0) exit(0);                   /* 父进程退出, 子进程继续 */

    setsid();                                 /* 创建新会话, 脱离控制终端 */
    umask(0);                                 /* 重置文件权限掩码 */

    /* 关闭标准输入、输出、错误, 守护进程不需要终端IO */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
}

int main(int argc, char *argv[])
{
    /*
     * 自检模式: ./task53 test
     * 验证hash算法 — 不同内容产生不同hash, 相同内容产生相同hash
     */
    if (argc > 1 && strcmp(argv[1], "test") == 0) {
        printf("=== 自检模式 ===\n");

        /* 测试1: 不同内容应产生不同hash值 */
        const char *data1 = "hello world";
        unsigned int h1 = compute_hash(data1, strlen(data1));
        printf("hash(\"%s\") = %u\n", data1, h1);

        const char *data2 = "hello worlD";   /* 仅一个字节不同 */
        unsigned int h2 = compute_hash(data2, strlen(data2));
        printf("hash(\"%s\") = %u\n", data2, h2);

        if (h1 != h2)
            printf("通过: 不同内容 -> 不同hash值\n");
        else
            printf("失败: 不同内容应产生不同的hash值\n");

        /* 测试2: 相同内容应产生相同hash值 */
        unsigned int h3 = compute_hash(data1, strlen(data1));
        if (h1 == h3)
            printf("通过: 相同内容 -> 相同hash值\n");
        else
            printf("失败: 相同内容应产生相同的hash值\n");

        /* 测试3: 读取自身并计算hash */
        size_t len;
        char *self = read_file("task53.c", &len);
        if (self) {
            unsigned int hself = compute_hash(self, len);
            printf("自身hash = %u, 文件大小 = %zu 字节\n", hself, len);
            free(self);
        }
        return 0;
    }

    /*
     * 守护进程模式
     * -f 表示前台运行(不调用daemonize), 便于测试
     * 第二个参数可指定检查间隔秒数
     */
    int foreground = (argc > 1 && strcmp(argv[1], "-f") == 0);
    int interval = INTERVAL_SEC;
    if (argc > 2) interval = atoi(argv[2]);   /* 自定义检查间隔 */
    if (interval <= 0) interval = INTERVAL_SEC;

    if (!foreground) daemonize();             /* 后台模式则转为守护进程 */

    /* 读取文件初始快照, 计算基准hash值 */
    size_t len;
    char *prev_data = read_file("task53.c", &len);
    if (!prev_data) return 1;                 /* 读取失败则退出 */
    unsigned int prev_hash = compute_hash(prev_data, len);

    /* 主循环: 每隔interval秒检查一次 */
    while (1) {
        sleep(interval);

        /* 重新读取文件内容 */
        size_t cur_len;
        char *cur_data = read_file("task53.c", &cur_len);
        if (!cur_data) continue;              /* 读取失败则跳过本次 */

        /* 计算当前hash并与基准hash比较 */
        unsigned int cur_hash = compute_hash(cur_data, cur_len);
        if (cur_hash != prev_hash) {
            /* 检测到篡改: 写入日志 */
            write_log(cur_hash);
            /* 更新基准快照, 避免同一修改被重复记录 */
            free(prev_data);
            prev_data = cur_data;
            prev_hash = cur_hash;
        } else {
            free(cur_data);                   /* 未变化则释放当前数据 */
        }
    }

    free(prev_data);
    return 0;
}
