write_mac_tool/update_mac_mt.c
2026-01-13 15:25:10 +08:00

629 lines
23 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <pthread.h>
#include <time.h>
#include <sys/wait.h>
#include "hiredis.h"
// 黄灯控制相关变量
static volatile int led_running = 0;
static pthread_t led_thread;
// 黄灯控制函数声明
static void gpio_init_yellow(void);
static void* led_fast_blink_thread(void* arg);
static void start_yellow_fast_blink(void);
static void stop_yellow_blink(void);
static void set_yellow_off(void);
static void to_upper_str(char* s) {
for (; *s; ++s) *s = (char)toupper((unsigned char)*s);
}
// 提取批次号(支持 batch=XXXX / Batch:XXXX / 扫码包含"D"+14位数字
static int extract_batch(const char* input, char* out_batch, size_t out_size) {
if (!input || !out_batch || out_size < 16) return -1;
size_t n = strlen(input);
// 主匹配:任意位置出现 'D' 或 'd' 后跟 14 位数字
for (size_t i = 0; i + 15 <= n; ++i) {
char c = input[i];
if (c == 'M' || c == 'm') {
int ok = 1;
for (int k = 1; k <= 14; ++k) {
char d = input[i + k];
if (d < '0' || d > '9') { ok = 0; break; }
}
if (ok) {
out_batch[0] = 'M';
memcpy(out_batch + 1, input + i + 1, 14);
out_batch[15] = '\0';
return 0;
}
}
}
// 回退按键名解析batch/Batch/BATCH/批次)
const char* keys[] = {"batch", "Batch", "BATCH", "批次"};
for (size_t i = 0; i < sizeof(keys)/sizeof(keys[0]); ++i) {
const char* p = strstr(input, keys[i]);
if (!p) continue;
// 跳过键名后的分隔符
p += strlen(keys[i]);
while (*p == ' ' || *p == '=' || *p == ':' || (unsigned char)*p == 0xEF) {
if ((unsigned char)*p == 0xEF) { p++; if ((unsigned char)*p == 0xBC) { p++; if ((unsigned char)*p == 0x9A) { p++; } } }
else { p++; }
}
// 读取字母数字序列作为批次号
size_t j = 0;
while (*p && j < out_size - 1) {
if ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || *p == '_' || *p == '-') {
out_batch[j++] = *p++;
} else {
break;
}
}
out_batch[j] = '\0';
if (j > 0) {
// 规范化:若形如 d+14位数字转为大写D
if (j >= 15 && (out_batch[0] == 'm' || out_batch[0] == 'M')) {
int ok2 = 1;
for (int k = 1; k <= 14; ++k) {
char d = out_batch[k];
if (d < '0' || d > '9') { ok2 = 0; break; }
}
if (ok2) out_batch[0] = 'M';
}
return 0;
}
}
return -1;
}
// 通过redis-cli查询批次映射得到MAC和密钥
static int redis_query_device_info(const char* host_opt, const char* port_opt, const char* db_opt,
const char* pool_opt, const char* batch,
char* out_mac, size_t mac_size,
char* out_secret, size_t secret_size) {
if (!batch || !out_mac || mac_size < 18 || !out_secret || secret_size < 64) return -1;
const char* host = host_opt ? host_opt : (getenv("REDIS_HOST") ? getenv("REDIS_HOST") : "180.163.74.83");
int port = port_opt ? atoi(port_opt) : (getenv("REDIS_PORT") ? atoi(getenv("REDIS_PORT")) : 6379);
int db = db_opt ? atoi(db_opt) : (getenv("REDIS_DB") ? atoi(getenv("REDIS_DB")) : 0);
const char* pool = pool_opt ? pool_opt : (getenv("REDIS_POOL") ? getenv("REDIS_POOL") : "batch_sn_mapping_mt");
const char* auth = getenv("REDIS_AUTH");
if (!auth || !*auth) auth = "Zzh08165511";
fprintf(stderr, "[redis] connect host=%s port=%d db=%d pool=%s batch=%s\n", host, port, db, pool, batch);
struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0;
redisContext* c = redisConnectWithTimeout(host, port, tv);
if (!c || c->err) {
fprintf(stderr, "[redis] connect error: %s\n", c ? c->errstr : "unknown");
if (c) redisFree(c);
return -1;
}
if (auth && *auth) {
redisReply* ra = (redisReply*)redisCommand(c, "AUTH %s", auth);
if (!ra || (ra->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis] AUTH error: %s\n", ra && ra->str ? ra->str : "no-reply");
if (ra) freeReplyObject(ra);
redisFree(c);
return -1;
}
fprintf(stderr, "[redis] AUTH ok\n");
freeReplyObject(ra);
}
if (db > 0) {
redisReply* rs = (redisReply*)redisCommand(c, "SELECT %d", db);
if (!rs || (rs->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis] SELECT %d error: %s\n", db, rs && rs->str ? rs->str : "no-reply");
if (rs) freeReplyObject(rs);
redisFree(c);
return -1;
}
fprintf(stderr, "[redis] SELECT %d ok\n", db);
freeReplyObject(rs);
}
// 尝试 HGET pool batch
redisReply* r = (redisReply*)redisCommand(c, "HGET %s %s", pool, batch);
if (r) {
fprintf(stderr, "[redis] HGET %s %s -> type=%d len=%ld\n", pool, batch, r->type, (long)(r->type==REDIS_REPLY_STRING ? r->len : 0));
if (r->type == REDIS_REPLY_STRING && r->str && r->len > 0) {
fprintf(stderr, "[redis] HGET value: %.*s\n", (int)((r->len>128)?128:r->len), r->str);
// 解析格式: "90A9F73002CD:kapoePfwbQ2tFkeRpR3Nut"
char* colon = strchr(r->str, ':');
if (colon) {
// 提取MAC部分12个字符
size_t mac_len = colon - r->str;
if (mac_len == 12) {
// 格式化为MAC地址格式 XX:XX:XX:XX:XX:XX
snprintf(out_mac, mac_size, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
r->str[0], r->str[1], r->str[2], r->str[3],
r->str[4], r->str[5], r->str[6], r->str[7],
r->str[8], r->str[9], r->str[10], r->str[11]);
to_upper_str(out_mac);
// 提取密钥部分(冒号后面的内容)
strncpy(out_secret, colon + 1, secret_size - 1);
out_secret[secret_size - 1] = '\0';
fprintf(stderr, "[redis] Parsed MAC: %s, Secret: %s\n", out_mac, out_secret);
freeReplyObject(r);
redisFree(c);
return 0;
}
}
}
freeReplyObject(r);
} else {
fprintf(stderr, "[redis] HGET no-reply\n");
}
fprintf(stderr, "[redis] not found or value not parsable\n");
redisFree(c);
return -1;
}
// 审计在Redis记录每次使用的MAC、批次与时间
static int redis_audit_log(const char* host_opt, const char* port_opt, const char* db_opt,
const char* audit_key_opt, const char* batch, const char* mac, const char* note_opt) {
if (!batch || !*batch || !mac || !*mac) return -1;
const char* host = host_opt ? host_opt : (getenv("REDIS_HOST") ? getenv("REDIS_HOST") : "180.163.74.83");
int port = port_opt ? atoi(port_opt) : (getenv("REDIS_PORT") ? atoi(getenv("REDIS_PORT")) : 6379);
int db = db_opt ? atoi(db_opt) : (getenv("REDIS_DB") ? atoi(getenv("REDIS_DB")) : 0);
const char* auth = getenv("REDIS_AUTH");
if (!auth || !*auth) auth = "Zzh08165511";
const char* audit_key = audit_key_opt ? audit_key_opt : (getenv("REDIS_AUDIT_KEY") ? getenv("REDIS_AUDIT_KEY") : "mac_batch_audit_mt");
struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0;
redisContext* c = redisConnectWithTimeout(host, port, tv);
if (!c || c->err) {
fprintf(stderr, "[redis-audit] connect error: %s\n", c ? c->errstr : "unknown");
if (c) redisFree(c);
return -1;
}
if (auth && *auth) {
redisReply* ra = (redisReply*)redisCommand(c, "AUTH %s", auth);
if (!ra || (ra->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis-audit] AUTH error: %s\n", ra && ra->str ? ra->str : "no-reply");
if (ra) freeReplyObject(ra);
redisFree(c);
return -1;
}
freeReplyObject(ra);
}
if (db > 0) {
redisReply* rs = (redisReply*)redisCommand(c, "SELECT %d", db);
if (!rs || (rs->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis-audit] SELECT %d error: %s\n", db, rs && rs->str ? rs->str : "no-reply");
if (rs) freeReplyObject(rs);
redisFree(c);
return -1;
}
freeReplyObject(rs);
}
// 优先使用 Redis 服务器时间,避免设备本地时间不准
long long sv_secs = -1;
{
redisReply* tr = (redisReply*)redisCommand(c, "TIME");
if (tr && tr->type == REDIS_REPLY_ARRAY && tr->elements >= 2 &&
tr->element[0] && tr->element[0]->str) {
sv_secs = atoll(tr->element[0]->str);
} else {
fprintf(stderr, "[redis-audit] TIME failed, fallback to local time\n");
}
if (tr) freeReplyObject(tr);
}
time_t base = (sv_secs > 0) ? (time_t)sv_secs : time(NULL);
// 中国时区(+08:00不依赖设备TZ格式为 YYYY-MM-DD HH:MM:SS
time_t base_cn = base + 8 * 3600;
struct tm tm_sv_cn; gmtime_r(&base_cn, &tm_sv_cn);
char ts_cn[24];
strftime(ts_cn, sizeof(ts_cn), "%Y-%m-%d %H:%M:%S", &tm_sv_cn);
char val[320];
snprintf(val, sizeof(val), "ts_cn=%s batch=%s mac=%s%s%s", ts_cn, batch, mac,
(note_opt && *note_opt) ? " note=" : "",
(note_opt && *note_opt) ? note_opt : "");
// 写入总审计列表
redisReply* r = (redisReply*)redisCommand(c, "LPUSH %s %s", audit_key, val);
if (!r || r->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "[redis-audit] LPUSH %s failed: %s\n", audit_key, r && r->str ? r->str : "no-reply");
if (r) freeReplyObject(r);
redisFree(c);
return -1;
}
freeReplyObject(r);
// 同时按批次维度记录key为 <audit_key>:<batch>
char batch_key[128];
snprintf(batch_key, sizeof(batch_key), "%s:%s", audit_key, batch);
r = (redisReply*)redisCommand(c, "LPUSH %s %s", batch_key, val);
if (!r || r->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "[redis-audit] LPUSH %s failed: %s\n", batch_key, r && r->str ? r->str : "no-reply");
if (r) freeReplyObject(r);
redisFree(c);
return -1;
}
freeReplyObject(r);
redisFree(c);
fprintf(stderr, "[redis-audit] logged: %s | %s\n", batch, mac);
return 0;
}
// 读取interfaces文件中的MAC地址
static int get_mac_from_interfaces(const char* interfaces_path, char* out_mac, size_t out_size) {
FILE* f = fopen(interfaces_path, "r");
if (!f) return -1;
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "hwaddress ether")) {
char* mac_start = strstr(line, "ether");
if (mac_start) {
mac_start += 6; // 跳过"ether "
char* mac_end = strchr(mac_start, '\n');
if (mac_end) {
size_t len = mac_end - mac_start;
if (len > 0 && len < out_size) {
strncpy(out_mac, mac_start, len);
out_mac[len] = '\0';
// 去除可能的空格
char* space = strchr(out_mac, ' ');
if (space) *space = '\0';
fclose(f);
return 0;
}
}
}
}
}
fclose(f);
return -1;
}
static int write_file(const char* path, const char* buf, size_t len) {
FILE* f = fopen(path, "wb");
if (!f) return -1;
size_t n = fwrite(buf, 1, len, f);
// 确保数据写入到内核缓冲区
fflush(f);
int rc = 0;
// 将数据与元数据刷新到磁盘,避免重启丢失
int fd = fileno(f);
if (fd >= 0) {
if (fsync(fd) != 0) rc = -1;
}
fclose(f);
if (n != len) rc = -1;
return rc;
}
// 写入interfaces文件使用DHCP并设置MAC
static int update_interfaces_with_mac_dhcp(const char* interfaces_path, const char* mac_colon) {
const char* tmpl =
"# interfaces(5) file used by ifup(8) and ifdown(8)\n"
"# Include files from /etc/network/interfaces.d:\n"
"source-directory /etc/network/interfaces.d\n"
"auto lo\n"
"iface lo inet loopback\n"
"auto eth0\n"
"iface eth0 inet dhcp\n"
"hwaddress ether %s\n";
char* newbuf = (char*)malloc(1024);
if (!newbuf) {
fprintf(stderr, "malloc failed\n");
return -1;
}
int n = snprintf(newbuf, 1024, tmpl, mac_colon);
if (n <= 0) {
free(newbuf);
return -1;
}
int rc = write_file(interfaces_path, newbuf, (size_t)n);
if (rc == 0) {
sync();
printf("写入interfaces文件并更新MAC: %s (DHCP)\n", mac_colon);
} else {
fprintf(stderr, "写入失败 %s\n", interfaces_path);
}
free(newbuf);
return rc;
}
static int scanner_read_event2(char* out, size_t out_size) {
const char* force_stdin = getenv("SCANNER_FORCE_STDIN");
if (force_stdin && strcmp(force_stdin, "1") == 0) return -1;
int fd = open("/dev/input/event2", O_RDONLY);
if (fd < 0) return -1;
struct input_event ev;
size_t j = 0;
int shift = 0;
while (1) {
ssize_t r = read(fd, &ev, sizeof(ev));
if (r <= 0) continue;
if (ev.type != EV_KEY) continue;
// 维护Shift状态
if (ev.code == KEY_LEFTSHIFT || ev.code == KEY_RIGHTSHIFT) {
if (ev.value == 1) shift = 1; // 按下
else if (ev.value == 0) shift = 0; // 松开
continue;
}
// 只在松开(key up)时采集字符,避免重复
if (ev.value != 0) {
if (ev.code == KEY_ENTER || ev.code == KEY_KPENTER) {
break;
}
continue;
}
char ch = 0;
switch (ev.code) {
// 数字键行
case KEY_1: ch = shift ? '!' : '1'; break;
case KEY_2: ch = shift ? '@' : '2'; break;
case KEY_3: ch = shift ? '#' : '3'; break;
case KEY_4: ch = shift ? '$' : '4'; break;
case KEY_5: ch = shift ? '%' : '5'; break;
case KEY_6: ch = shift ? '^' : '6'; break;
case KEY_7: ch = shift ? '&' : '7'; break;
case KEY_8: ch = shift ? '*' : '8'; break;
case KEY_9: ch = shift ? '(' : '9'; break;
case KEY_0: ch = shift ? ')' : '0'; break;
// 小键盘数字
case KEY_KP1: ch = '1'; break; case KEY_KP2: ch = '2'; break; case KEY_KP3: ch = '3'; break;
case KEY_KP4: ch = '4'; break; case KEY_KP5: ch = '5'; break; case KEY_KP6: ch = '6'; break;
case KEY_KP7: ch = '7'; break; case KEY_KP8: ch = '8'; break; case KEY_KP9: ch = '9'; break;
case KEY_KP0: ch = '0'; break;
// 字母(显式映射,避免假设键码连续)
case KEY_A: ch = shift ? 'A' : 'a'; break;
case KEY_B: ch = shift ? 'B' : 'b'; break;
case KEY_C: ch = shift ? 'C' : 'c'; break;
case KEY_D: ch = shift ? 'D' : 'd'; break;
case KEY_E: ch = shift ? 'E' : 'e'; break;
case KEY_F: ch = shift ? 'F' : 'f'; break;
case KEY_G: ch = shift ? 'G' : 'g'; break;
case KEY_H: ch = shift ? 'H' : 'h'; break;
case KEY_I: ch = shift ? 'I' : 'i'; break;
case KEY_J: ch = shift ? 'J' : 'j'; break;
case KEY_K: ch = shift ? 'K' : 'k'; break;
case KEY_L: ch = shift ? 'L' : 'l'; break;
case KEY_M: ch = shift ? 'M' : 'm'; break;
case KEY_N: ch = shift ? 'N' : 'n'; break;
case KEY_O: ch = shift ? 'O' : 'o'; break;
case KEY_P: ch = shift ? 'P' : 'p'; break;
case KEY_Q: ch = shift ? 'Q' : 'q'; break;
case KEY_R: ch = shift ? 'R' : 'r'; break;
case KEY_S: ch = shift ? 'S' : 's'; break;
case KEY_T: ch = shift ? 'T' : 't'; break;
case KEY_U: ch = shift ? 'U' : 'u'; break;
case KEY_V: ch = shift ? 'V' : 'v'; break;
case KEY_W: ch = shift ? 'W' : 'w'; break;
case KEY_X: ch = shift ? 'X' : 'x'; break;
case KEY_Y: ch = shift ? 'Y' : 'y'; break;
case KEY_Z: ch = shift ? 'Z' : 'z'; break;
// 其他符号
case KEY_MINUS: ch = shift ? '_' : '-'; break;
case KEY_SEMICOLON: ch = shift ? ':' : ';'; break;
case KEY_APOSTROPHE: ch = shift ? '"' : '\''; break;
case KEY_BACKSLASH: ch = shift ? '|' : '\\'; break;
case KEY_EQUAL: ch = shift ? '+' : '='; break;
default: break;
}
if (ch) {
if (j < out_size - 1) out[j++] = ch;
}
}
out[j] = '\0';
close(fd);
return strlen(out) > 0 ? 0 : -1;
}
// 黄灯控制函数实现
static void gpio_init_yellow(void) {
int fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd >= 0) {
write(fd, "113", 3);
close(fd);
}
fd = open("/sys/class/gpio/gpio113/direction", O_WRONLY);
if (fd >= 0) {
write(fd, "out", 3);
close(fd);
}
}
static void* led_fast_blink_thread(void* arg) {
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY);
if (fd < 0) return NULL;
while (led_running) {
write(fd, "1", 1);
usleep(100000); // 100ms - 快速闪烁
write(fd, "0", 1);
usleep(100000); // 100ms - 快速闪烁
}
// ensure off
write(fd, "0", 1);
close(fd);
return NULL;
}
static void start_yellow_fast_blink(void) {
if (led_running) return;
gpio_init_yellow();
led_running = 1;
pthread_create(&led_thread, NULL, led_fast_blink_thread, NULL);
}
static void stop_yellow_blink(void) {
if (!led_running) return;
led_running = 0;
// 等待线程退出
pthread_join(led_thread, NULL);
}
static void set_yellow_off(void) {
// 确保不再闪烁
stop_yellow_blink();
// 初始化GPIO并置为低电平灭灯
gpio_init_yellow();
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY);
if (fd < 0) return;
write(fd, "0", 1);
close(fd);
}
static int read_qrcode_string(char* out, size_t out_size) {
// Try hardware scanner first
printf("尝试读取硬件扫码内容...\n");
if (scanner_read_event2(out, out_size) == 0) return 0;
// Fallback: read a line from stdin (useful for piping during tests)
fprintf(stdout, "请在终端输入扫码内容并回车...\n");
if (fgets(out, (int)out_size, stdin) == NULL) return -1;
// trim newline
size_t n = strlen(out);
if (n && out[n-1] == '\n') out[n-1] = '\0';
return strlen(out) > 0 ? 0 : -1;
}
int main(int argc, char** argv) {
const char* interfaces_path = (argc >= 2) ? argv[1] : "/etc/network/interfaces";
setvbuf(stderr, NULL, _IONBF, 0);
// 首先检查当前MAC地址
char current_mac[32] = {0};
if (get_mac_from_interfaces(interfaces_path, current_mac, sizeof(current_mac)) == 0) {
to_upper_str(current_mac);
printf("当前MAC地址: %s\n", current_mac);
// 如果MAC不是默认值程序退出
if (strcmp(current_mac, "90:A9:F7:30:00:00") != 0) {
printf("MAC地址非默认程序退出\n");
set_yellow_off();
return 0;
}
}
// MAC是默认值开始快速闪烁黄灯
printf("MAC地址为默认值开始快速闪烁黄灯\n");
start_yellow_fast_blink();
printf("等待扫码...\n");
while (1) {
char scanbuf[512] = {0};
if (read_qrcode_string(scanbuf, sizeof(scanbuf)) != 0) {
fprintf(stderr, "读取扫码内容失败,重试...\n");
continue;
}
printf("扫码原始内容: %s\n", scanbuf);
// 提取批次号
char batch[128] = {0};
if (extract_batch(scanbuf, batch, sizeof(batch)) != 0) {
fprintf(stderr, "未识别到批次号,请重新扫码...\n");
continue;
}
printf("识别到批次号: %s\n", batch);
// 查询Redis获取设备信息
char mac[32] = {0};
char secret[128] = {0};
if (redis_query_device_info(NULL, NULL, NULL, NULL, batch, mac, sizeof(mac), secret, sizeof(secret)) != 0) {
fprintf(stderr, "Redis查询失败请重试...\n");
continue;
}
printf("查询成功 - MAC: %s, Secret: %s\n", mac, secret);
// 写入savedDevSn文件不带冒号
char mac_no_colon[13] = {0}; // 12位MAC + \0
for (int i = 0, j = 0; i < strlen(mac) && j < 12; i++) {
if (mac[i] != ':') {
mac_no_colon[j++] = mac[i];
}
}
if (write_file("/root/savedDevSn", mac_no_colon, strlen(mac_no_colon)) == 0) {
printf("已写入 /root/savedDevSn: %s\n", mac_no_colon);
} else {
fprintf(stderr, "写入 /root/savedDevSn 失败\n");
continue;
}
// 写入appSecret文件
if (write_file("/root/appSecret", secret, strlen(secret)) == 0) {
printf("已写入 /root/appSecret: %s\n", secret);
} else {
fprintf(stderr, "写入 /root/appSecret 失败\n");
continue;
}
// 写入interfaces文件DHCP + MAC
if (update_interfaces_with_mac_dhcp(interfaces_path, mac) == 0) {
printf("已更新 %s (DHCP + MAC: %s)\n", interfaces_path, mac);
} else {
fprintf(stderr, "更新 %s 失败\n", interfaces_path);
continue;
}
// 记录审计日志
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac, "success");
// 停止黄灯闪烁
stop_yellow_blink();
set_yellow_off();
printf("所有配置更新完成!\n");
// 重启mt_server服务
printf("正在重启mt_server服务...\n");
// 先重新加载systemd配置
printf("重新加载systemd配置...\n");
system("systemctl daemon-reload");
// 然后重启服务
int ret = system("systemctl restart mt_server");
if (ret == 0) {
printf("mt_server服务重启成功\n");
} else {
printf("mt_server服务重启失败返回码: %d\n", ret);
}
break;
}
return 0;
}