2025-11-06 02:32:18 +00:00
|
|
|
|
#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>
|
2025-11-06 02:53:53 +00:00
|
|
|
|
#include <time.h>
|
2025-11-06 02:32:18 +00:00
|
|
|
|
#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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 02:53:53 +00:00
|
|
|
|
// 审计:在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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 02:32:18 +00:00
|
|
|
|
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);
|
2025-11-06 02:53:53 +00:00
|
|
|
|
// 记录审计:批次、MAC、时间
|
|
|
|
|
|
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac_from_redis, "auto-loop");
|
2025-11-06 02:32:18 +00:00
|
|
|
|
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};
|
2025-11-13 01:44:19 +00:00
|
|
|
|
if (extract_mac("90:A9:F7:30:00:00", mac_colon, sizeof(mac_colon)) != 0) {
|
2025-11-06 02:32:18 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-11-06 02:53:53 +00:00
|
|
|
|
// 记录审计:批次、MAC、时间
|
|
|
|
|
|
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac_from_redis, "legacy");
|
2025-11-06 02:32:18 +00:00
|
|
|
|
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) {
|
2025-11-06 03:05:38 +00:00
|
|
|
|
write(fd, "113", 3); // TODO 注意改版对应脚名称
|
2025-11-06 02:32:18 +00:00
|
|
|
|
close(fd);
|
|
|
|
|
|
}
|
2025-11-06 03:05:38 +00:00
|
|
|
|
fd = open("/sys/class/gpio/gpio113/direction", O_WRONLY); // TODO 注意改版对应脚名称
|
2025-11-06 02:32:18 +00:00
|
|
|
|
if (fd >= 0) {
|
|
|
|
|
|
write(fd, "out", 3);
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void* led_blink_thread(void* arg) {
|
2025-11-06 03:05:38 +00:00
|
|
|
|
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
|
2025-11-06 02:32:18 +00:00
|
|
|
|
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();
|
2025-11-06 03:05:38 +00:00
|
|
|
|
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
|
2025-11-06 02:32:18 +00:00
|
|
|
|
if (fd < 0) return;
|
|
|
|
|
|
write(fd, "1", 1);
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
}
|