206 lines
6.8 KiB
C
206 lines
6.8 KiB
C
|
|
/*
|
||
|
|
* 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;
|
||
|
|
}
|