write_mac_tool/update_mac_pdd.c

848 lines
33 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 <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <pthread.h>
#include <time.h>
#include "hiredis.h"
// 前置声明:黄灯控制接口
static void start_yellow_blink(void);
static void stop_yellow_blink(void);
static void set_yellow_on(void);
static int get_hw_mac_from_interfaces(const char* interfaces_path, char* out_mac, size_t out_size);
static int is_hex(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static void to_upper_str(char* s) {
for (; *s; ++s) *s = (char)toupper((unsigned char)*s);
}
// 新增检测是否包含显式MAC带分隔符的形式用于避免误把批次号当MAC
static int has_explicit_mac_pattern(const char* input) {
if (!input) return 0;
size_t n = strlen(input);
// 1) 冒号分隔
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != ':') { ok = 0; break; } }
}
if (ok) return 1;
}
// 2) 连字符分隔
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != '-') { ok = 0; break; } }
}
if (ok) return 1;
}
return 0;
}
// Remove separators and validate exactly 12 hex chars
static int normalize_raw_mac(const char* in, char* raw12, size_t raw12_size) {
if (!in || !raw12 || raw12_size < 13) return -1;
size_t len = strlen(in);
size_t j = 0;
for (size_t i = 0; i < len; ++i) {
char c = in[i];
if (c == ':' || c == '-' || c == ' ') continue;
if (!is_hex(c)) return -1;
if (j >= 12) return -1; // too long
raw12[j++] = c;
}
if (j != 12) return -1;
raw12[12] = '\0';
return 0;
}
static int format_mac_12_to_colon(const char* raw12, char* out, size_t out_size) {
if (!raw12 || !out || out_size < 18) return -1; // XX:XX:XX:XX:XX:XX\0
char upper[13];
strncpy(upper, raw12, 12);
upper[12] = '\0';
to_upper_str(upper);
snprintf(out, out_size, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
upper[0], upper[1], upper[2], upper[3], upper[4], upper[5],
upper[6], upper[7], upper[8], upper[9], upper[10], upper[11]);
return 0;
}
// Try to extract MAC from input string in various common formats
static int extract_mac(const char* input, char* out_mac, size_t out_size) {
if (!input || !out_mac || out_size < 18) return -1;
size_t n = strlen(input);
// 1) Look for colon-separated MAC: ([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != ':') { ok = 0; break; } }
}
if (ok) {
// Copy and uppercase
strncpy(out_mac, input + i, 17);
out_mac[17] = '\0';
to_upper_str(out_mac);
return 0;
}
}
// 2) Look for hyphen-separated MAC: ([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != '-') { ok = 0; break; } }
}
if (ok) {
char tmp[18];
strncpy(tmp, input + i, 17);
tmp[17] = '\0';
// Normalize to 12 hex then colon
char raw12[13];
if (normalize_raw_mac(tmp, raw12, sizeof(raw12)) == 0 &&
format_mac_12_to_colon(raw12, out_mac, out_size) == 0) {
return 0;
}
}
}
// 3) JSON-like: "mac":"XXXXXXXXXXXX" or with colons
const char* key_variants[] = { "\"mac\"", "MAC", "mac", "Mac" };
for (size_t kv = 0; kv < sizeof(key_variants)/sizeof(key_variants[0]); ++kv) {
const char* p = strstr(input, key_variants[kv]);
if (p) {
// find delimiter ':' then take next token until quote/space
p = strchr(p, ':');
if (p) {
++p; // at value start (maybe quotes)
while (*p == ' ' || *p == '"') ++p;
// copy until non-hex or separator
char rawbuf[64] = {0};
size_t j = 0;
while (*p && j < sizeof(rawbuf)-1) {
if (is_hex(*p) || *p == ':' || *p == '-' ) {
rawbuf[j++] = *p;
++p;
} else break;
}
rawbuf[j] = '\0';
char raw12[13];
if (normalize_raw_mac(rawbuf, raw12, sizeof(raw12)) == 0 &&
format_mac_12_to_colon(raw12, out_mac, out_size) == 0) {
return 0;
}
}
}
}
// 4) Fallback: first 12 consecutive hex chars
for (size_t i = 0; i < n; ++i) {
if (is_hex(input[i])) {
size_t j = 0;
char raw12[13];
size_t k = i;
while (k < n && is_hex(input[k]) && j < 12) {
raw12[j++] = input[k++];
}
if (j == 12) {
raw12[12] = '\0';
if (format_mac_12_to_colon(raw12, out_mac, out_size) == 0) {
return 0;
}
}
}
}
return -1; // not found
}
// 新增:提取批次号(支持 batch=XXXX / Batch:XXXX / 扫码包含“批次”关键字)
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 == 'D' || c == 'd') {
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] = 'D';
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] == 'd' || out_batch[0] == 'D')) {
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] = 'D';
}
return 0;
}
}
const char* p = strstr(input, "batch");
if (p) {
p += 5;
size_t j = 0;
while (*p && j < out_size - 1 && ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z'))) {
out_batch[j++] = *p++;
}
out_batch[j] = '\0';
if (j > 0) {
if (j >= 15 && (out_batch[0] == 'd' || out_batch[0] == 'D')) {
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] = 'D';
}
return 0;
}
}
return -1;
}
// 通过redis-cli查询批次映射得到MAC
static int redis_query_mac(const char* host_opt, const char* port_opt, const char* db_opt,
const char* pool_opt, const char* batch, char* out_mac, size_t out_size) {
if (!batch || !out_mac || out_size < 18) return -1;
const char* mock = getenv("REDIS_MOCK_MAC");
if (mock && *mock) {
fprintf(stderr, "[redis] use REDIS_MOCK_MAC=%s\n", mock);
if (extract_mac(mock, out_mac, out_size) == 0) return 0;
strncpy(out_mac, mock, out_size - 1);
out_mac[out_size - 1] = '\0';
return 0;
}
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_pdd");
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);
if (extract_mac(r->str, out_mac, out_size) == 0) { freeReplyObject(r); redisFree(c); return 0; }
}
freeReplyObject(r);
} else {
fprintf(stderr, "[redis] HGET no-reply\n");
}
// 回退 GET pool:batch
r = (redisReply*)redisCommand(c, "GET %s:%s", pool, batch);
if (r) {
fprintf(stderr, "[redis] GET %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] GET value: %.*s\n", (int)((r->len>128)?128:r->len), r->str);
if (extract_mac(r->str, out_mac, out_size) == 0) { freeReplyObject(r); redisFree(c); return 0; }
}
freeReplyObject(r);
} else {
fprintf(stderr, "[redis] GET no-reply\n");
}
fprintf(stderr, "[redis] not found or value not parsable as MAC\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_pdd");
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;
}
static int read_file(const char* path, char** out_buf, size_t* out_len) {
FILE* f = fopen(path, "rb");
if (!f) return -1;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return -1; }
rewind(f);
char* buf = (char*)malloc((size_t)sz + 1);
if (!buf) { fclose(f); return -1; }
size_t n = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[n] = '\0';
*out_buf = buf;
if (out_len) *out_len = n;
return 0;
}
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;
}
static int update_hwaddress_in_interfaces(const char* interfaces_path, const char* mac_colon) {
// 如果当前MAC与目标一致则不改写
char current[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, current, sizeof(current)) == 0) {
to_upper_str(current);
if (strcmp(current, mac_colon) == 0) {
printf("MAC 未变化,跳过写入: %s\n", mac_colon);
return 0;
}
}
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 static\n"
"address 10.10.12.12\n"
"netmask 255.255.255.0\n"
"gateway 10.10.12.1\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\n", mac_colon);
} else {
fprintf(stderr, "写入失败 %s\n", interfaces_path);
}
free(newbuf);
return rc;
}
static int get_hw_mac_from_interfaces(const char* interfaces_path, char* out_mac, size_t out_size) {
char* content = NULL; size_t len = 0;
if (read_file(interfaces_path, &content, &len) != 0 || !content) {
return -1;
}
const char* needle = "hwaddress ether";
char* pos = strstr(content, needle);
if (!pos) { free(content); return -1; }
pos += strlen(needle);
while (*pos == ' ') ++pos;
size_t j = 0;
while (*pos && *pos != '\n' && j < out_size - 1) {
if (*pos == ' ' || *pos == '\r' || *pos == '\t') break;
out_mac[j++] = *pos++;
}
out_mac[j] = '\0';
free(content);
return j > 0 ? 0 : -1;
}
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 j > 0 ? 0 : -1;
}
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 >= 3) ? argv[2] : "/etc/network/interfaces";
if (argc < 2) {
// No scan input provided: check current MAC and if default, loop until scanned and updated
char current_mac[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, current_mac, sizeof(current_mac)) != 0) {
fprintf(stderr, "无法读取 %s 中的MAC\n", interfaces_path);
return 1;
}
to_upper_str(current_mac);
if (strcmp(current_mac, "90:A9:F7:30:00:00") == 0) {
fprintf(stdout, "检测到默认MAC %s开始等待扫码...\n", current_mac);
start_yellow_blink();
while (1) {
char scanbuf[512] = {0};
if (read_qrcode_string(scanbuf, sizeof(scanbuf)) != 0) {
fprintf(stderr, "读取扫码内容失败,重试...\n");
continue;
}
fprintf(stdout, "扫码原始内容: %s\n", scanbuf);
// 优先按批次从Redis查询目标MAC
char batch[128] = {0};
char mac_from_redis[32] = {0};
if (extract_batch(scanbuf, batch, sizeof(batch)) == 0) {
fprintf(stdout, "识别到批次号: %s正在查询Redis...\n", batch);
if (redis_query_mac(NULL, NULL, NULL, NULL, batch, mac_from_redis, sizeof(mac_from_redis)) == 0) {
fprintf(stdout, "Redis返回MAC: %s\n", mac_from_redis);
to_upper_str(mac_from_redis);
if (strcmp(mac_from_redis, "90:A9:F7:30:00:00") == 0) {
fprintf(stderr, "Redis返回为默认MAC请确认并重新扫码...\n");
continue;
}
if (update_hwaddress_in_interfaces(interfaces_path, mac_from_redis) == 0) {
stop_yellow_blink();
fprintf(stdout, "已更新MAC为: %s\n", mac_from_redis);
// 记录审计批次、MAC、时间
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac_from_redis, "auto-loop");
sync();
char verify_mac[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, verify_mac, sizeof(verify_mac)) == 0) {
to_upper_str(verify_mac);
if (strcmp(verify_mac, "90:A9:F7:30:00:00") != 0) {
set_yellow_on();
fprintf(stdout, "黄灯已常亮MAC非默认\n");
} else {
fprintf(stderr, "写入后仍为默认MAC黄灯保持关闭或闪烁。\n");
}
}
break;
} else {
fprintf(stderr, "写入失败,重试...\n");
continue;
}
} else {
// Redis失败时若仅有批次号且不含显式MAC不再回退使用批次内容当MAC
if (!has_explicit_mac_pattern(scanbuf)) {
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败且扫码内容不含显式MAC如 12:34:56:78:9A:BC请重新扫码或确保Redis可用。\n");
continue;
}
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败回退使用扫码中的MAC...\n");
}
}
// 若未识别到批次号,给出提示
if (extract_batch(scanbuf, batch, sizeof(batch)) != 0) {
fprintf(stderr, "未识别到批次号回退使用扫码中的MAC...\n");
}
// 回退直接从扫码内容提取MAC此处允许无分隔的12位HEX
char mac_colon[32] = {0};
if (extract_mac("90:A9:F7:30:00:00", mac_colon, sizeof(mac_colon)) != 0) {
fprintf(stderr, "未识别到有效MAC重试...\n");
continue;
}
to_upper_str(mac_colon);
if (strcmp(mac_colon, "90:A9:F7:30:00:00") == 0) {
fprintf(stderr, "扫描到默认MAC请重新扫码...\n");
continue;
}
if (update_hwaddress_in_interfaces(interfaces_path, mac_colon) == 0) {
set_yellow_on();
fprintf(stdout, "已更新MAC为: %s\n", mac_colon);
break;
} else {
fprintf(stderr, "写入失败,重试...\n");
}
}
return 0;
} else {
// 若非默认MAC确保黄灯关闭
set_yellow_on();
fprintf(stdout, "当前MAC非默认(%s),无需更新。\n", current_mac);
return 0;
}
}
// Legacy path: user provides scan string directly
fprintf(stderr, "Example: %s 'batch=202501;MAC=90A9F7F032E8' ./interfaces\n", argv[0]);
const char* scan = argv[1];
// 先按批次从Redis查询
char batch[128] = {0};
char mac_from_redis[32] = {0};
fprintf(stdout, "扫码原始内容: %s\n", scan);
if (extract_batch(scan, batch, sizeof(batch)) == 0) {
fprintf(stdout, "识别到批次号: %s正在查询Redis...\n", batch);
if (redis_query_mac(NULL, NULL, NULL, NULL, batch, mac_from_redis, sizeof(mac_from_redis)) == 0) {
fprintf(stdout, "Redis返回MAC: %s\n", mac_from_redis);
if (update_hwaddress_in_interfaces(interfaces_path, mac_from_redis) != 0) {
return 3;
}
// 记录审计批次、MAC、时间
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac_from_redis, "legacy");
char verify_mac[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, verify_mac, sizeof(verify_mac)) == 0) {
to_upper_str(verify_mac);
if (strcmp(verify_mac, "90:A9:F7:30:00:00") != 0) {
set_yellow_on();
fprintf(stdout, "黄灯已常亮MAC非默认\n");
} else {
fprintf(stderr, "写入后仍为默认MAC黄灯保持关闭或闪烁。\n");
}
}
return 0;
} else {
if (!has_explicit_mac_pattern(scan)) {
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败且扫码内容不含显式MAC如 12:34:56:78:9A:BC请重新扫码或确保Redis可用。\n");
return 2;
}
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败回退使用扫码中的MAC...\n");
}
}
// 若未识别到批次号,给出提示
if (extract_batch(scan, batch, sizeof(batch)) != 0) {
fprintf(stderr, "未识别到批次号回退使用扫码中的MAC...\n");
}
char mac_colon[32] = {0};
if (extract_mac(scan, mac_colon, sizeof(mac_colon)) != 0) {
fprintf(stderr, "Failed to extract MAC from input: %s\n", scan);
return 2;
}
if (update_hwaddress_in_interfaces(interfaces_path, mac_colon) != 0) {
return 3;
}
return 0;
}
static volatile int led_running = 0;
static pthread_t led_thread;
static void gpio_init_yellow(void) {
int fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd >= 0) {
write(fd, "113", 3); // TODO 注意改版对应脚名称
close(fd);
}
fd = open("/sys/class/gpio/gpio113/direction", O_WRONLY); // TODO 注意改版对应脚名称
if (fd >= 0) {
write(fd, "out", 3);
close(fd);
}
}
static void* led_blink_thread(void* arg) {
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
if (fd < 0) return NULL;
while (led_running) {
write(fd, "1", 1);
usleep(200000);
write(fd, "0", 1);
usleep(200000);
}
// ensure off
write(fd, "0", 1);
close(fd);
return NULL;
}
static void start_yellow_blink(void) {
if (led_running) return;
gpio_init_yellow();
led_running = 1;
pthread_create(&led_thread, NULL, led_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_on(void) {
// 确保不再闪烁
stop_yellow_blink();
// 初始化GPIO并置为高电平
gpio_init_yellow();
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
if (fd < 0) return;
write(fd, "1", 1);
close(fd);
}