Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/netmgr_ipc.c
T
MeowOS 3b6a73ac10 feat(launcher, NETMGR_DESIGN §12): 设置页 WiFi/蓝牙子页迁 netmgr IPC + WiFi 新网络录入 UI
- wifi_page/bt_page 查询/操作改走 netmgrd IPC(netmgr_ipc.c 客户端:
  socket /usr/data/meow_net.sock,HELLO 鉴权 → WIFI_CTL/BT_CTL → RESULT)。
- WiFi 新网络录入:扫描结果点选 →(加密网络)SDK IME 输密码 → connect →
  结果反馈 → 刷新已存网络;开放网络直连;当前连接详情含断开。
- 双轨过渡:netmgrd 不可达(NETMGR_UNREACH)回退 net.c 直连 wpa_cli/hciconfig
  (保功能),页面显示「网络管理服务未运行(直连模式)」降级占位(不崩)。
- BT 仅迁 on/off/status/paired(netmgrd BT_CTL 无连接/配对子命令,其余直连)。
- net.c net_wifi_scan 补 sec 字段(wifi_page.c 补 meow_net_t.sec)。
- 测试:+test_netmgr_ipc.c(假 socket 帧:扫描渲染/连接成功失败两态/降级占位)。
2026-09-01 22:09:31 +08:00

506 lines
20 KiB
C
Raw 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.
/* netmgr_ipc.c — MeowOS launcher → meow_netmgrd IPC 客户端(M6.0 设置页迁移,NETMGR_DESIGN §12)。
*
* 【迁移注释】wifi_page/bt_page 的 WiFi/BT 查询与操作统一走 netmgrd IPC(socket
* /usr/data/meow_net.sock,env MEOW_NET_SOCK 覆盖)。netmgrd 是 WiFi/BT 操作权威
* (IPC 0xE4 WIFI_CTL / 0xE6 BT_CTL);本文件是「设置页客户端 → 权威 IPC」的唯一入口。
* 【双轨过渡】netmgrd 不可达(NETMGR_UNREACH)时,本文件回退到 net.c 既有的直连
* wpa_cli/hciconfig/busybox 读取(保功能);状态栏物理键语义、sdk/amsd/netmgrd 本体不动。
*
* 鉴权:跟 pm_power_ipc 同款 —— 先 MSG_HELLO(app_id=os.meow.launcher,netmgrd 按
* SO_PEERCRED 认出 /usr/bin/meow_launcherd = 系统身份,写级放行),再发 CTL,收 RESULT。
* 只读观测(wlan_connected()/hci_powered() 读 /sys 链路态)仍在 net.c;此处只接权威操作。
*/
#include "meow_launcher_internal.h"
#define NETMGR_SOCK_DEFAULT "/usr/data/meow_net.sock"
/* NETMGR_UNREACH 定义在 meow_launcher_internal.h(测试/调用方共用) */
/* 可达性缓存:netmgrd 不可达时每次 connect 都秒回 ECONNREFUSED,无成本;缓存约 1s 防热循环。 */
static int g_nm_ok = -1; /* -1 未知 / 0 down / 1 up */
static time_t g_nm_ok_ts = 0;
static void nm_cache(int ok) { g_nm_ok = ok; g_nm_ok_ts = time(NULL); }
/* ---- socket 连接 ---- */
static int nm_sock(void)
{
const char *sock = getenv("MEOW_NET_SOCK");
if (!sock || !sock[0]) sock = NETMGR_SOCK_DEFAULT;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, sock, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
return fd;
}
/* ---- 帧收发(复刻 power_menu.c pw_xchg:读超时 1.5s,绝不让子页主循环卡死)---- */
static int nm_w_all(int fd, const uint8_t *b, size_t n)
{
size_t off = 0;
while (off < n) { ssize_t w = write(fd, b + off, n - off); if (w <= 0) return -1; off += (size_t)w; }
return 0;
}
static int nm_r_all(int fd, uint8_t *b, size_t n)
{
size_t off = 0;
while (off < n) { ssize_t r = read(fd, b + off, n - off); if (r <= 0) return -1; off += (size_t)r; }
return 0;
}
static int nm_xchg(int fd, uint16_t type, uint32_t req, const void *pl, uint32_t len,
uint8_t *rbuf, uint32_t rcap, uint16_t *rtype)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC, rlen = len;
uint16_t ver = MEOW_IPC_VERSION, t = type;
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
memcpy(hdr + 8, &req, 4); memcpy(hdr + 12, &rlen, 4);
if (nm_w_all(fd, hdr, sizeof hdr) < 0) return -1;
if (len && pl && nm_w_all(fd, pl, len) < 0) return -1;
struct pollfd pf = { fd, POLLIN, 0 };
int pr = poll(&pf, 1, 1500);
if (pr <= 0) return -1;
uint8_t rhdr[MEOW_IPC_HDR_LEN];
if (nm_r_all(fd, rhdr, sizeof rhdr) < 0) return -1;
uint32_t rmagic, rlen2; uint16_t rver, rt;
memcpy(&rmagic, rhdr, 4); memcpy(&rver, rhdr + 4, 2); memcpy(&rt, rhdr + 6, 2);
memcpy(&rlen2, rhdr + 12, 4);
if (rmagic != MEOW_IPC_MAGIC || rver != MEOW_IPC_VERSION) return -1;
if (rlen2 > rcap) rlen2 = rcap;
if (rlen2 && nm_r_all(fd, rbuf, rlen2) < 0) return -1;
if (rtype) *rtype = rt;
return 0;
}
static int nm_hello(int fd)
{
m_hello_t h;
memset(&h, 0, sizeof h);
snprintf(h.app_id, sizeof h.app_id, "%s", "os.meow.launcher");
h.version = 1;
uint8_t rbuf[256]; uint16_t rtype = 0;
if (nm_xchg(fd, MSG_HELLO, 0x100, &h, sizeof h, rbuf, sizeof rbuf, &rtype) < 0 || rtype != MSG_ACK)
return -1;
m_ack_t ak; memcpy(&ak, rbuf, sizeof ak);
return (ak.code == MEOW_OK) ? 0 : -1;
}
/* 发一条 WIFI_CTL/BT_CTL 子命令,收 RESULT。返回 netmgrd 业务 code;NETMGR_UNREACH=不可达。 */
static int nm_cmd(uint16_t ctl_type, uint16_t res_type, const char *cmd, const char *arg,
char *text, int tlen)
{
m_net_cmd_t q;
memset(&q, 0, sizeof q);
snprintf(q.cmd, sizeof q.cmd, "%s", cmd ? cmd : "");
if (arg) snprintf(q.arg, sizeof q.arg, "%s", arg);
int fd = nm_sock();
if (fd < 0) { nm_cache(0); return NETMGR_UNREACH; }
if (nm_hello(fd) < 0) { close(fd); nm_cache(0); return NETMGR_UNREACH; }
uint8_t rbuf[MEOW_IPC_MAX]; uint16_t rtype = 0;
memset(rbuf, 0, sizeof rbuf);
if (nm_xchg(fd, ctl_type, 0x101, &q, sizeof q, rbuf, sizeof rbuf, &rtype) < 0 || rtype != res_type) {
close(fd); nm_cache(0); return NETMGR_UNREACH;
}
m_net_cmd_result_t res; memcpy(&res, rbuf, sizeof res);
close(fd);
nm_cache(1);
uint32_t tl = res.text_len;
if (tl >= (uint32_t)tlen) tl = (uint32_t)(tlen - 1);
if (text && tlen > 0) { memcpy(text, res.text, tl); text[tl] = 0; }
return (int)res.code;
}
int netmgr_wifi(const char *cmd, const char *arg, char *text, int tlen)
{ return nm_cmd(MSG_NET_WIFI_CTL, MSG_NET_WIFI_RESULT, cmd, arg, text, tlen); }
int netmgr_bt(const char *cmd, const char *arg, char *text, int tlen)
{ return nm_cmd(MSG_NET_BT_CTL, MSG_NET_BT_RESULT, cmd, arg, text, tlen); }
int netmgr_ok(void)
{
if (g_nm_ok >= 0 && time(NULL) - g_nm_ok_ts < 1) return g_nm_ok;
int fd = nm_sock();
if (fd < 0) { nm_cache(0); return 0; }
close(fd); nm_cache(1); return 1;
}
/* ================= WiFi(IPC 优先,net.c 直连回退)================= */
/* —— net.c 的 wpa_decode_ssid 是 static,这里复制最小实现(本模块自含,避免改 net.c 可见性)—— */
static int nm_hex(char c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static void nm_decode_ssid(const char *src, char *dst, int dlen)
{
int o = 0;
while (*src && o < dlen - 1) {
if (src[0] == '\\' && src[1] == 'x' && nm_hex(src[2]) >= 0 && nm_hex(src[3]) >= 0) {
dst[o++] = (char)((nm_hex(src[2]) << 4) | nm_hex(src[3]));
src += 4;
} else {
dst[o++] = *src++;
}
}
dst[o] = 0;
}
/* —— netmgrd `wifi status` 单行摘要解析:`wifi state=X connected=N ssid=S ip=I rssi=R`。
* 已知字段以「空格+key=」分隔;ssid 可能含空格,需读到下一个已知字段为止。 —— */
static int nm_is_status_key(const char *p)
{
if (!strncmp(p, " ip=", 4) || !strncmp(p, " rssi=", 6) ||
!strncmp(p, " connected=", 11) || !strncmp(p, " state=", 7)) return 1;
return 0;
}
static const char *nm_val(const char *text, const char *key, char *dst, int dlen, int space_ok)
{
const char *p = strstr(text, key);
if (!p) { dst[0] = 0; return p; }
p += strlen(key);
int o = 0;
while (*p && o < dlen - 1) {
if (*p == ' ') {
if (!space_ok) break;
if (nm_is_status_key(p)) break; /* ssid 含空格:停在下一个已知字段 */
}
dst[o++] = *p++;
}
dst[o] = 0;
return p;
}
static int nm_parse_connected(const char *text)
{
char v[8];
nm_val(text, "connected=", v, sizeof v, 0);
return atoi(v);
}
int nm_wifi_connected(void)
{
char text[256];
int rc = netmgr_wifi("status", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) return wlan_connected();
if (rc != MEOW_OK) return 0;
return nm_parse_connected(text);
}
void nm_wifi_ssid(char *buf, int len)
{
buf[0] = 0;
char text[256];
int rc = netmgr_wifi("status", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) { net_wifi_ssid(buf, len); return; }
if (rc != MEOW_OK) return;
if (!nm_parse_connected(text)) return;
char raw[128];
nm_val(text, "ssid=", raw, sizeof raw, 1);
nm_decode_ssid(raw, buf, len);
}
int nm_wifi_info(char (*info)[48], int max)
{
char text[256];
int rc = netmgr_wifi("status", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) return net_wifi_info(info, max); /* 回退:完整详情(直连 wpa_cli+ip route) */
if (rc != MEOW_OK || !nm_parse_connected(text)) return 0;
char ssid[40] = "", ip[32] = "", rssi[16] = "";
char raw[128];
nm_val(text, "ssid=", raw, sizeof raw, 1);
nm_decode_ssid(raw, ssid, sizeof ssid);
nm_val(text, "ip=", ip, sizeof ip, 0);
nm_val(text, "rssi=", rssi, sizeof rssi, 0);
int n = 0;
if (ssid[0]) snprintf(info[n++], 48, "SSID: %s", ssid);
if (ip[0]) snprintf(info[n++], 48, "本机IP: %s", ip);
if (rssi[0]) snprintf(info[n++], 48, "信号: %s dBm", rssi);
if (n == 0) snprintf(info[n++], 48, "已连接(无详情)");
return n;
}
/* —— 解析 netmgrd `wifi networks`(wpa_cli list_networks 原样文本)→ meow_net_t[];
* 直接复用 net.c 既有表头跳行逻辑。返回项数。 —— */
static int nm_parse_saved(const char *txt, meow_net_t *out, int max)
{
int n = 0;
if (!txt || max <= 0) return 0;
if (max > S_NET_MAX) max = S_NET_MAX;
const char *line = strchr(txt, '\n');
if (line) line++;
while (line && *line && n < max) {
const char *eol = strchr(line, '\n');
const char *p = line; while (*p == ' ' || *p == '\t') p++;
if (*p && *p != '#') {
int id = atoi(p);
const char *sp = p; while (*sp && *sp != ' ' && *sp != '\t') sp++;
while (*sp == ' ' || *sp == '\t') sp++;
const char *se = sp; while (*se && *se != ' ' && *se != '\t' && *se != '\n' && *se != '\r') se++;
const char *bp = se; while (*bp == ' ' || *bp == '\t') bp++;
const char *be = bp; while (*be && *be != ' ' && *be != '\t' && *be != '\n' && *be != '\r') be++;
if (sp != se) {
char raw[160];
int l = (int)(se - sp);
if (l > (int)sizeof raw - 1) l = (int)sizeof raw - 1;
memcpy(raw, sp, (size_t)l); raw[l] = 0;
out[n].id = id;
nm_decode_ssid(raw, out[n].name, sizeof out[n].name);
if (be > bp && (be - bp) < (int)sizeof out[n].mac) {
memcpy(out[n].mac, bp, (size_t)(be - bp)); out[n].mac[be - bp] = 0;
}
out[n].sec[0] = 0;
n++;
}
}
line = eol ? eol + 1 : NULL;
}
return n;
}
/* —— 解析 netmgrd `wifi scan`(wpa_cli scan_results 原样文本)→ meow_net_t[](按 BSSID 去重、
* 优先具名;补充 sec 安全类型供「新网络录入」判断是否弹 IME)。返回项数。 —— */
static int nm_parse_scan(const char *txt, meow_net_t *out, int max)
{
int n = 0;
if (!txt || max <= 0) return 0;
if (max > S_NET_MAX) max = S_NET_MAX;
char seen_bssid[S_NET_MAX][24];
int named[S_NET_MAX] = {0};
const char *line = strchr(txt, '\n');
if (line) line++;
while (line && *line && n < max) {
const char *eol = strchr(line, '\n');
const char *p = line; while (*p == ' ' || *p == '\t') p++;
if (*p) {
char bssid[24] = "";
{ const char *b = p; while (*p && *p != ' ' && *p != '\t') p++;
int l = (int)(p - b); if (l > 22) l = 22; memcpy(bssid, b, (size_t)l); bssid[l] = 0; }
const char *ssid = "";
char sec[16] = "Open";
const char *br = strchr(p, '[');
if (br) {
/* 安全类型 = 首个方括号内容([WPA-PSK-CCMP] 等);纯 [ESS]/[IBSS] = 开放 */
const char *be = strchr(br + 1, ']');
if (be) {
int sl = (int)(be - (br + 1));
if (sl > 15) sl = 15;
if (sl > 0) {
memcpy(sec, br + 1, (size_t)sl); sec[sl] = 0;
if (!strstr(sec, "WPA") && !strstr(sec, "RSN") &&
!strstr(sec, "WEP") && !strstr(sec, "SAE") && !strstr(sec, "EAP"))
snprintf(sec, sizeof sec, "Open");
}
}
const char *q = br;
while (*q) {
while (*q == ' ' || *q == '\t') q++;
if (*q == '[') { while (*q && *q != ' ' && *q != '\t') q++; }
else break;
}
ssid = q; while (*ssid == ' ' || *ssid == '\t') ssid++;
}
const char *se = ssid;
while (*se && *se != '\n' && *se != '\r') se++;
while (se > ssid && (se[-1] == ' ' || se[-1] == '\t')) se--;
int has = (se > ssid && *ssid);
int idx = -1;
for (int j = 0; j < n; j++) if (!strcmp(seen_bssid[j], bssid)) { idx = j; break; }
if (idx < 0) {
if (n >= max) { line = eol ? eol + 1 : NULL; continue; }
idx = n++;
strncpy(seen_bssid[idx], bssid, sizeof seen_bssid[idx] - 1);
seen_bssid[idx][sizeof seen_bssid[idx] - 1] = 0;
named[idx] = has;
out[idx].id = -1;
snprintf(out[idx].mac, sizeof out[idx].mac, "%s", bssid);
snprintf(out[idx].sec, sizeof out[idx].sec, "%s", sec);
if (has) {
char raw[96];
int l = (int)(se - ssid); if (l > (int)sizeof raw - 1) l = (int)sizeof raw - 1;
memcpy(raw, ssid, (size_t)l); raw[l] = 0;
nm_decode_ssid(raw, out[idx].name, sizeof out[idx].name);
} else {
snprintf(out[idx].name, sizeof out[idx].name, "(隐藏网络)");
}
} else if (has && !named[idx]) {
char raw[96];
int l = (int)(se - ssid); if (l > (int)sizeof raw - 1) l = (int)sizeof raw - 1;
memcpy(raw, ssid, (size_t)l); raw[l] = 0;
nm_decode_ssid(raw, out[idx].name, sizeof out[idx].name);
named[idx] = 1;
}
}
line = eol ? eol + 1 : NULL;
}
return n;
}
int nm_wifi_saved(meow_net_t *out, int max)
{
char text[MEOW_IPC_MAX - 32];
int rc = netmgr_wifi("networks", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) return net_wifi_saved(out, max); /* 直连回退 */
if (rc != MEOW_OK) return 0;
return nm_parse_saved(text, out, max);
}
int nm_wifi_scan(meow_net_t *out, int max)
{
char text[MEOW_IPC_MAX - 32];
int rc = netmgr_wifi("scan", NULL, text, sizeof text); /* netmgrd 内含触发+有界等待+读结果 */
if (rc == NETMGR_UNREACH) return net_wifi_scan(out, max);
if (rc != MEOW_OK) return 0;
return nm_parse_scan(text, out, max);
}
void nm_wifi_scan_trigger(void)
{
/* IPC:scan 子命令在 netmgrd 内已一次性「触发+等待+读结果」,此处无需单独触发;
* 直连回退:异步 wpa_cli scan(结果稍后 net_wifi_scan 读)。 */
if (!netmgr_ok()) net_wifi_scan_trigger();
}
/* —— 直连回退:wpa_cli add/set/enable/select 新网络(复刻 netmgrd wifi_connect 逻辑)—— */
static int nm_direct_connect(const char *ssid, const char *psk, char *err, int elen)
{
const char *wpa = "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0";
char cmd[256];
snprintf(cmd, sizeof cmd, "%s add_network 2>/dev/null", wpa);
char *o = popen_read_tmo(1200, cmd);
int id = o ? atoi(o) : -1;
if (o) free(o);
if (id < 0) { if (err) snprintf(err, elen, "add_network 无 id"); return MEOW_ERR_INVAL; }
{
char *r;
snprintf(cmd, sizeof cmd, "%s set_network %d ssid '\"%s\"' 2>/dev/null", wpa, id, ssid);
if ((r = popen_read_tmo(1200, cmd))) free(r);
if (psk && psk[0]) {
snprintf(cmd, sizeof cmd, "%s set_network %d psk '\"%s\"' 2>/dev/null", wpa, id, psk);
if ((r = popen_read_tmo(1200, cmd))) free(r);
} else {
snprintf(cmd, sizeof cmd, "%s set_network %d key_mgmt NONE 2>/dev/null", wpa, id);
if ((r = popen_read_tmo(1200, cmd))) free(r);
}
snprintf(cmd, sizeof cmd, "%s enable_network %d 2>/dev/null", wpa, id);
if ((r = popen_read_tmo(1200, cmd))) free(r);
snprintf(cmd, sizeof cmd, "%s select_network %d 2>/dev/null", wpa, id);
if ((r = popen_read_tmo(1200, cmd))) free(r);
}
if (err) snprintf(err, elen, "connect: ssid=%s network_id=%d", ssid, id);
logmsg("netmgr_ipc: [direct-fallback] wifi connect ssid=%s id=%d", ssid, id);
return MEOW_OK;
}
int nm_wifi_connect(const char *ssid, const char *psk, char *err, int elen)
{
if (!ssid || !ssid[0]) { if (err) snprintf(err, elen, "ssid 缺失"); return MEOW_ERR_INVAL; }
char arg[132];
snprintf(arg, sizeof arg, "%s,%s", ssid, psk ? psk : ""); /* connect 带空 psk → netmgrd 复用既有网络 */
char text[192];
int rc = netmgr_wifi("connect", arg, text, sizeof text);
if (rc == NETMGR_UNREACH) return nm_direct_connect(ssid, psk, err, elen);
if (err && elen > 0) snprintf(err, elen, "%s", text);
return rc;
}
int nm_wifi_disconnect(char *err, int elen)
{
char text[128];
int rc = netmgr_wifi("disconnect", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) {
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli -i wlan0 disconnect 2>/dev/null");
popen_read_tmo(1200, cmd);
if (err) snprintf(err, elen, "已下发");
return MEOW_OK;
}
if (err && elen > 0) snprintf(err, elen, "%s", text);
return rc;
}
int nm_wifi_toggle(int on)
{
char text[128];
int rc = netmgr_wifi(on ? "on" : "off", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) { s_net_toggle_wifi(on); return MEOW_OK; } /* 直连回退 */
return rc;
}
/* ================= BT(IPC 优先:on/off/status/paired;其余子命令 netmgrd 无,直连)================= */
int nm_bt_powered(void)
{
char text[128];
int rc = netmgr_bt("status", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) return hci_powered();
if (rc != MEOW_OK) return 0;
char v[8];
nm_val(text, "powered=", v, sizeof v, 0);
return atoi(v);
}
int nm_bt_onoff(int on)
{
char text[128];
int rc = netmgr_bt(on ? "on" : "off", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) { s_net_toggle_bt(on); return MEOW_OK; }
return rc;
}
int nm_bt_status(int *powered, int *paired_n, char *addr, int alen)
{
char text[192];
int rc = netmgr_bt("status", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) { /* 直连回退:读内核/tool */
if (powered) *powered = hci_powered();
if (paired_n) *paired_n = -1;
if (addr && alen > 0) addr[0] = 0;
return rc;
}
if (rc == MEOW_OK) {
char v[8];
if (powered) { nm_val(text, "powered=", v, sizeof v, 0); *powered = atoi(v); }
if (paired_n) { nm_val(text, "paired=", v, sizeof v, 0); *paired_n = atoi(v); }
if (addr && alen > 0) nm_val(text, "address=", addr, alen, 0);
}
return rc;
}
int nm_bt_paired(meow_net_t *out, int max)
{
char text[MEOW_IPC_MAX - 32];
int rc = netmgr_bt("paired", NULL, text, sizeof text);
if (rc == NETMGR_UNREACH) return net_bt_paired(out, max);
if (rc != MEOW_OK) return 0;
int n = 0;
if (max > S_NET_MAX) max = S_NET_MAX;
if (text[0]) {
char *line = text;
while (line && *line && n < max) {
char *eol = strchr(line, '\n');
if (eol) *eol = 0;
char *p = strstr(line, "Device ");
if (p) {
p += 7;
char *mac = p; while (*mac && *mac != ' ' && *mac != '\t') mac++;
char *nm2 = mac; while (*nm2 == ' ' || *nm2 == '\t') nm2++;
int ml = (int)(mac - p); if (ml > 47) ml = 47;
if (ml > 0 && ml < (int)sizeof out[n].mac) {
memcpy(out[n].mac, p, (size_t)ml); out[n].mac[ml] = 0;
}
snprintf(out[n].name, sizeof out[n].name, "%s", *nm2 ? nm2 : p);
out[n].id = -1; out[n].sec[0] = 0;
n++;
}
line = eol ? eol + 1 : NULL;
}
}
return n;
}