From 79a0fed336d81321e85cb3cda7f072c8ac07111f Mon Sep 17 00:00:00 2001 From: MeowOS Date: Tue, 1 Sep 2026 19:55:26 +0800 Subject: [PATCH] =?UTF-8?q?meow=5Fnetmgrd=20=E6=9C=9F1=EF=BC=9A=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E7=AE=A1=E7=90=86=E6=9C=8D=E5=8A=A1=E9=AA=A8=E6=9E=B6?= =?UTF-8?q?+=E6=8E=A5=E5=8F=A3=E7=BB=9F=E4=B8=80=E5=B1=82+CLI+IPC+?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NETMGR_DESIGN.md 期1范围(→ M6.0)落地: - meow_netmgrd 守护:svcd 托管 trigger=boot/restart=on-fail,进程内 HibyOS 只读降级 (/tmp/meow_boot_mode != meow → 读可查/写拒+日志,热切换)。 - 接口统一层(§3):iface 枚举(/sys/class/net+/proc/net/dev,lo/wlan0/usb-eth 热插拔节拍检测); WiFi 状态/扫描/网络列表/连接/断开(wpa_cli popen 有界收割防挂死,257 教训);IP/路由/DNS 只读 (ip 优先 /proc/net/route+resolv.conf);BT 状态/开关/已配对(hciconfig/bluetoothctl);rfkill 读写;udhcpc 续租/释放(MeowOS)。 - IPC 0x00E0-0x00E7(STATUS/IFACE/WIFI_CTL/BT_CTL+各自 RESULT):读级任意、写级仅系统身份(SO_PEERCRED 同 svc_power),0xE8-0xEF 期2/3 预留(仅注释)。 - CLI 双形态 meow_net(status/iface/wifi/bt/rfkill/help):IPC 薄壳,help 本地必可用。 - 日志框架:TF MeowOS/logs/net_.log(无卡降级 /tmp),接口状态变化/写操作必记。 - 单线程 poll、静态表、热路径禁 malloc;mipsel 静态(0 warning),宿主+qemu-mipsel 全过, 真机只读验证 status/wifi scan/bt status 正确。 meow_ipc.h 仅追加 0x00E0-0x00E7 段(不改既有行)。 --- patch_v2/meow_launcherd/meow_ipc.h | 60 + patch_v2/meow_netmgrd/build_netmgrd.sh | 33 + patch_v2/meow_netmgrd/meow_netmgrd | Bin 0 -> 152892 bytes patch_v2/meow_netmgrd/meow_netmgrd.c | 1756 ++++++++++++++++++++++ patch_v2/meow_netmgrd/meow_netmgrd.conf | 16 + patch_v2/meow_netmgrd/meow_netmgrd_host | Bin 0 -> 77656 bytes patch_v2/meow_netmgrd/netmgrd_authtest | Bin 0 -> 144816 bytes patch_v2/meow_netmgrd/netmgrd_authtest.c | 218 +++ patch_v2/meow_netmgrd/test_netmgrd.sh | 164 ++ 9 files changed, 2247 insertions(+) create mode 100755 patch_v2/meow_netmgrd/build_netmgrd.sh create mode 100755 patch_v2/meow_netmgrd/meow_netmgrd create mode 100644 patch_v2/meow_netmgrd/meow_netmgrd.c create mode 100644 patch_v2/meow_netmgrd/meow_netmgrd.conf create mode 100755 patch_v2/meow_netmgrd/meow_netmgrd_host create mode 100755 patch_v2/meow_netmgrd/netmgrd_authtest create mode 100644 patch_v2/meow_netmgrd/netmgrd_authtest.c create mode 100755 patch_v2/meow_netmgrd/test_netmgrd.sh diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index e9172a9..9f673c6 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -504,4 +504,64 @@ typedef struct m_audio_volume { int32_t pct; /* -1=查询;0-100=设置 */ } m_audio_volume_t; /* 4 B */ +/* ================= meow_netmgrd 网络管理服务(devdocs/plan/NETMGR_DESIGN.md §3/§10)===== + * 语义:接口统一层(WiFi/BT/USB-eth/rfkill/IP/路由/DNS 只读)查询与控制的系统权威。 + * 独立 socket /usr/data/meow_net.sock(--sock/MEOW_NET_SOCK 覆盖)。进程形态对齐 powermgr: + * svcd 托管 trigger=boot + 单一二进制双形态(/usr/bin/meow_netmgrd 守护 + /usr/bin/meow_net CLI)。 + * 鉴权分级同 svc_power(§10/期1):读级(STATUS/IFACE、WIFI_CTL 内 status/networks、BT_CTL 内 + * status/paired)任意调用者;写级(WIFI_CTL 内 scan/connect/disconnect/on/off/rfkill_set/udhcpc、 + * BT_CTL 内 on/off)仅系统身份(SO_PEERCRED → /usr/bin/meow_* 或 os.meow.* 包)。 + * HibyOS 模式(/tmp/meow_boot_mode=hiby):读级可查,写级拒 + 记日志。 + * 期 1 实装 0x00E0-0x00E7;0x00E8-0x00EF 保留(期2:连接普查/DNS 代理;期3:管控策略/事件推送)。 */ +enum { /* 与主 enum 分列,追加式不改既有行 */ + MSG_NET_STATUS_REQ = 0x00E0, /* ↑ 概览查询(空负载=全部) */ + MSG_NET_STATUS_RESULT = 0x00E1, /* ↓ 概览 JSON */ + MSG_NET_IFACE_REQ = 0x00E2, /* ↑ 接口列表/详情(name 空=全部) */ + MSG_NET_IFACE_RESULT = 0x00E3, /* ↓ 接口 JSON */ + MSG_NET_WIFI_CTL = 0x00E4, /* ↑ 子命令字串(scan/connect/... + rfkill/udhcpc) */ + MSG_NET_WIFI_RESULT = 0x00E5, /* ↓ 结果/文本 */ + MSG_NET_BT_CTL = 0x00E6, /* ↑ 子命令字串(status/on/off/paired) */ + MSG_NET_BT_RESULT = 0x00E7 /* ↓ 结果/文本 */ + /* 0x00E8-0x00EF 保留:0xE8 连接表 / 0xEA 策略读写 / 0xEC DNS 控制 / 0xEE 事件推送(期 2/3) */ +}; + +/* MSG_NET_STATUS_REQ ↑(空负载=全部;预留 subset 位,期 1 恒 0)→ MSG_NET_STATUS_RESULT ↓: + * 概览 JSON(mode/ifaces[]/wifi/bt/rfkill[],见 NETMGR_DESIGN §3 统一模型)。*/ +typedef struct m_net_status_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint16_t json_len; /* json 有效字节(不含 NUL) */ + uint16_t pad; + char json[MEOW_IPC_MAX - 8]; /* 概览 JSON(.data 形状) */ +} m_net_status_result_t; + +#define NET_IFACE_NAME_MAX 32 /* 接口名上限(lo/wlan0/usb-eth) */ + +/* MSG_NET_IFACE_REQ ↑:name 空=全部接口;否则单接口详情。→ MSG_NET_IFACE_RESULT ↓。 */ +typedef struct m_net_iface_req { + char name[NET_IFACE_NAME_MAX]; +} m_net_iface_req_t; /* 32 B */ + +typedef struct m_net_iface_result { + uint32_t code; + uint16_t json_len; + uint16_t pad; + char json[MEOW_IPC_MAX - 8]; +} m_net_iface_result_t; + +/* MSG_NET_WIFI_CTL / MSG_NET_BT_CTL ↑:子命令字串 + 变长 arg(connect 的 ssid/psk)。→ *_RESULT ↓。 + * cmd(WIFI_CTL): status|scan|networks|connect|disconnect|on|off + rfkill/udhcpc 命名空间 + * (rfkill arg="list"|" ";udhcpc arg="renew "|"release ")。 + * cmd(BT_CTL) : status|on|off|paired。 */ +typedef struct m_net_cmd { + char cmd[24]; /* 子命令字串(大写不敏感;接收侧截断) */ + char arg[132]; /* connect: "[,]";rfkill: " ";udhcpc: "renew|release " */ +} m_net_cmd_t; /* 156 B */ + +typedef struct m_net_cmd_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_PERM / MEOW_ERR_INVAL / MEOW_ERR_NODEV */ + uint16_t text_len; /* text 有效字节(不含 NUL) */ + uint16_t pad; + char text[MEOW_IPC_MAX - 8]; +} m_net_cmd_result_t; + #endif /* MEOW_IPC_H */ diff --git a/patch_v2/meow_netmgrd/build_netmgrd.sh b/patch_v2/meow_netmgrd/build_netmgrd.sh new file mode 100755 index 0000000..8679583 --- /dev/null +++ b/patch_v2/meow_netmgrd/build_netmgrd.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# build_netmgrd.sh — meow_netmgrd 交叉编译(mipsel 静态)+ 宿主编译可选。 +# +# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。netmgrd 仅 POSIX + Linux 基接口 +# (复用 meow_ipc.h 帧格式 + m_net_* 负载,无 launcher 依赖),单文件满足「固件内、不入卡」。 +# 单一二进制双形态:/usr/bin/meow_netmgrd(svcd 空参=守护)+ /usr/bin/meow_net(CLI,同一份 +# 文件两次安装,argv[0] basename 分形态;fw_content.conf 两行同 src)。 +# +# 用法: +# ./build_netmgrd.sh # 交叉编译 → patch_v2/meow_netmgrd/meow_netmgrd(mipsel 静态) +# ./build_netmgrd.sh host # 宿主编译 → patch_v2/meow_netmgrd/meow_netmgrd_host +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" +I_HDR="$ROOT/patch_v2/meow_launcherd" # meow_ipc.h(帧/MSG_NET_*/m_net_*) + +if [ "$1" = "host" ]; then + HOST_CC="${CC_HOST:-cc}" + # -Wno-format-truncation:宿主 glibc 对固定缓冲 snprintf 的「安全截断」误报(musl 交叉不报)。 + "$HOST_CC" -Os -Wall -Wextra -U_FORTIFY_SOURCE -Wno-format-truncation -I"$I_HDR" \ + meow_netmgrd.c -o meow_netmgrd_host + echo "== host build ==" + ls -la meow_netmgrd_host + file meow_netmgrd_host + exit 0 +fi + +"$CC" -Os -Wall -Wextra -static -I"$I_HDR" meow_netmgrd.c -o meow_netmgrd +echo "== meow_netmgrd (mipsel static) ==" +ls -la meow_netmgrd +md5sum meow_netmgrd +file meow_netmgrd diff --git a/patch_v2/meow_netmgrd/meow_netmgrd b/patch_v2/meow_netmgrd/meow_netmgrd new file mode 100755 index 0000000000000000000000000000000000000000..4fa5774482e421b051a2ad581405dd7956f168c7 GIT binary patch literal 152892 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%0D^`*G=IrM$Wp2)x zm%NVO$C6zEzH86rcfxMd1b)pbJQCcil+aHOfZwUOJN)R{K$q%sl`|}p3dXWXx*Yi= zk)?Apl}MlNLtfuzW%3B~Y%0xpO?j0)RJ+Z}<5SJ8?n_R}om(j{Rz|UuzEs{^5&>HSlvi)^}1VUpd%gwy-WP{9*&SMf7i76&erdCfTRo6Tc2~ z_Uiqw7^_2hQ(VxUDcxvs`fm0xqq-5Vm;TpS_oZ+x{njgNof9J3%m=@XSzkTHtPc8Z ztLIhUXV`FM{-H_z6V+IA$58p}NvwxO{$ez)@hFMyS`Efe5?$_OTc zM?lZ(vDbs~G}1GqPr4pl9KImdYVOxzA%-Yaa zS~V*82DJZLJ}sFb`*j$fl)m5b!jbpUME&TUY+02%I-d>V2ZZPN7(kSD+|vWEU|cQO zdecH1M|YTs7r-wq+(oPMNjT^>8F+{IhWMQJcxRiX(ms2n_MK?I zm-kE_ej!;QefPRbop$RwRy3DPqGTdDrn8F+kY$o%vW;$ddWC+)el~E}-9m>R@u8$la7;J(@PBd*6Hx%Ea_)*;Yd!vYi-4- zoukUlQYFZBift?#U$RW{MfCNT;#b5FXM_8rv4Pr-spCfQ7wq|K*PR{gB^_0Nh}K7X zM(W|$>KUSg#{KGU;-H=Z27(znqKS0zNj&A8z_)|%MQhTAj?e~=^Tux0d*B8vhf>BX2&RpG8VoLU z@*c*9ua_~Ui3549FYmSYE7$F;@E)28U_ACM) zPYj>UY75gGvP}LV(Z-SfyU4D3xGY;glHv7ruKOcB$sX1d{*HW00<&|Fm6DI-He; z(wC8^6Yz(fVZL&~UOs~yuzwaD??Z-6z>l5<-X`?Dj|zT8auSEU0NZv5T_G-931qWz zKfw6p&)&?Z?#Pp08#`p^U!e(N1)yg~_Bv}H@ZKcfgFTgb{!P?TTj(%8WyU(qJoJ~5 zcG>ieu!?jQ>Bq{G9F2FU*@lg8TsuyD{gS7%(e+d2Rxa`g?3P9xqkYC=8rqibWGqaZ zhI<%^GnC<8d)8(Q9+cO7}Bod z-kKHMTT_fZG~UhKHC^0YQ{e8JJa^Z0a(7LRyKDRyGLpM%D#XBUMRsSl!J7_N%#&s- z`)!-sUG8e;jF~@7jL}B+f(~Q5t>RPk{|KM6oh|JX+^>BKdG;=&&dGfC@!4#GoSpb# z{5x3B2dR$_z_0S1`)d77d`59APw-v7zgu!%VK?8OoW=Q~C%wYX@cBz;c>Z?2bN`2JMog$=)7KCPak^hxr1t_h#-CXeYT-=BQa>HhkY ziwj@xak{Vix>vY?XMg(^;;z5y6}IvH`7Z9t8}bU*^Zm1@INhK9tXH_0?;7`ow^0&mzrIs%_lsWBw;rzeY`$IFWRdnqB;U;sYrDNB80vhP`U7o#C zJ2R>G1`%6&G0b&mhGS?7{m#bhxlQ;N`OpHn!o5D$o~^pfqx?;D zO|==^)%3{v{k5C1`$sylhsndPa~gYYSFSw=du_MbR$fF*+D`LDa(0Z8n|FX_SwWiGKF9+*W zrywE#6*Kf_C#C$8^e08Rgm>P731ZyWelTH-0 zlP*#`)p|C#bGvxn5_HsgoW-xzW>aq8`g!Ks5I^^TD`{ZR-`NquCT4LZ?zbD%MgA2g zVI2e3%F7}hZpq;MT>+eQf6OWN`-!yuFZgY)wCy?OMBlS*&Sso(;J?O6Sle>8Ut}It zM2FVRTbmAGi@&XTZ4a=044GUXS0+_;4hp^p;fG;g`YzaB9l~}dV|3~JNZ2kJJUJYr z#;3Z$@7cf+FHkra`Nwpo%$V8elF%{b7TxeRhdi+BZ?5(GkFjT+p`ZEa&6({LQ{+5R zX}8W49dJt?xiK||#DF8XJY35R*J>m7E^IKT^QRWK|E2v@)&^O|p4hhWxic!3ceI3Z z^hxHi9yn#TO_*xJ1;*s{vUWt<;34=rP#V09@gD{cVl#T;S`+av)%)hFK|Q_KQ$5Si z2xP0;<4i7b0TbAkRi|ji$8Y?ynYF^~9_O#j?{a2s(ee+~L&qK#pGk%=mAwIfFZ(Gc zR;%r~J$12jqe;1EW?IpqTHV+Rqk@dZ?z~@UfFQb?P^}`;T#orzad(WQ1S?M3V0%?H`TIeT!>3ny3(C%SmKIO=#R2eUFW~JMV zA#+pHJCV7nXA%dNNxopi+HpAMxn-Jt`;Y$1%sP0} zjBa~RX~Xkk_*~=ik&OW_h2uEEd4o?YXNdHyRFU}nKJG*;nwbZA*2VYTJR9aSd>8d2 zKcI9C?l~mZyO```%)YaknE4(KbCY}8d;vXv`C0wUE05o*+kC!E95_2_TgIQSy^Aut z#+8pQgCAUNZtt1Vz3o5gvy^h8J{42aTsq5~yPLyPtYZOuikbVe@DVo!FI;Kv!Uo?~ zUaWgf$uAheB9k|+=B)GMvfGjWL+II)ayJj(&E((>q1=t$Lnp}4V2z8wS2VPivL9sq zbgoF}QrCdL+GlG{Y2SXDKiNj=#W&Z-MZ7#?h{mOyvXbpu`{rTC4PeuI1Z-aB>;q>m z#!IuC$h+kb)8pmyA>QcB&Mf0Ceh{39hvVDNyng_#Fgf#5Z7EBZ>^9ALd@UJ$DTeLYj3)S# zzf|j>^%^yImrq1rIgvF{T)bpXM=s`MP|>tD?o8Bftqq*LDZhDgZGKU!wz#Cbh}L6i zIR0oYqV}WTjcX`*B3YvTB`3Ho3OQa1;)yp_k@F3A&#X-UM#yx4x?# zJy%(G=1lO-1!m;HM-J&dGoQpU%ga`tg%9fU@X1}Q@!mx#M`yS;k2fPDumj89#vYxw zXFcu*S0>yaiig1;#lzO?dy2VC(&9uwpL(z7@NG^{qK5`_FWo2NqxJWJj1ces5$8{Z zpXY?R@U_>qA3E;FjyTQiS#laNI?cQHa87?2XBgkY zv6`l%JEhDE%bGbm+T4n!Q(A30k}=oIwZS*w7x}6??HBS#%^?21C)?3_mT^<>TANPA zp|cfZ|G{1mF+tE$(bC)D+UBunJfC454-;!0{q9KI>%s?Bp7Hez{ic>m9?Hd0^t$I3Z@BRaI0Sq!s`SR;?^vzg#nyF5IpV@oP3hbvG{Q23iEOCsP>sjYh zOxlpp)o6a)y8}K1<NiQ2h-N}H2rKDlKqef*IIVTq<)`L-8I}5tC+8+vzbJdes$J+z_}Wn$R_uq@Ar0g zv~v15xtiT+&K)NH$enB-o8V>cL{`3!g|7PNpWnlsD4U6WYBNcm?1f(}hi@y7N41sD zR@#B5*jt>QvDbU$;dxkUN;T`smqukC{d;+ad#Z5Hm)`U*GvF9mqRx?mHGxan!8WM6vJvP5 zT{@68rfPd@iT_>?cA!evGBLbI+V#KZZn6@i+3M9Z(4U# zY6RMwW3st^p3UfP>be;m>^Bp zXSxoUY{_xcoXc4N{2A#zf&R&UD!Oh8doJRWx?@ASv~-_-r)bAZ9dHS0h0de8%1t8K zAMvF3I>DS<#mTlI=~03a-1H~z?e zOCQK$7aN`VO}J%pndwf7kD9u#k;b_1I)V$4+-q3$X1zJceW$FGBs%{rStbr))Smm zPkyVZ_0Su!vCL#Wa~%6<5%!Vv-bNegGQkL+{i4L%Of&kbaLG>`aDE*63_izu`4W8S zq`CEt=#yLE%kl$YJ8(8+I=meuVZDlc&W6`+vASAjV;^?KR^WfDWJjlbR*MpQ9sE^B zx}1C>=vcr|ewn(?uDLeCSmTZKYUxCh0WX`)Tcis*^w$RuSD!1;pCf%Sz5P1+`i5IX zuWa7F$H^w%?JP>X$$TN4lh^t#et+2LzDU{J&BHzLySc9V{R_&-j=+|4p2SX(P9+;y zV_nJIWuMj8-?=RXZ;fn&U@anh*EFoZ3D^Jb0C#9h?FuJ^CyZ11;jNt#tZ~q;<{q^z z{8aw=0JlI{yUxMbR$?RAG0|?cwk!$%<_31o@4$(uj%;S}S@BTmr_$9Vqs5COIo-%# ztzLtc66iJJ6`Rp(dK&ba82UpD9Rm*&xYB`4i1Zrv=Q171WxyW&CHY5g=9;40QM!+F zEM~6mt36y>jlMDRnOCgNd0*yg_`qVb$NHgugU+Y*#14Sp4LXZbdtLU*OgnVOIdZ=_ zH-L=YY+mI&ru~h4gXbYD2J|_eF#l`mF!nU}Q`z`tp@G=H*%^_&>0)xlelM#iqHh$JE=Njcv4# zTtNP0Inn0SvY8u(&?OHnmOpV(qSL$>%2wNFwcqJT;H58`{-{Qvhat`jq}?Q&d*Ekb@jXeugSs- z7Xh=+h3`7?5Bunp@@E#&jo34H3h)wd_}%!I>%2{Vv=z|Ov1CBtFPoU;<&qzg-PvLf zuzRfx(VA$@KON?L$Lb^L z>)_Y*k<=&AN7k?oTfwnj*5PW_VIS+Tg>@*g4(KH8`^+)(=3028FAM&2p^rR!1E~iC zoxTd)G*d>;YPI#icmulWBiNFnBV#(la>|)YY>KTrpL>(Is(;$(M> zHmu){cmvHxafEtT1rJU^Ll1QO5Oz6mICZm0=T-na?UP*q+%^NZ)fJtA$0q~aO0@gP zN=9~xp6dgP6-Kh`;6Rzf4hr`M42FCm{`v=$q3%?~B zWo6Nr&@S2!4ob&bjo)23~mKAEV-K_YNtxnrVjfyx{2bOlh8LndkY; zb6vwcE6mflCz+=#en|f#KNI{ZSdRzkKfsCDX5va%yH)V}04Kf?;so&w{(NTyT-aXz zS?wvSAEJxiDx8SmA{nZ66i>b;QHwq7O+YusoxbLO-kP2J9oZWbl7W z_f3JD8oOv@25YV7T66Hu(cet&A;yNWZR$>VwWoG9X88zBD%S;%F)8|7DZQt`uP(i@ zQAcR-DOdEJ9naByC$|{+naSZh+VFmB!}rGL_WRU&y^L47*BeaLuJ8t?$KkUz?**~#$@NtE6)Tj1GqffP|@kGC)a*fY5{^)Wg z$gz;LITlJLO}r6yY7~`iqlK7Z!Fq zOT-Dq!?_b5T#4F>+R*v67j=FO+KXu1Bt|+<4C|a{M)D=&6|fO1>t;?vj(Y{*TY2yy zBW=y!%d>}_n14oi--Mp4pS?}fuw^`QsRuAN6Ap2RBLLeiyuq|tSm1Lf24TdwCpgH9 zuw2K;jirT~Z!DSK!g*#3eP3CKne`RdtgB3pW7aftuwfjEb&mM)<;Z*Vo>7fM@mHmV zBR8IBHq%aTp|K6Od77DMXoK^HuW3W|8(~`{zVkuiJ9nEMir*aH&fd?cQEWYOdSey; zZ3$~+q`hf4V=BB?JW5A$#Av?=xig+520TOF1cPjh&WS4idE0M1|BJ*A5*O31+&@>~ zgXlJ!$K`is|9ZTS-$v(=sViKuXE~6o^i3Suy=BHyW=!bl0UlI|$F0ZV9wi6%aE0-f z@T=ZT-1@*^dQ-++X6}F&uN$&w$~N_YZ5ucT*f&?_yn?f28u!4EmpW7L#^BM!exHfI zX5E~nOlzIu(8yGXDROxJpL(e3^f97<&T-YmFgPp9wcGh4N z7;j?@w%RoS{u`MWYj89;Yk%ho;7JY-VkO}Lzx9Z75PWt2UI5o?IFliLh4#qPb1!?Q z_W|D>z&C*F?$>iR4tPc5xXj!>Xq@k&F9mC^v+|jIiM@$SP1Ck@>_u;Pn@jR%$|uto zo`*Ns+aGl=Gd*L(qjNT5ExL8j>&~fr4ZQ3Qc-gS?(DH%;9%IM z+PI!J@bAY}Ub+^~h=C?&0ltYFc<;3mQMC!bQ|e0hM)vx>+)nmNS6knZWS91jy^3$j z7G_V86Zt)Myl0q8EX@8U9E-#J7XhE{@Ep|1VY&VEDSy}7LO8f)`tOTtxwcelp~~Z9aKheg#%_aKKW&%HPLuJ>h{?22;OSv_l5c6j5WZUt=TSXL$t%k zJp7e{$@<~__+@HFww4#h%gX0=IzG44O*MJCUAvlDO-uxPL$z~#$E@S;$a*h&p%AO=bPEDUFD{d zoaW+;#@*1&yL`n&>JB+Ckv5NbLCj4I9o@zUZ7`^@nBm3%+Z9GJht+(}%+o(|~(#F50dFo(>+qV+{;zf$LN5AB6U zx=OBvCG%1l)yw=I^!DixHqqMt|3Pp2&O6=m1bp8y@q_~KjBqi^+ag@t!PvwXqOl!x zigAEyMEB@M_Ph8QaWIyDY^yz&^`J*$3N0NSVe_;F>ytFo{3L7g5#ahk&VBmG5#%=V zJCN-TI^#!JgGX3{D@EM&btaIw@@27|;exrOvTk%!Kjd)X5-=Ru!XAEW0trXw$t+1_&p^uxx z?eWC|J6U=M>lj7t5y*WiNTg%wxSDyx)wWhI9M-IljZZyDg&XHRj z?=!)3C=)r8LdSX!_2q-cw*xI^I(2UEitucXbTqA_)(=Q$p`$Ey%I^^k!h07mchBx0 zw6TBf_@{+@{m=N$JwleR(Qdf|P5htPYx)$hU=Iua(lh))Vs-^T@>@X1{qViIBpd?{Ji8+@A);#r9}4($hB&X`39dMBBv@qa(G z`JSoWXK}MgY>H!+6~0eyuM%GFXwUg)mbQ#hokGK$p+WcOq5t&t%=JHxGuIQtc7+$Z z+d*UeN$s7CtF01^OJmpAq`SWm?uQ=;#}>)5ZgdfNUAZ@W{%K&<$DXs=?SzK(uFLM> z#{7Q|%jlW-Lc~usm*cJ3X<@yHUpKDVY2iH7UT{Z6TV*cg8}*Q*;lZ-wwE175PN+FD z5AZ4m-UqTfn5SfM@cUxs4-Cc?JFYn#Uw7mjA+g6Dt;=nU@Qv_o;fhC$RE9mwkA!o< z&a-<#+2nh8ZgI0ye5NDm+TR}v%PGECID*WwbRZaLJ=m!l6-~_!Wq&8n;-=4*3GlNY zdQOeTLtOHRX9@UVs-k&GSNZSiU+DVyeHA~`#xBOlc%(xdOSZ1Wu38Y@6+xV;rHe7v zMrZu=PTv_U|AKKf;??Hxo$yKYsPc*r)qa=Yf{kM5{Aid%kMjWK=sP>1wy(zXgV;c2 zY@sqXQ5oB)jEz*rRw`pNm9d@5*idC`DeJG@gdaxzsjs`jas0l&qd7&mB%a5(EzLx8 zi=Is_t2swyqWXf3)-I|uwT#}WjOvLtqjFK(M}zIWkgvIBvkx7GID|sAKNaV&+GL$oQ3wb8tu2J{f=CQd=M+#MX43Ytvl&|19z8< z;8!f!{u7~HoHNC^%5!5&_Ii^TT5Dzu;p4`aKYa)vTBk?MekSpvNk;7^UNGt6G$-A~ zCqBt^ai7`u_JrvuzG$+m7P3FQkhT)%XwFTECibU8J3N0i`(O_C@AR>BR~q=FiJAPA z*?0B~^uoB~j&+ovp5DJe}6oT8_QTeQYU@`-dI$VRvjTf7h7U zSV!xvvW?sus3*JRM)c<+eKP@F=>JkPmEYG*3BaaC3}jSO6D8CY4Oj(U-0*g&mqorsa@0g{s-8{ zXyti|d=7E5nz{BXF2t^f^m=SxYwJh0#;-y?elP844FdVmH);FpOqibkq_)boA)PI< z6FT_L9V_Kot1Yx@3I_>kY)V=kip8DVbyZ-@O?AJaHueq)*W zlu_GJn{Pf=o5+E;VOxBM^Bx-W{6~(QQ)8PyvG?H_%FE{Kx8_TSGM@FBHeH7$85${VUTJS6DQ~9q(#&P8P6LxNOeEQ~5 z`pbiF-!Q9y<9vUG?dSB+XVC}k?^&XJlCyZ9KDZ|qjT;<%4Q@nbe+9m&toW}(+jAZ@ z>|&3MPlbopkw41#8sYcWu>2iI!w+CAnDtI+v;Qb!HCT zePyfR7yUz5&}Mpf3%sgMmp?p4o5JhUri|$%=e6+50k64EK$C*oX4=zz?R>}QbZmaG zAHSmfJrVC7nKF*(z0TW+wjQSZ8z}$d@b`~G-{3!n&z}#UAHFHp4L9~*wPT%6Ec|MC zd%*9N3nlO$^p6cY6>C?&1fi?_pNIYin=gfx7d?n~9kKqUfQM8aXZ~;DJJI|io1+n~ z{{;T1-Kc&#@n6IMCb6+sC57jQ!udAVnQ8IaO!BfrwE(VB8?S^sQ8bdmS1XzlEfE8Z zZ{uF8I|yf^ah*A(zWOiNHs(*_xad(hAJL|vjM`AX+a=fl`)kXI{ohHRgm;qPt1{kr zmO*r_^Htg(S1h_>5Z5wqbmj3Z`m25i&x10^+fLQdd*p0Bid+8xoeNf(?fBRamfSy~ z4dIf`*CA7vc$ES72@OB@7cR0TK7YxK_BReJMj+AM= zz~}fCROW?n4!{Ojq;1(>)x5a+xFED;qCLL(z)s_n z?@0b+;a9LH&@&pz+z7A5Lk_+g@D}wi8lLT>Xm>s|7SQa`=<#hKOnv}N4mZ%_2}j|b z`@^v}jwu+=e1GbA1n-N&w)fHYZyMVE%u(9D+UgL3LI789`@7Kf_PSp36UIB(1Fr5U zpBwJE&19~N-~h^B*~BRp@^abr%n~=0O@23)LD$9xx6gdw+=|^coxM14uQRYaZO%6P zUbd6s_0_YL>!t8a8}Zo0j)mt-jcd;UvnQ!}O2#8^S--s-s6vU1oR=Cm<@R>EEiV6J~V|UEU*=Rh!53UQhE(>vs+-ohr zZonZYvna}^_xr*v!F&Z<_HVk=(9_CvuV1pVNVwz=+A}p%X!7Eph;P?vvPguO z&l<~Dtvqn2ayAlMC3ge+P~soc6CW`j<-2HIxayeavx2Mc>pe`anL{<@O4GSL>@{3Q zQ68XHv&WuE+e_Av<#tRycr7`}dAHOacd_*)X#aQ5qM7EC8a6sw4&rH zd;vPA*|qXI@>DE_^3e-=MqjS&L1*ms{Z(&unh$>3`On(5EzR2(F_wu?Z@<$2gU6*; z-937q{nXyI{$h;n5#lc2lI{Ul-N(ynfV;wMKh~t zLvp?Ve2H;(m%(SAuYDvd`PMW^TaJH0NKe!&%jd`F$;;SdieZ_X@X?F+Uq^eiH4gmQ zvnx%W&21+v}c9XeMXUp9ZXAU$E?p>qps|GWP5q|fN-bVdQYl(e>lq-16B&Jp6 zFR<;ljcOk>(>h_{h{t90CiZ1(?BAB~gWhDkyq9|p9cs13k(m(=hB5A%0 z^S`R4=LBudG;cgH&VKCyw>EP_Ay~5k=p~S?YU@E~kL6_xzy${!*V*HCv+ty)l>?kF zB<^X`nT|0h_?f8@r#zb|PG8&M z_?N%maW7wUop;UGDKoa%n8l-x`FgwX++X`s=O%N{(&mF>PV@F9_&e6m31;Y+nO^YmOXlHj$YJPZ2u)399TV4ulS*5)kX`%UH@ z`4ami9yy=jH>1LdXx&s!bsNj0n>ebg^Y(g&&D|c!?3>I|&QvX%@0$Ir-`r)5?JCEa z0&Lf+s7Us+BRFW%`#a2ELRZ zzm~BQU*u-Cm6&bZ*SXu<*hfw0&qPlvTK!3}%NvvHxlF~lnNro&xKv*C zbZ1B7+-_jcQ?kSU<$0%hFemmN#_f64BVj9Y!_{C3bEFe|;bz5T~*&-`BPy!hW}XX@OJ z-Nv!!CcKf|P{3Yj1s_>lqc}p~WAQ+66aE}zd=bp_EIOmh9*nJH*Ep3Do=nygY;8SY zXz5MwiF*Jy!?V*>i&wG_6tfbXot|)Y#@!rrKJjX8Z@Y38`~l+<-eto2jcXY_W39%O zYvUs6Ne5MyK518Fi^~~1c#$a;S>vLaBl;g!8J@8(8kNy=+4acITqb2E=OLX&c+j+M zPfYV}+K7yRueX>joE=_fyh=a1zyRlk`_1eSmjzSk-tPBh@i9mr*B-wySLQPOZsoVm z&HCVf#AhRYRW_9Ln`mxd5ly2XMEY){zWI9WID8c3wJ)N%%}3YtY;KhA)sC)binlfD znUWuZmH4w{M}=6;D~Q#+9C%-bPiYl=;$nP1EAjcPfR`1G?o?x6z--6gv2O4hd@Y&b zDmYbv2aA5Tk;|qhxxmIr$*(wK{r%!8+Ru`&>Xgu~kv}Ey2etU`v2=bCazZ|7Jt8O>!i?M zTtRLR;4i)-@E33DccXTW^cM&1L}jhNn6mO0DaS(MtHJ(VG(L@e#xcg;H0kFH=KjhN ze(9i}Lk+&Z*&$Di#`P8Et3Dh1`l>D8!dL|3lP1R_IH_#lqdUp2$-(oS?pD@SzP>1q6$M@GRkWDkC3;%4q#{Hwk&m z3jc_n$O+ocKMnbk-r=jUG67sEzcZvY%>laBnSO9CfYHA(=gg+6dt=xqdosDtb8eV? zvxcZXd@A$tA1uSC@+K1x<&K$|G#{nBSMuBwDAUnUrZp^+!$+7&q2Iw*L@V1ZUrgbL z40VRx@RfWql(FO^CBKo%DOOf~I{6s&K9DwFkW1+k=>LA`K;akwE&;=J_SErJ?R73jNiV;@543~e|eSf`V`LK z>$Lpgw>&$Q`g*RpG>-54;ogYa{gcTyM~?7=s@}Uof2rDSs&IGJrakC5EvA&i1|#>* zMD3lNY3*U}cC)*VGjOfwNg2+bE60nCFD8z!R$GQGD*vA1cX}70yW_)JhTpbm;T*|? z1>nyA#JBcP2&=!Y4P-gLVE2X>wf10NGH!I$MZikXJyw5>@>KYIET37_&$JLvq-Tm& zqWEsjBl1B8zCG7h9r^aOwxc$GdW=1UW0%X(pvIavyNDS%3BPewvf9$5WVmRg@!7$L zXLMV7lU`xGMaru{PK8)hMt98XeNz|y@a^PpOI+s`Nrq(gh*A&hG6KK9Kl{U4Pdq)` z5C-4*Eh%C*%&E)9>p1Y_3Zno`SYw=Ji0B+VOrYzS`& zl*W1DUC34O?ZgObFj9F)nE9H{xZpL7&|(!v3A17KAXQ{fU!!aIRc)v zuX(Dh_FTlX^YUqgFqFPybMN4f?_hi>)3zjl*@P<^#&5L@TPXtD>r=Fk9TezO6&D?aSSx&f*ed8QWpo%~+VJ_YcIY-`bR6>4g+4!G zBA!6^DWLo0;pLsIllHuP^q#4AlTPYA=sb#_&y-5QOgzXz*SU09aS8Zl=Pw3dejMUb zAe$K)De}E-OUyi1y0ru(@nF7mj@r9+jf8dB&1+LY~?0?yoIn zpK*ZmwQZg8(EdOVceU3mJ%0ty_WjKqbD`sOw~U=xIPh0a_t=GRI!?d6!{<-wckdhL zn4&V2E5}(Yf5o}r;G?GZY>fjxk)Q71iA9W_}&Ows|8Qh`SdRXWl$~meC$4IsyFaSa*x%o=!2AG{}1soL{yDRgao+t7Q@ zl=!F~yT()Fqh2`ze~ZV7kBZUma*IRaGcBP$oh5cv{mDipmz}SEi4(Bp0{RU4ZT)({ zTOvI{ds&M8m0ghfrr!0824&`iJg3O{KXmEVlgLZn552H^4gCc9awNma!@+7WziJxcjU!f}$9#&6oB{ZQ_%w6-tO01tTwd!(m^dgk@awN4v7 zmY3P{dFZ#o1<98{f2rpP2yZ0WA^O;)O&f^o|UarP+BYa=@v%hvJ{IP$@ z0*ebRzV2R-P4(H&=HXKT9FF9V0sQ|woLAJJ_JOMC=;BG<_A@8jjbbG_68l~5F}Awo z$HThnM}5EB33$m~^6>PY9JI3lJZ%M!Tfkr0-rBd7u3E$v@-}K8A?&fu)OVN?P<>Eu(oavQIraOTi+L8DNc;r98BcHPT z(KL*;*xJIL6gmfU?mc5Z`wQ#&9l)R{SZrKRe74Ux)26+(4;^ayaKytIcE2_94c$6hF*&-1gQ9yB|3BAlhx}_77*U z<-C#)d_Ms_&vOOSFN8AdczAm{Fc#iNcx(GI2bN)<#yS%J!umUmyKNE{e;bbP6mz@n z{}l5vn=e#j5D)QEYZwDICUW+v@JzeyjH>P6T=;?--&T=wOtR<%kk!R*_64(TYqKOAhUfRaS7+Lw%>_K zK50CAXUEvP3BF$)*3)|Yy7@hYDRkIh z8bKe)o8~<0QJRM>`84O#lZG?6mhOem7O%iLANeGG^_uj;}gNV~z5t>#hlOuR)|EilwR^&&DDMk%F z3BB?@^vWHaIogSRc0cx6a1R6e74})6Z?0<4H;EnT9(|Fxv#xfUe}2$26$^(|oR8C) zx=aY)%&Mwc5B(XlZFuiWGSL%6p*M2hY!xX2v+<8(Uf;PD1%C z&oupUjsJ~d`^+s*p&Rr0 z;na$mH-d1})GkgiJ8olc8C97M5{T(jeuc)I9`X~Y{JFJshB0;iDC8&6nd9q2zQ!3M zVm!WzEm^dC#BZT3#U}-AK0g}tH!*66b=0$T@=+6G4A}R6?O&axhVvVZdTxAjd;^0z zmx2ERn?|4YJMtdl>=@I5&VqH4etgXQ4#ViWC3wq5-3z!ContF!2^9O?m{+6jJF5W!E;dKj2#` zr!$^Et6klK4d(>))>R$l_)1oJyKdq)4R;^kU0Y!95K(Mi%v4$8IoZRjXvXM<6y9+!i98*3lq$-jthU;0K+v6-{+YLJBOI*v#WCuD({VqxIfFDywd_cA$xioAa zpR+vD=1j9|N3_ond!_cBV1N&jTyI7D+t$Ztn8csi8O9-)(n;OF;k*&C21DtP-c*k{ z>=ljh`CN4WbtZSI#hn1Q>AlVC@50s(l-N zLsNaWonP7YcJW^*?zc`CUz&O^Eqb%oQ)>sGK#!il=TkyY{ye;NTbcWg%lNnPc~#LZ zQ|x7Yxjo6=ZHxK*xJT?33yfd*%k7gn%%`*$ZJ|DQGXX2X&JX!* zj&?TD{u=Y}I6e^0>hENKc?W**`^HzHvtDeJkBPB>A0^iJ2HIRsjGtohiPvBb2Z$?l zyK7(RC7z^*v*@~Cti?}p_WRpfmsHifC)(p9R#Y*0=}H^p-#M=M6UF5#mNKo}`^g^T zGe+(^7|Q9{Cg5cHr{@@t_Hy-Jd%ueJQhk+GtQWath?&_j{x(yzw6*s{?9**M2&jsc zQAOU#AGGhplgtX{t~2hy2AJyJ$O>?&j9)M(?n{@(2ky=b@#${lA!lnXF3Oh_;DA?X zO8g(K?m+Q5i8bFb+160<=xyPTwrw4)tnfJMH;r2O=k%>S3dHwWJMVDx zo4ASQRKHmr{%)=e07K;`TQ?uHK$&s62Vd|8@wZ_M6opqVYglG2@vGm5UwsFD^_^w; zyI6A@Q+thYfVtetx;-)JXXido=9B{u>UUmGfCu2@F!->kWaP6azeD?DzzgmO40eu3 zdIEUsdLbMY|DZh(l@o8uKp-i`M7z+MVwK44NL!8m%ot}o6X;s=nVa-4Zn?8Gw!Pdx zY|O~$_VSvFbhAhXL^#iWDf=w}&I%UEXU-W!S3fC;t6^V*dwF#R+(xN)OXn)Ht8&gIM7)!R=^wI3MktZ$$gDwI*gS^Y!`fvorq2Z;Q5z?);Fp^?aEHSo^TWVsmSE(G?27qw9~;GudpxH8QeX)OtD zFgMf2mY|KC+NkR^>WgzmwmrLcPvFxJ*Za@HHI+VbtaG>Z`PcDh|BL4gstw)S9>pOf z;bHV+c@5(n77RaK2F2&<_Lys%q%|*j~ zyZ8^f@NXC3)6mOn=u`bjZ$?IPHoJ}Z@(ailT1|TRZfBT%7B1X3VW0no*Z{y~F8Ype zUG~uO!8@(4?IOdkzGHJ3$GyPx4=ys-+#&v?oB}Sfx4OGUdn~({YmlR~XVA;{!<&EP zH&f5!cF4oEw+_BiyVmMBuhiaebtUXg#`RY2{!W`6)J<>fFdG@4`j_p&?GBb-8}rq{ zb)nr<`*ZH@HeX|WvykcHb1{E9V^OSc;O~=;agu+q)?WBNZ62=W3YkhDgtX{0r_cr` zzcYtx{hz^a;&_)YHTZQGVn^G#|FU+09bdD#(0=~Bw$hICY1+y3^*J+4uYLZL+VcAI zO?{4ie@*zgt?G|v9wU?yeoVfE0GH)vikv%Fltt7mTkx6uh)wX6F{IfE1Hvi2zJHAj7;gM6HL-*)5W$+czI-JR*;7dqQak9+x_h0lmp`s>Ia#@Y;JN+pw#-m$%c zE!Xd=Z)76RjQbqAp86J^NBajl@1(q68f!4G^c^m_c$in7IB*MN{IGW@2CsG#ch80W zCd~zd+)ri4iTY7 zc0I>_WNm|2Fy_`lFX`bQxrFv_vu6W{32|rFV%?=LUs+Im$Z4;}y3PM{Wub#Ofh@n9 zQf&milbO`AWDRZ}Pc@8#m?)&1)(_vQ5kVh_nj^wR2CjxM?X8_E1 zfh+s%;9^|(68Ur9j{Ys54Bm^Q$jE>vR(B4kYs5+hVTJ&yj|#c_oL(OWdCf(_zGk_`MKdU;(rs!`U-7-XHYhq@Im`Far50l zZ})(kFi#K8Ayz3fTw)(d`|&%FQ}ay$cy!X9_8}wPD#sn{XIuR$)UD7BBs{{ZTw)869DjAMt---BeIqQKxgTFMsKu+?=r;%F&ET)miVGB1)>+FNi zUmAEgW8i`1{b_V_U z?jc5Li#aMjc7^!JUg^u&leO9`|M!s(3&@eT{Bftdv>BbbM1P4md}ai`T>vgTzhr4a z`%a>F8*|S(%?j_VJB540xG=!xn}?I&e7JYq&0V+l-Z%es!AoS&RR&yS7wdl4ct>hF zu=4|)ue7mmH9$Z3pttERyb^6!(6g(PZGYe#r~9UqS8!Xo53hB->!m&htgp%umyBQi zX0t9eojuC|+xKs4ar`yYx<533M)yay|EO@;b=wN79^HWdWTx+>9-(a9d@;SDbMJlZ zrEm8gH}S-Wy+js0s@;G{W^l*!?t5Q(tZ?adcNX6I>lx5-!r%M$xY_5irU!2^UMgem zpSGd1`C!~}Q;&Z{`Ak|()=if17MTs?j3>?@rx>o7@t9lh>=v^R4p5zgpMAIoBNWuq z_f3j*obErzc{$--I`Lt*dJ6A6;>~jFfmg=tUEE@dV54Fiic8@zUGq6d@NeMNh^a2* zJ9=QT4VvJyBee#udKYEHZ^6CP9Cz;<$vo1=xN|p~i+}BBKjduaj4_|)T-@yN@^ZUTD=gDb8WG683ifMC(8OY6o9(0aKYk!87?@o6ylf~~^Vhq<( z_8IK`g{4J_YfW%Z%*&k3W?vb3!dW3B{=n$KgFWC>mKco3>1&3=Sp(Bc+rrZh)cC*m*QV~H&%;Ke9>{L7dfVM z$qZBKn}JRFpG#h<{WROO`|D16)u$b=m~*OcbxgI%q3>DfHu(MUOMAI5Ae&yy?ghs3 zS*$USp*MdX8QyUZclO=WVzT$lGUGEDpd+s&z>|8=APaZV1sZx?#s{g%Iq-;$pQz8Kg$|F^c+e;4_MMjXledBn`a zzZK7bypruB`koofqQ5!aIpY^r!J8U$5M7)JauDU&_i8ck9{S*nUxMooxceSB*?7cO zE?{4ryFe6IV&(Q@PY3#cCQ&pWnDHF(u3lG1>${2R9bs>IpZme5+1G9D$0yV~gigcx zr-0{b9G~LdMJJx+U+eUAE_8zDQF|Ynae%fsOAN2D_{BNnrcLk-_9@h6hCOA?VK(@- zf!|)L2fXQFygo6*LGB@)vs-fn?cpXO?oV^mUAfJKQhSN3SXLT}@F}1Oaa`&~Ig$~~{)Qr5=84Q=Rs#)@FYrYwDk=@@5VDD3=gZV3;Q<}Zubn6=H zLuu@fn55VA7r?8HxR4&R^$fpL_93{NYsw9nr^pqgd}^GpfS2noZ>PJX8D4;G`^bG} z)*5V9gUzFHX11l=Xx{socl15R_h{W8Im)_6aP9!kDdzAM1JmZ;=35hS#uXPu|lgT4&O3U+mPmN)9 zyEiVMFA)_EPi7yciyrgzLe%a3qe^`bRu#H;9xxZoJk2=>~L%5FgSSJ{_{bLapE zghMBo^K(r}&1H>vjK#60*32>KA(G{deXISc_$WMYd?viT&F3u2MP5Pgrei~LPQ>UA z(KI@8FUuzxwZNGyU4@vaxmIzH3bpw%K+3NEI3CYx08qwDw$N9oiZroHAO z3)hc@{;r4~rgMJU(gG`_bEm!Fzv5cr;MY2MgM50jBb2M+jpi!b27VfJiT;)XYxf_Q zINfL5>=jzrcWO2LmgnjG|Gt^xcRuzqR)zz0u|aU{9Pl<@`HL7Nyc4gRk@# zUg7v|qJM2E?I@>tUj2vnk>`h9Z}JX`gSGy=OMB-wp&usvweBqXfJYkoNvF1&qUE(d zuR&-#^8Cpjh^#!vkGB%w&pJ#2RGmLkX4Vn#Ytd?-U z?e;@nLHD)g4=pF&RP*V{pBDD@!5O`Nc`x*P1$IHAXYJ|JV%@8DW1j=dbqUXu-B@?- z3T$-77};|XEF;-r2iuJg zZYO%!cJ#1O^ssH%xm)2U74)#rp@(h4{`s=0uxotxxYfO|pEIQrcjS)X>(<$<`NTuS z$lEXvAKOSfXPD9FPV$S;|0&SF@*c29eA|o=W~10ZTg+7kVCGH-a1pQ%)6Z~bjwuC4J;##i4A9A!U9?&;od z*~~up%buZ~191>7IwP88@1$e5W8~{#Z{EH;pYPMcJbUCdy3H|nWWPdxf|LGQfUj(9 z`C3wi*o~a!Y6r&U#8YP09r$JcG=$Tv@a&W^D~99d|Jb$#r$opH9iHDoJKX8)KTrG2 z-%kP4JJ@T|U(F2UXPPrDJyEtqoV$&qkB4(m`B!SUbBE-WKIPfJ`U~_2Y}C{Nw>{B^ z9p>YYVKvPj;BY7W`7zpfj5Z$c{59wMwU>wQsEkE7c*h|xhmVXL(u(oa`erM-hi^`} z#!coan+rnco4|nK<#4NMl$Bg-!shBo+-1nKo9y73@=2c%j#06OP2qR77qoq5MP)P( z;g8D5*QWRB{G0IWkgpp5?H$i2&Qj-c>S%2gD^;ttWtCg| zEXFH*I6M4QK9``+R0Vn{Ll<4ZT7Up7R2%Y z_U8gzHP~q4)gz)XVrtr0vsm|f>Z)F&|4erVP6Nl`5#72&bXQBSU((48{2=1dTeJ22 zt>iRUEK`U>PPfLft3_=)-EH`~^kIUkbm! z+}?(dziM&b;7@(ku2nC7^~e0k*Du=qN|@VHWgcTL8b=)tnZd>Q52e#YW32D1$tPGc z+6U3TuEFPszEFl?&eeX1^b`31SYYD@a$S4N_4+$l$NTW#-HW#*Xsg9+7)+Wu*oWSd z)^NUGSmbnn@e9&3y&xA3xu$|#IBo2D1@cs~X5Sg?@NaB`ZrT>jGMVf?&W1E^+{}9J zFFi9n@#%kJE!`_)@32klglIe0iwv)@mPsMxE}T&|Z)SrbG|3{oWhEuH1h*@x=L#SKNb+;oQQiezuZu zZuHFKN%vw8{m{7uYPQds*~@Vs@qq1Bj`(h|=vZG);1lYher_W%hnp&i#EtIt9(?$1 z?!F6Wn{+Yfri%{uSI=~^Pt0`EE8pgJRJ)v}#3`}n+=!FzD?8172OMC#_y^Ek=9-GR z>-zy8*mvSglc}bhpVYV?YGum2FMM8~&vba(|J&aCfX8)I3BGl&+Hza+pSEKYWia@* z`nJ@z6uWH;P(T^CWG4|2W)z4Szi%=hk}Ww7IEk>GEVHXeT8?cztY0J&c=3CaT_nqn z4Jfkn7c!|uz1JtT|_?5u|gO9+O3zf<>K>6WfqZQ~@9nW4W=>f85J zojP^SsZ*y;{hR6L;>542@LTbDL|(Lje#QVJjJEd9Ku__;_77B*_1KG3)O~0Kjq5)* zb~p2F9qRwledCFvayN3ms(c(rnX0Bf)zruCv1cb$!+#f=&Wsf3`~F-FY8xbUd~Ynd z?<0P{;-8_*lEkXFx+ihBkN-2{CU~?Dd$OwFCy`1X}ezjk1`)F`QOhvnzh{D z>e))6^Ck4_$bY9jGgl>J#`HkD*0cE8iTCqW`}kUk?@}+}oy2q(pWVegCNX%%4LZh8 zJSABr`z{{$=WmE(570i3px=p6e3`p#*TRscwI-6lBi1_fzau zw&eZ!!mW#5DeOz{@XM4oD)vM2C0XBP*b3TKU9QT9;xgGS~>&172a(F;=zI z*BX~G@Ul3YyaGE#Bh>Zz{O7&?@CtQC_eqMoNB%wbVP_*!QV-J3o@5fs>&M6WCU(LR zJJHLZ*okC+vT8F_ZSK#}_VW(BqhDe_G_Xzmau%uJ6u;8LQKQRXpSGG<4>bM0+jN{Z zkJWU^Ir%!$p{=wz@c>S6fZL{0s3fnl(RVJ%PmXPU2^j&9a<7_d64eaTcpxtj(0z*IMja2mE|{!zlYPlo%jlCq5Tfu#w78dQ`*0g@1&0BP43elBcAd&W3lKy z(7=b5GecyKPUc}V%ySE`UKq=|(g&vUN=c*d|56~4~N?{nIQ+D*>cmVFY_V_c0qs%dY`EA7qcdawEa#1CHS%D23eKd}=+ zi|ltTwkBtQ`}c0*{=E&{xpyOX?zM5}-u2wM*UFuH>$r1I=wEt7&HjTquA9!w*o(DB z73@dIS0h7KF<5$7Zp)C#v64B?mEci?8;2F zvJ->#HgnsR&Y*gZv&q0SEPlJtAoMJJu6j7l+BNU4$(+p^nS0+!n`8W=`#wu8sFAgO z7Vp2|m*GO!TNqoX{|9xz&u4vQ-xB{^9Oe=9-uXNI{hl(e-tIp;v+bTBesG&n*R(4< z7MxprA5VC2%bbb#8~yhIegCb{q4iK`O8281{QX9CaB1wsS-Mh3S?7e;-T!u94?Vei zc*P&dhRRDlui{Oatn10d(?|KOn15>V^Xqfl$oI`3s@cnl-8K1rD0e;8$k@Oi6Vo|RoMF*VNuNC{y83XfZlmag_;@qaW!Q)Ak(=Z0>Tu6eoO% z&!Fy>#Cs3amRmB`96HRJs-xxf9}CqrHHvX{nHt1*8PtA?_~e|}-EYThn8!zDu|K~k zti}EuVhp#AbC+Dw%bLAjZBH_1JBA(ZTU2i)wxM?SDW$^IFtl2o(#o6{tB(alhEo6@bztXSgRx<9Nx+xw8j!*zcobK{9+>ZfBH zAF5TodtKE#PG^o-cOE}8$^9wGs_WFbv7v{Ss@{HC9V}yzjN03;u9Y!vMlF-)7Vo)E z?dq2}igWhFL!5=$k4L6u#Tj(S>Hzza*3zz;`_C8B?5k;4w(54%$lKG?a_F9cP7Vw& zK07)*gQou7G4;?4HH$He^U(8SpLeffO=3*t9uogdsY?DZZ}2&$-q(Mb8qj{$e3jSV ztOf2*ws)xK$A+=@&HdsxTk5)gHP=##SsI%>>cew5W9qO~#&f#ye==uKt7iQV760zo zOx6SpXuolR@8fHHj`#)ZDBr^;`E2zE{X{|etE3&&J*4sX%u{OHT$S$rww2}_(p3CP zwfO9bVVQ5MW1RCh{y1kID-5JhxT(;-DDC8}%PCuad+peDpC3C2?K5>>viNK*JkkEW zyA_^s2Jx_$jy@8764=i<$MpFzcUa(phnZM>`D^=|u<@4>HHN$>!ZXZ#K%h}YN784?Cx4JC8sZ$MgIw5=Nk=IP>Ep?K1v754pAj-U0 z{0wcFYu0tXL*#joJWtpE?}Az+>p^8sEk*sWWN)ePRmQF_Ge;6*jzo!H#hjhkbg|tc z3-S9|&yYyx2ejY+9^dbmbja1M_`c}f9(cZo-zPM!d{b_Uay#mU=N`Q?;LX>73vc#J z;!O&ACGRQn5yR!o-T&>!+=;(2ce)$g0nddG9&QgWB@~sd;FWwsKHkmX9YwYdM9ZA) zWSY~YEu;Lw`ZPS2`B{%<(Q%=f`Eh@aP<(rj=BL1Yh&+eKX?~05398H(UdmuBOPaLC zEBJ-(7`PcX>hdlIx9m$MRs_HA25(3I3iuhr<~oZ7UxzFGtLP@%G$>p40eXE~++UX@ zL`{q{f_UzLsu$m4Zyvt}zX0F!SN`6H@cso^pDKGHCeF6;a7%1Iiw-@54n2)u`XpnQ zQFKXkhPZ4~-bptcng-t$Di;&F7*}A64%P1P{2XEi3mC)Yh%3z?Rw(PuWWCgYUPr3v z17uG1$3KuBin)AZR2#HEVvR^09`wtri9e!lMIIFy`T%&v+_r+YK4|QN);?&~egCte z`H8s7d-P-nkTL6e7N*@@(4-PG-Ckd|!i!(9-y}TOFfrcYj8ElypBGWJ== z*eA!>r-`voBV!-E#w$On(x>xE#ec+@_DF4E=1f;)Bm2eX`ukRdw_zCw|KH`0FJv8V zxz(#}-=|c5ro?Vl!gB9X&3c?bB;(#keV?B7dia9*vVSwh9v8v+3vfN+&$UT9*{|6% zIW1#a+52`@IWehwmBivnE54=h=Bdf)9dFNz?9=&kANN}<@kt%~3FLhzHqo=VnhSm$lxq=Ulxv&i9YA>W^o# zM}2maI>+F$`%2|z9#ZaxtyVhm9_6;yTj{D(%6=O@!PRlay65}FkISgfGcF${#`O@f zfg^qG>S$lTI^LI8Un1W1W#V1YGtV|Kj=za`*k0mc`_QG2L2H_Imp!aCOefxp|8|%d z)z_?jYSzeJ;&MG|$;ijlTSvC4%SX1TH;r_vMI-HM;YgFZY@|^w7+I+1k0jLMk$!c> z2)hbLy4Adq4cuLKBfs0oe?9M7$-j>GIr6(|&WNLCk1SRd3hs8;w4$Yt6;2#U86B5(+{6xijx%)2a)>H)hJiPO(nuoKey|XBS{SS5c zcsgdbe_(u1$ZY6f+%TRtz4x?A_ha+(d6i>6ojcRBF?@lSI7>kKUujnbp6yD^_acTg z@zg3gSE%Cj5aT^r@2GSPLiV|)InO?w%iBSJA^VHB`R&czNq8=6+B7{SH~@ z#mkONzt!oUT2aQFsm%Gbv+wPIdK!O~G0j4eL%r2ezs7(5?r%0RmTF&EMmhfp8az3* zxZf#AuT94_dfp}YsF+t1pLq%7ig?oNl>&J}C9NYAVS&9`i% z1AP%5j3(IKvQ*B(Jpj#&QR2PSL3~+wb3+ThLbX}|6|#mi;MWzZ#ZEXN%gFr2pt7^o z+iq)wcc1ZiSJwz0+K=9kpx2>w@LU`6dUninLTB;I$4jdQr<~$agx;JI{W_4sd+I~llZcdHB=tG)keK9grUS9X_aOG~5Pw6&%s0Wup`-QiU)E)1 z#c#o0r!&GYr}X&+A(@0_8;mJ6znrw%1Z@@BS`{9TC`;n#?2D`#rAc)S7YZ%zui2|8 z@%cjG0DeeL>ki|YejS^NpCDbePWKAwn7bv-X<`Fhdw}1}J~P^xVqeSvZH#+0)gGOH zKI6H?zTJuO9K@aF{$0jV{mH}$-NqzNPU~S#tr{#R`z>M|HLO>Wxh*-rwYrIl;iv3Xzytm+S?|{TUex`ZaI~-H_VZ~oi2hpL~jA7%{LHg*qGL=lM8Xom= zK7JoCzCGV8%>bKpeSxe4eP_&lw1#|=U)JPEI^L1bvq>@zpX;ynN->`+GMTA1CJO3D zS;`@PTBCJC^yF8Cjp+Cx=*|zje=B;B*E9#u;X28fKd6V`l(?Db@~?HAtwrmk;GA4X z*@r51e2Mm%X22kIm3k&Q^A+8>PwFV=Q3UmzkliCkXRC)jUiA;Cpw1PH8DtzUI0YXz z#MZd=TKxm?Veq~is7nR=cxCR87?iSQy1}XhY;hBZ9mY`Sj8V?-(IY+!6 zWJ+YPJ0yePIgo*@83@b3}{ z6TeUHX!C9TAb67do`#0x@prJUAdL5Zdv1`&nZT=YHnwY=)Kk;=ee%m4lF}apJPGqB z{5+9H?(LOWG3Qsla`NNUWdvUL!SgE($J)U) zc;7<5-73Z(@o@Uw0OOufdz0X5%yT}%;C(avv~lc|oCRR1Rn#*#kYvAkGSOgv_=jRA ztr72AWN72xNWbwgv5`mm8291lPCd>EY@5uhtcJeMvo(e`@_3U?#~LFwLI?Aw8|QPx#M~#=Q-xJJn%_| z1K+rcajLALm)PfQ-7gJzyoLX1M={6l^IGl;@cA3lJj~hm3*WdO3LazAg$IwogGb?k z*!LtlGk70($L+i5LBNA>dD^y9o}7i};dboP(tbI&YXbMwIL-dvuLJfc=r3reT7Nve z)F+!$UVkBLdI#?-_vJ0+oPl4`FL~b*2N!?l82!e>M!zvn?H+GGl1602x=-dQ@8-L< zf0i0sSl7Clb@!)rx=Q6}dHoptA|L60Bp}oX8f`+N|uUiTUr@M>4RAd`jgHV*8md>OJE9?mZs-rq4Pq^%310r9Ms6 zXS#E0(#$_y4t|O4OW(&mk(vhf7Pt=fs!-T@Z*azXK>Ib&AbSsfU0AEr%A8$rM!KvQ z2+nV4*Xb6y2v+NaA|7VOUFN(0S=Xm|SoTRP_Q`bV-x4?Zbd9_dUedLq7i_-gBfgf! ziflO_uhOTLISJ<6PiQ!PbpL039Qw@jY>gV=ciI^x_u76(#oVvrPX_X+aAaTB-Rfh! zmv{3NEZ}3S-K-b*bj`EAd_(j80-X@}9L0Lw0=Cw&Y&f+nuy!uNQluh7Q6j zk?n-c7V5hZL{4%y0u4#ak~83tk2NmOYU(0q5=nea{6L3x96eJkI48#Qopacy|D|IP zN`1?hSzsr}^)fM8FL(QTd9eQ%#Gx3$jkl?vXxa}k=Da(km%aMg)5~7Xzw#HQj%Da& z(8sPDHubW3cr$6lRw&Xbb+Eru{}#PGq~FQ@q%5+W?aMi#_3|LPFL!f!ZAkR;UE_MW zxBqG%N1&HiF=mrE#x?$X=9tmXndqmL9@o!C(a$mTbEdDKm?*8EpJUy^A@0=R>|JzF z^lJe!6)0r--u* z-XHkn0l!4>wbejC4)8}N2N&N)JEg>8glp}SRj6-6tf`|P~4f4&p9QuI+ zx_=X!cENtl-cjPR14?Z48ugD^@%w&2+%={iqfK2?q)k0;w5cke#x(wq*QVN~Z@gc4 zEVNf~CcLyKJMlN$PSwhpa4u^{%T&1k)yniv2X!9;kU77+E{W#?6L>5ri8_m_25KbRN61PAm8u}!o!s2 zVVUA?d)8-HkMr=GMR=%wRGfz%?A7Ms6nk&*tsHGTQuLi2hK*VXeU@5D-?>q@0qHxJ zs}B7vHmZJn?aL};pFnRVR{L;m1?NE)*WE`A-EAG$T}SJ#>=`8vaDU6V?k=?DUa!%r zgkE>8_i%-D*hYunjt&z?8cO9whlT&P=6@C(_;EuST@SfiZ{;|hv(+O$o#`TN%%n58 zH$KgnQ`(r&N&C2;w&%C8>FJycowJ~m_2@%MO{e&fUR$FLEu;?|dcV(au`U0OXF1=Q z>GmdRFH`S{@cJg1CsBFUw4XcBTU;rwh~(G^ETs*4tI^(l9lsXUGDify(ZQa%dA58YO=A`m6V<2Cgk{)OISf9bd|@K zsY>>`$vN*8tQM+d->SqC)c#~oMLf>?4)qK?O7C^`xAcGZocZcEW%$Z7liAqMZ|D)Y zeZCrf(qpj^*~=_gI=ji>)-J~ul&kMg=1mx7-$?ZY~z zCi)<8;lDoP(Ic_QWa0*VIb~lC9yh~%dVeSNpBP)nnqT?t07vgtc#I))ANcg>hZl1W z=L`BuV$}P>c_zjs-dGHutV#Cl%HDzVtg})0Cry{8?F(*MXleK23JtD14Ul z6uMQj>t@oz^XzEWBL3Q&jWvEp#hep7cVugGGr7~^1mt{f)NQM^%GJ6>LYv!~vC^$6 zi}$Br_Rn80=R#LmQz-mmtfzHh(VX*XZnadbZKV}!ud!lVYOUH072unD!u{Acq4lR8t=-?U(%pXr zt#i+C7boLD=!>tg0$N}6V=4jvgkQp|4{G^k^}5ac*`&)M5c}9auiLQ5U+|n}U&Lbe zMO3j4Xg_Ocgy(tm`X@g|m$2c1thpm6d*!%oJ65}&n0_9cr`MpPGwavOS<1G%Ukw~y zzdqHldNpy2)o#N)?y3Jh%A7YZO`EXp*{=@YFR|a4O4PK#Az3ez;;gTo^2{8Sn|%`f ztx@($(c>E4rExK+`Z|7Rk>gzXW*?WNk!S2%p)8plV-AM3IfwbXU+9*1dFCK0^Kv&c zwh+DXcmN+%_2>-c#ZuD{jW&#*9}6$IC)MWsDcgo8_;7EaxJvb}rcbt-^6U}tbnW-E$zu!bMswMXHwm=<_b|!k9%x^~vV)w}Xujh#=yjfMFuQ`=)L|@ssjqwa=l(b+AY&bD30UbuH7xDV65a=y-fVZ43) z(AQa^!=k>)_;yv$`sxld*4@{rPS7`}eA}fCNIZHXF8?U8BRMnr@jhh0SnZ3<7kR1iww!tSneM>S?fVko4%Q&X`Ug2Hk-PtnEnuA7Y**s< zq^iWGSKHFw+<~>sPwikoTZ3{FA7l=|u~Sa7k~NDJ&Se<1KhkyqdREO*LHvi9FF0jB zOZptf@a%VDoUM79aD)1P$>*^fzmqbn(HYqfXfH_h+qU!*(x0#8&>bMeLRfoIGMKg^n_M_3#6C~Kr1>w~v_@VF0N z_rY`exYK2A^lhxg*L4c|@0SaE_MI>E?pvZxW=F9dEmw%%9vG5%n)nYf_Uk6;_x_%J zP$_38br4(=XKWrvrt6St4!$=b(?(?4fK0QutV#|67^?C3k0^A-Pzy*~@m`2p56 z-$47aum{}RcRXpIFEroNGM_srkDRkp1Ak--E#7`*u@lv+9A{T|azFPwY=^<1ZH;#y1?mNj!#Kc^JF$2s-yDI`jFMZ_}RyfUZHmDu}WRSQg6Zz6Bih=^SiqPUIlaTmE*BSX?HT0 zzX{r9-lThc-sJ!E`5|@m#sCxkYCwaR#&!cc^*-jng@-{q_=3MyB<9DQZIxs74jf9@ z>Z|C3wAt_+aggux7nv_u_dHcO;x~DBm`wPKa(*snY-_m(v`frdWS`25yAqzm_wt43 zW=~useO35AFW~!}W*zkN_&>i=z4g_;{>wf|>_XGA@iQ*6yWWg^#8#uH%ZNE@Itwbf z3R{OiQ>}kXJ727lESe+UwkEdfPw=Cjwhq;LYxA-VPg{e1bsRt@woUx`XRHH#75R<$ zIo05`xvQaCy@HHok7=Ou!8*S}!8^|xTKk~658Ab@8^brDPiB2yHSxP<*8Y36mEkAc zlE^mXY0FX<*^}IpnK)xcXp*vIT=^B~-T)MODAkqo-Q^fHbUg z!UhiZ+4xrCi_4v0^jox%1Kiittk#p>k@Zi^^<9DQyTG9r%Xq%B?6q91IG4je3pn6O zUAzrj06&~&zwUP{_QuuX5x!meKytgC*oeF6`-H!(*5FWHE#`g96<<}_Z@<4r%WM16 zhj;eR^zFa+Vy?s0h}gdK_#GC$r&8?$VjI3txJk=x?TBXuQPgZNk;z1e;G9rBcK zn>8Zd?}Yn3`U98#fcswPhvm*qZ=ZoTAL-rQfv4`u+|AIti7Oyn(dX!sHP z{ttAj*`GI%vvSgoWB+uDHC_RZxB2@PM29%*sILaO$e9-SkDN7KhHh;}N6Sa#teu+7 zM$S=}^N)8mR8(CY+X*eM>G-Xe5r z?Y>{>^+nEj`;+-8@$H`1G|5T&GCX=FUrxc4R669V$rz~xth5Zqd1P{0+~VZe(9PBZ>w&i>r|}elq7HexbBqX(Q5S z%6iM|O02h>*z*%zC#u_jk@ZHEdcGVD^!8yf^4f`9KhZB|Tge*kUle}G`tKafI$b6{ zp{GOKr{(7(4%yEcZY^Vt!|%=A)#O zc}__u--2843NCbQK+f6{`xK-L^!P69pvW?;D-+|=sqeBsIZsA6Gl>8ZKjW??z3zMO z0BawbiJN^!=FgZz&0~X+g}**R#{gNs@}^)+x!%<=zyX0o|quL?+bwu8$L^`#A19@seuwB03|!d>WfCew@tt$^GGJ$`k%W zgZ5*u|EYe5-tvyKK6D=9by4{T=U8}lLE@h^@f&rYBKXAy%ics?20KkF_HI*Of`*Fx z`-xXVChE6`FGgB-nNsiKKKk4sWzTSD@=xxp zJUI_JacWkM{gVPaCuL?FtEF)PWoE95#aCOg{#8~AdtJ`i=z`Bt$(5Bk#(ZedviNQt z$91zcb|q^lCB~3y-J((()pfeOlp(vRaEl+1VlR0)y5v-Y=6Bjda))H;n{%GEv_ zJ;wNb0r0dAnF%ejzn=9m+|y}k8uRGC%q1>pWP1a2?&L0Sng=qtTjhwaq%)l=?{J?t zdwqYyewjMfEZ52TcK8T#4!{S|n?j+M_zCy9#feWi&34NiTi@%>T5!&|`Y8Id%hhFM zvF@2W*o%?o`v(1;yHd``8D#!a&gv~!r_rAj@nDhlxx31nEVZ}ZUQ~t*H><+Tch^_6 zS{D00bc_l=qp9kDqO18qju2o!E&Dm->|S-X+((|GJ|Fe!HbuEXc`4$Ly1ZB~Ez;{F z&*4{#J@If`wtc>r-o;52%07V}oYwaLWGyzC9CD7D+TYTXb`H{3li1LM+@t&%Y^bY} zP8aX)hIh@h9b4Vt;}^VwU!8lg$9{YUb$gILt!8;)=F`jN%qNF|xU_4jU)phOkB&3y z!pg*VpdF^Gx+!}%Wp5;&LqEZIrPR6z>Bppf$=V3vz3@C8?;ZCYgihvfY zyAq`H-Ul)Fpnk%~fQPcq!=KK9n#k>y-Lnb&TW}Z zA5^W%t5VD*+-ix9D6je^vKz9>w>(3i#Jv|XcUCoGRc_&Y2=4GmC*DVUJ}}I_^x>9F z1^Jh$7-vD2XVUCT%?Xc4FY|4!q}#bq;$%WodFG_<7t7hZ8cS@n^xPrkRW{D0oDtG~ z0-QVbI&g`5yjJ$M4qXb{TY5uuYk@nuV3BHJ{tp6WqrQFPVcssDrI`0x|kvq2@>=R$$>CB7# zE>omAdU9SFzRQo`tK4OEh&h1=nHxB8^cP}hsz#@^OAA7FY2Dv@c1irq!px{`@!%F` zE9cQjH2Bv$d*qzuw~>#tMf-}`20DDhHh6f>p!XB)b*^t`q~GhqKQFhFsHmFTQdy_DbjsY>L!7SeG`Hec4jP zzC4MHll|{k!V9r$;cwwhz$33s%b683T_@A!&y64AzMW=uhdSWKGHaE?9HO-8nA9gX zpel1>3-%7LQLWh}ob6X$Rc58J88Q#fUXtOv;Gx*wRN{;pz54E+8vH!bZ*}Z&k2>^%}% zp-Ppj-I{*JP~zC>x(D?j4yWI}>38s*pE$Y} zqsgdYgvNNjQ@yd(Nxp;0r20=Z`PMw&UrR)B3-owds_{E7A_Ed zPqi+zb1HcycMWJ?Ar+scTDoJ*vAf8>E*smD(!4BpTua6a>Gqse9$&61+bfa(pj8=v zua(D)q}I7{XK7y9lnuDROw?dP@ln15Ga z(KyK1@3htn?St*jt6xak{tnh5ZRV^8BC{%uzxK6yv1!d}bUdw>uGtR!nZGRL-y~zI z318+3?aMR>zJVN^k~RA=+V%m)(NC-S>U2z>^OM1MqYc+#2a=566ld(Z^xf-xzO=;Q zAA64JU$U0W#f#o!ProgA@jbQa=cmAVlJVRM^g#B& zJMrFbZOhQ-%J>j*5%iiqf-!4jiv$1hr)8X$%3F516T_~IB0FM=nVIUpQf4Llq|VWf zYvR{v8$a9EubRXMY?UwLy=%+pWnGrz9xURRgv_J6?sZES?t2U%w|m3{E+#_#My zG5fIh+=bhRf~36w`{1Q3Z6BHh-)mqWo)f&UvwiqJWnPMX@OXA{_94hq%s%*XcvbC# z;QXhs4^pn!2QOwatEny*qu)qbHHll8L$a+JY_Y`nIf${xULVKi&9W@@jrzjO_gq=g zmbBjL?=ef`Po;golre&U%Q3LkHc>dMAAFIxKLCF)RPx%A(>JGX{C?#?xg--#(XoX$BtTIivjRBIjm&YKb>=U^qM)T4)QSv>SDLSC2|Cz5>tqnff`KaaMD~QTQWw zSx78}kqGszM(6Qksp}knK03f7dLZLEbtKs%`9wBvQ+qgvb0a#~k@av97k-C%q=caH#e5&I<+6YMtuWWN?TldRgCz2OCuhx^Ca@F zwPLL&;LX>v=|n4QjYapiWL2W9DGjWWAV4Lv{*{e;7z>p{&t}f5$5(YefzKP`dmZC5 zTl1nWyGIdO=e&+)B{W;iX_l$Gbvux_kDR5X+jD$S^IFoWbGv);1-UDHP_Oa53!gfW z^-0DpinD~+Yp%Wzz8v~|at=D}rO|j#vUW%M0+E52CPqwQz_*Em-HTMp8+RwVR13cR z61BNe^jcy7j@q1M1%Fdz=33fUIepKN>Mff=I~dfqQ}Btd>vpYepNy+|J=z%)P0Xcx zYe86Fo=HJZ9qmD4U2;ZNOh?rlkO4{|5+KRCqv zn=P?!{zkA-#7+iv-SAwfr|n8T<+(1tfX27bl`(A$*}YjUuvyb>C-RKo2c*V_Sx=<& z+`Ct|9A{Vre3Q7#;E2RuJiZN5)0dY%ZCG{+Nt;aK zvjm?N+y8X?icMQPwr^i?Ti1@@?d$iwqI=uzo22L!on3dne#KsJNfY#KyZw$GT^F41 z^-4HF-{xC3erU(GTi#$KuISpaX~nj#?YD2ebNQy*Z`=HO@#R9x3F-f~%^$gC>(&d+ z_$s9fmA`T89bG$azkSD+R|)k6p;@ux*6tOzcHRDOKD3ddKXmKuoo_4!T5-qrZ7VuE zc66+`^|l==ws(FgyW)LN`-dM`vGw+ww_i{d|49>>{N3?}qquh#C$95%%M433@;3t? z=@0o$=Sm-EeuR-TLfq{9mZXY)e)^29p5|}h1@Ye|K9f@i*e6n4{L|_G;+1hBbT$69 zN7oUdD@2+#saT;)c0XRq=VSj>3b56_)-tu`Yz$x7_~diU)$qxmr>p?t@bDJ?mhjiF z;ROTU7=j;fiRPEd${_#C8a~g+--ZVgz}x;*@)t(K z{-;CufBL>CJR72K?;i+!rjg$X!CU`O;CC7D?hriJCUCQUVmJf(VmC~Wzgxpi`j3V3 z=ie{+P5!rr>qnSUsWk?DxlsP^-6-%f1Kt*bx2_j>^b^qkz5fu+?~puzZ}9RP_!$C> z!`uF&&x0UOfNeni} z3i#Ld;V7KuISxPe5rG%y-^&_q>hAy{t^ogIw@2|4f*FSoe>93eT>hAboBDAqMBmo! zlHb&?y&?Qh-XU;PKb=ti(L1B`WqB3Q-}G65oBR#u&zFFYm4F`{5d2FF{Rzu&$DqKY zpMbue67XFbZr1-<&;|Jq4hen}?u6j84^9v7)^JlkVfsfk+|=K2``CI&@SE}r+lT8P z)b%&`6V9JKqT%2$KSBNcw?*?edHIXNA1HzUpoSOMkND?=zT)+7&~TG>`4!NAy@s3k z!|mhr7ozyX{OcZ$!o&O<*6`xxKjy)U^2g6v6#ncK`NR4#`ynmAqWL#W0T1W@w1%7f z4a1$I)8p^fa5KLX;=l9o^!#ghM8ikJ{*CbWt^A39HT%)&%dhv~q%=Q){2NNZn>73` zgMVTBXkwlC72t1se0us`(r{D%20#hg=` z&v|fzKVkjv)^OAQ5o8^&pO3g`{uidmzlBTz{rkQo^qKZC+qjmGm$Ce0|1Ye+Pir_yCVc|@{%fQBF#nxz3;yEz z@#!hx4mkq+&a;x=tbdrkT^e2-KbhrIl)lB^iSmaD(DCw*{bzxj<1<1L<`uNbfv5Zh<*b4a?GVn^$FkyHQbcHLxuppaIWMx%WsF|0A61+1w52L!zpvp^hVcpVuk+xBe8cU}&r}rNuJfDv z6PEwWQ{WHx4^!a}!xyr?cDnxMc&fe8@;5X@S48g0e zo4)*34LAG$aQPA!p4>k;A^iK^EpW5_hT+b81a7v^)=>T%-xtLnmVd(^2)uav@6_;u z!N2wpe$_U8`Ob|RZsgB}@>l(_E}u8%C*c3VKZ)WWC20Vk-J$av_?-|uI|V%4zDOz` zBYzk^uu<@v{140Tw1%7ZCk)@(Ieq;GG~AS5juM0V4SZPF-=Ht7e_3(KX8oQG z!JpOe;_^-WTgh+oKV1LseSCWQ$UNmIsNa#hB>!E8{KD;TEHCil`lIgFF zcfc5we{c$TSU*os0T1JE+!ZZ9oIgJWJe>bv2{@!q`3dNs63EMcVeq2z{Z~@|NHzb3 z$@gD){Y?9wh_=rX`duo&>3^_P`GVX3;@wgE74Cm>9=xc0_Dung*3YCrjNe0S_OD_4 zA1T;>@#^Zo*}piHVd6LY&#?XW$T8~|t$(TfX8#?{Z^EPHoAgKFCVj6y+^k=eeHxqWmy@ettuLqx>l;{|FgD`5wTu z57F{X|A0e0#`*h_rr&J8Q_xqU{0Mj)zXvh#N9i;BuMC;Y`k8Pi0yptT=`YTo_CK3m zfA(p(SBvm}lfL5k7k@H}KV1KtG~66thvnn$ll*4=9Nw7ao9!!%{~4XX`1tzS|4Z-} z*S~WfT+$eyp!}Qek^H6PQwna@FDk#{@bBvK&H9J)XZnP`;&880CjK!0{7M&Xzxy?Q zKUL@};E(E`p1)oelRr`YH}xxAzr`AVakz)S6g)(@|2R&+{%z`KBkxSOsXtB$c$j{T zeo}m?J2bzu&f}k#EcAa;`5}n+Xu?hUyGb$~d_x3owx1~6w69_KvzmT0e=dUG#H_I=({-lxk&y}<(v8wZQo}8i^p%;|7iW! z81jwU?@Mi;X8#avKbKm5sr;qnS4uxkc$og1{#@*bX@8^gGvym@|HpNHlfU8q^`-~3 zeKO<|&Yv#AXWQ|CA7Yo)jVfB&H=_!EMAl$rEL%P$2t^=pdy8~h97U-)3O{!`?S zFfgc}J4OC5em`T;@^8AZ{6)(zh5ug&yj1-!6<$<65ybwBK>q&E*E;{D;*V5g68)v% zms)KAnBB!0RM0a`kvKr zlfS=(enn^q%5NGL{*>~+BH73J6MsnaH~} zpC)tXUz>d{)xVa4zrOunBjTE_{YT;E_^mZJIll?t5P_G{4>NyvB)VKH{`yn?tKfhUahc5&BK~x+CHr1~5AyG!IR1uum^%HK5x|8gbDp8{^=Z@l>Y zM*XAuXUab+-z7%=Jms7GHSJfFf2Mv!>u18Vk@}UQ?^5BW{s{eM{Yv3CA|BX)n#rc;Gl{Wn^ zlmD`R+~mIr?TO#!{ z?L!p5Dc@-S^G2sH!v0C}|Lx)K!_&_nMlpMDO!=2;->)^il>AG|H)V*_wg{^tBbDf*)DQuLYp$(69LT;nx~J~RK32>m8p z>TlN1gp2();idR*=FgU(UzMPLf24d9e^fpuJZe8p{fgShf}wv={WS5nL7%BVX8x#s zE?K^zpK?C+B>V=vy##;8Lh!7%e|H)6%Z1<@9)C6LV=4VG+gF(X-@TynP5z&Qev?1E z2z?x$1N|L%Lh4t%{=;9IzW%fSVtRO}SmQA$-+yWFKU{wgZr1NvUXIuA%TfBXz$fAN zi<$Vk6!>Tae{uTU(bt7OL;n-dH_jig$_8BcGYRgc3jLqNpQ7?v_v8i1-^gD@Ig{{* zO7dNKe>LT_;0|&@@w$uD_Z}y3!^_m;3WQ*f)}MPlF@%L34c-g zU!VM??C(D{+~h)zpOg4!Bq#-UO632ihC|-8^?R-1;rh@1+6A?LA<6hmCm%^@!cG0k zm4G|1JDdVTpB&F7{gw*PAGSu|q0BrwZ*aI!Yt~QRn4i}Y?od>Ke`^W-d!Yo}qHL%2MdK$!=;QEg(!a*&pK=j6FG3%O zXET2Y=syPVPk;SF?8_3P{7dy;FNVG-<=&fse(%>Cojy~4+92Ab->ju`(Qp%gH~C** z{VW*zQL6nK^?Q1H{d>LyoT~CM%g^!VQur$vr^_FO-(}Q48b6R66Q3N9)8ThYzyVL2 zzZ6{j_vy;dy~glT^j!?R{TtKkkKmf>GhO{9{c8z#E)jn?|1;lw_3IzT52;grUS0l} z_N$!|r^EkSjL#!=m=3=r{vF)Af68Z^Vc(|UzX6X&_-D#@O9XC?Kce=@%%6+oH{uY*6EwszchvvX(Hw(Ymvew(vug>An%FSI}o!fWZzUd>* z>eUUKHec28wyw8t%C26$vg@kNtKPn{t7CI^<>pPR-}d&#cdTmIylGR{RZG?Nx7_xT zu5IshuD^cWg_gH<>z%in#WlX8ar4_(Wmj+9yyv~BTTxr>oyW>-?Dwj zwjCWCgQisSo9RlmOes0`Pi?R){*;nFGWf)Kkl)fTm;33uNhkL-htqMass4Qh--2@$ ze|t$M_vyYBP6QvpC!ZvLl1|D!vdrR7sq-OoEcoQJg1;c$!R3}ZxLm2Wxf-PZ1?jHx z(?O$BSA+Fx@ZKvqy^qi@pEdjm4P|6J^6fJIEEV7oc<_7;&yrh6I`-`tf0o)hlOO&k z!1rE$`{@$SbKIf+oTc$kPS?f@T`u1_(q%~3`Tii4QjY()p6>~&l}tPk3IgRRa?B-3R{c^~C3#C~-PcZ>hKN$KS0{|}NaO&kCK literal 0 HcmV?d00001 diff --git a/patch_v2/meow_netmgrd/meow_netmgrd.c b/patch_v2/meow_netmgrd/meow_netmgrd.c new file mode 100644 index 0000000..9011264 --- /dev/null +++ b/patch_v2/meow_netmgrd/meow_netmgrd.c @@ -0,0 +1,1756 @@ +#define _GNU_SOURCE /* struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */ + +/* meow_netmgrd.c — MeowOS 网络管理专项服务(devdocs/plan/NETMGR_DESIGN.md,期 1 骨架)。 + * + * 定位(与 powermgr/audiod 同级的系统服务):接口统一层——WiFi/BT/USB-eth/rfkill/IP/路由/DNS + * 的查询与控制收口为一个系统权威;系统设置、MeowPanel、未来系统组件一律走它的 IPC,不再各自直调 + * wpa_cli/hciconfig(设计 §1/§3)。期 1 范围(设计 §13 表,→ M6.0):骨架 + 接口统一层 + + * CLI(status/iface/wifi/bt/rfkill) + IPC(E0-E7) + 日志框架 + svcd conf + HibyOS 只读降级。 + * + * 单一二进制双形态(同 powermgr/svc_power): + * - svcd 空参拉起(板载 services.d/meow_netmgrd.conf,trigger=boot/restart=on-fail)→ + * IPC 服务常驻:bind /usr/data/meow_net.sock(--sock/MEOW_NET_SOCK 覆盖),收 + * MSG_HELLO→MSG_ACK、MSG_NET_STATUS_REQ/IFACE_REQ/WIFI_CTL/BT_CTL。 + * - `meow_net `(argv[0]=meow_net 双形态)或 `meow_netmgrd ` → 一次 CLI(IPC 薄壳)。 + * CLI 是 IPC 客户端:查询经守护,写操作经守护鉴权;守护不可达 → 报错(help 除外,help 本地必可用)。 + * + * HibyOS 只读降级(用户定案 2026-09-01):进程内读 /tmp/meow_boot_mode,"meow"=全功能; + * 否则(hiby/缺失)=只读——读级查询放行,一切写操作(wifi/bt on-off 等)拒 + 记日志。 + * (与 audiod 的 exit(0) 门控不同:本服务读侧无害故常驻只读,每采样拍重判可热切换,同 powermgr。) + * + * 内核事实(NETWORK_HW_FACTS.md,期 0 实测):无 iptables/cgroup/conntrack;内存 available≈27MB → + * 本服务 RSS 预算 ≤1MB(单线程 poll、静态表、热路径禁 malloc;popen 有界收割防 wedge,257 教训)。 + * + * 日志(§8):TF `MeowOS/logs/net_.log`(无卡降级 /tmp),每次开机新文件(复用 /tmp/meow_bootts + * 共享 bootts,一次开机一份);接口状态变化/写操作必记。行格式 `bootts lvl tag kv...`。 + * + * 编译(mipsel 静态):build_netmgrd.sh;宿主:build_netmgrd.sh host。 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meow_ipc.h" /* 帧格式/消息号 + MSG_NET_*(0x00E0-0x00E7) + m_net_*(号段 0x00E0-0x00EF) */ + +/* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */ +static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */ +static char g_netdev[160] = "/proc/net/dev"; /* MEOW_NETDEV */ +static char g_net_route[160] = "/proc/net/route"; /* MEOW_NET_ROUTE */ +static char g_fibtrie[160] = "/proc/net/fib_trie"; /* MEOW_FIBTRIE(无 ip 工具兜底) */ +static char g_resolv[160] = "/etc/resolv.conf"; /* MEOW_RESOLV */ +static char g_rfkill_dir[128] = "/sys/class/rfkill"; /* MEOW_RFKILL_DIR */ +static char g_bt_dir[128] = "/sys/class/bluetooth"; /* MEOW_BT_DIR */ +static char g_boot_mode[160] = "/tmp/meow_boot_mode"; /* MEOW_BOOT_MODE */ +static char g_log_dir[192] = "/usr/data/mnt/sd_0/MeowOS/logs"; /* MEOW_LOG_DIR(TF 卡日志根) */ +static char g_bootts_cache[160]= "/tmp/meow_bootts"; /* MEOW_BOOTTS_CACHE */ +static const char *g_sock = "/usr/data/meow_net.sock"; /* MEOW_NET_SOCK/--sock */ + +/* 后端命令路径(宿主测试用桩脚本覆盖;默认走设备 PATH 的真工具) */ +static const char *g_wpa_cli = "wpa_cli"; /* MEOW_WPA_CLI */ +static const char *g_hciconfig = "hciconfig"; /* MEOW_HCICONFIG */ +static const char *g_bluetoothctl= "bluetoothctl"; /* MEOW_BLUETOOTHCTL */ +static const char *g_ip = "ip"; /* MEOW_IP */ +static const char *g_wifi_if = "wlan0"; /* MEOW_WIFI_IF */ + +#define NET_POLL_SEC_DEF 2 /* 接口轮询/状态节拍(秒;MEOW_NET_POLL_SEC 覆盖,测试用) */ +#define RUNCMD_TMO_MS 1500 /* 后端命令(wpa_cli/hciconfig/etc)有界收割限时(ms) */ +#define IFACE_MAX 16 +#define RFKILL_MAX 8 +#define DNS_MAX 8 + +static int g_poll_sec = NET_POLL_SEC_DEF; + +static long mono_now(void) +{ + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec; +} + +/* ---------------- 极简日志(stderr;svcd supervisor 捕获) ---------------- */ +static void svc_log(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "[meow_netmgrd] "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +/* ---------------- 文件小工具(同 svc_power) ---------------- */ +static int read_file_txt(const char *path, char *buf, int cap) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + int n = 0; + while (n < cap - 1) { + ssize_t r = read(fd, buf + n, (size_t)(cap - 1 - n)); + if (r <= 0) break; + n += (int)r; + } + close(fd); + buf[n] = 0; + while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r' || buf[n - 1] == ' ')) buf[--n] = 0; + return n; +} + +static int read_file_int_def(const char *path, int def) +{ + char buf[32]; + if (read_file_txt(path, buf, sizeof buf) <= 0) return def; + return atoi(buf); +} + +/* ---------------- 日志框架(§8:每次开机新文件,TF 优先降级 /tmp) ---------------- */ +static int g_logfd = -1; +static char g_logfile[192]; + +/* bootts 复用(与 launcher/audiod 共用 /tmp/meow_bootts 缓存,一次开机一份日志) */ +static void get_bootts(char *out, size_t n) +{ + int fd = open(g_bootts_cache, O_RDONLY); + if (fd >= 0) { + int r = read(fd, out, (int)n - 1); + close(fd); + if (r > 0) { out[r] = 0; return; } + } + time_t now = time(NULL), up = 0; + fd = open("/proc/uptime", O_RDONLY); + if (fd >= 0) { + char buf[64]; + int r = read(fd, buf, sizeof buf - 1); + close(fd); + if (r > 0) { buf[r] = 0; char *p = strchr(buf, ' '); if (p) *p = 0; up = (time_t)atof(buf); } + } + struct tm tm; + localtime_r(&now - up, &tm); + strftime(out, n, "meow_%Y%m%d_%H%M%S", &tm); + fd = open(g_bootts_cache, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { (void)write(fd, out, strlen(out)); close(fd); } +} + +static void log_open(void) +{ + char ts[64]; + get_bootts(ts, sizeof ts); + char path[260]; + int use_tf = 0; + if (access(g_log_dir, W_OK) == 0) { + snprintf(path, sizeof path, "%s/net_%s.log", g_log_dir, ts); + use_tf = 1; + } else { + snprintf(path, sizeof path, "/tmp/net_%s.log", ts); + } + snprintf(g_logfile, sizeof g_logfile, "%s", path); + g_logfd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0644); + svc_log("log: %s (%s)", g_logfile, use_tf ? "TF" : "/tmp fallback"); +} + +/* 日志行:`bootts lvl tag kv...`;stderr 与文件双写(svcd 捕获 stderr + 文件归档)。 */ +static void net_log(const char *lvl, const char *tag, const char *fmt, ...) +{ + char msg[512]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof msg, fmt, ap); + va_end(ap); + char bootts[64]; + get_bootts(bootts, sizeof bootts); + if (g_logfd >= 0) { + char line[600]; + int n = snprintf(line, sizeof line, "%s %s %s %s\n", bootts, lvl, tag, msg); + if (n > 0 && n < (int)sizeof line) (void)write(g_logfd, line, (size_t)n); + } + fprintf(stderr, "[net] %s %s %s\n", lvl, tag, msg); +} + +/* ---------------- 有界 popen 收割(257 教训:命令 wedge 时无条件 waitpid 会拖死调用线程) ---------------- + * 非阻塞读子进程 stdout(poll 限时);读完后 WNOHANG 优雅等 300ms,不死 SIGKILL 再等 300ms; + * 仍不死(D 态)放弃收割(泄漏一子进程换线程不卡死,已记日志)。返回读到的字节数(-1=起不来)。 + * cmd 为完整 shell 行;buf 由调用方提供(静态/栈),不 malloc——热路径禁 malloc。 */ +static void reap_bounded(pid_t pid, const char *cmd) +{ + for (int i = 0; i < 30; i++) { + if (waitpid(pid, NULL, WNOHANG) == pid) return; + struct timespec ts = { 0, 10 * 1000000L }; + nanosleep(&ts, NULL); + } + svc_log("run_cmd: child %ld not exit in 300ms, SIGKILL (%s)", (long)pid, cmd); + kill(pid, SIGKILL); + for (int i = 0; i < 30; i++) { + if (waitpid(pid, NULL, WNOHANG) == pid) return; + struct timespec ts = { 0, 10 * 1000000L }; + nanosleep(&ts, NULL); + } + svc_log("run_cmd: child %ld unkillable (kernel D-state?) — abandon reap (%s)", (long)pid, cmd); +} + +static int run_cmd(char *buf, int cap, int tmo_ms, const char *cmd) +{ + int p[2]; + if (pipe(p) < 0) return -1; + pid_t pid = fork(); + if (pid < 0) { close(p[0]); close(p[1]); return -1; } + if (pid == 0) { + dup2(p[1], 1); + int dn = open("/dev/null", O_WRONLY); + if (dn >= 0) { dup2(dn, 2); close(dn); } + close(p[0]); close(p[1]); + execl("/bin/sh", "sh", "-c", cmd, (char *)0); + _exit(127); + } + close(p[1]); + fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL, 0) | O_NONBLOCK); + int off = 0; + int remain = tmo_ms; + while (remain > 0 && off < cap - 1) { + struct pollfd pf = { p[0], POLLIN, 0 }; + int r = poll(&pf, 1, remain); + if (r < 0) { if (errno == EINTR) continue; break; } + if (r == 0) break; + ssize_t rd = read(p[0], buf + off, (size_t)(cap - 1 - off)); + if (rd == 0) break; + if (rd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; break; } + off += (int)rd; + } + close(p[0]); + buf[off] = 0; + reap_bounded(pid, cmd); + return off; +} + +/* ---------------- JSON 构建(vsnprintf 助手法,截断分析免疫;同 powermgr) ---------------- */ +typedef struct { char *buf; size_t cap, off; } jw_t; +static void jadd(jw_t *w, const char *fmt, ...) +{ + if (w->off >= w->cap - 1) return; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(w->buf + w->off, w->cap - w->off, fmt, ap); + va_end(ap); + if (n > 0) { + w->off += (size_t)n; + if (w->off >= w->cap) w->off = w->cap - 1; + } +} + +/* 带引号的字符串值(转义 `"`/`\`/换行)。空串 → ""(不当 null,调用方自决)。 */ +static void jadd_q(jw_t *w, const char *key, const char *val) +{ + jadd(w, "\"%s\":\"", key); + for (const char *p = val; *p && w->off < w->cap - 3; p++) { + if (*p == '"' || *p == '\\') jadd(w, "\\%c", *p); + else if (*p == '\n') jadd(w, "\\n"); + else if (*p == '\t') jadd(w, "\\t"); + else w->buf[w->off++] = *p; + } + jadd(w, "\","); +} + +static void jadd_int_or_null(jw_t *w, const char *key, int v) +{ + if (v < 0) jadd(w, "\"%s\":null,", key); + else jadd(w, "\"%s\":%d,", key, v); +} + +static void jadd_str_or_null(jw_t *w, const char *key, const char *s) +{ + if (!s || !s[0]) jadd(w, "\"%s\":null,", key); + else jadd_q(w, key, s); +} + +/* ---------------- 动作执行结果容器 ---------------- */ +typedef struct { + int code; + char text[MEOW_IPC_MAX - 8]; +} ctl_result_t; +static void ctl_reset(ctl_result_t *r, int code) { r->code = code; r->text[0] = 0; } +static void ctl_sets(ctl_result_t *r, int code, const char *s) +{ + r->code = code; + snprintf(r->text, sizeof r->text, "%s", s); +} + +/* ---------------- 接口统一层:模型(设计 §3 统一模型) ---------------- */ +typedef struct { + char name[32]; + char kind[8]; /* "lo" | "wifi" | "usb-eth" */ + int up; /* 1=up 0=down */ + char ip[40]; /* ""=null */ + char gw[40]; /* ""=null */ + long rx, tx; /* 累计字节(/proc/net/dev) */ +} iface_t; +static iface_t g_ifaces[IFACE_MAX]; +static int g_iface_n = 0; + +typedef struct { + int connected; /* 0/1 */ + char state[16]; /* wpa_state 值;""=unknown */ + char ssid[64]; + char ip[40]; + int rssi; /* 信号强度 dBm(可为负);has_rssi=0 时无效 */ + int has_rssi; /* wpa_cli 是否给出 signal= */ +} wifi_t; + +typedef struct { + int available; + int powered; + char address[32]; + int paired_n; /* -1=null */ +} bt_t; + +typedef struct { + char type[16]; /* wlan/bluetooth/... */ + int soft; /* 0/1/-1 */ + int hard; /* 0/1/-1 */ +} rfkill_t; +static rfkill_t g_rfkill[RFKILL_MAX]; + +static int boot_mode_is_meow(void) +{ + char buf[16] = ""; + if (read_file_txt(g_boot_mode, buf, sizeof buf) <= 0) return 0; /* 缺失=只读等落地 */ + return strcmp(buf, "meow") == 0; +} + +/* kind 判定:0=lo 1=wifi 2=usb-eth(热插拔出现即记录)。 */ +static int iface_parse_kind(const char *name) +{ + if (strcmp(name, "lo") == 0) return 0; + if (strcmp(name, g_wifi_if) == 0) return 1; + return 2; +} +static const char *kind_str(int k) +{ + return k == 0 ? "lo" : (k == 1 ? "wifi" : "usb-eth"); +} + +/* /proc/net/dev 单接口行 → rx(列1)/tx(列9) 累计字节;返回 0=找到。 */ +static int netdev_iface(const char *ifname, long *rx, long *tx) +{ + FILE *f = fopen(g_netdev, "r"); + if (!f) return -1; + char line[256]; + int found = -1; + while (fgets(line, sizeof line, f)) { + char *c = strchr(line, ':'); + if (!c) continue; + *c = 0; + char *n = line; + while (*n == ' ' || *n == '\t') n++; + if (strcmp(n, ifname) != 0) continue; + char *save = NULL; + char *tk = strtok_r(c + 1, " \t", &save); /* 列1 = rx bytes */ + long vr = tk ? strtol(tk, NULL, 10) : 0; + for (int i = 0; i < 8; i++) tk = strtok_r(NULL, " \t", &save); /* 推进到列9 */ + long vt = tk ? strtol(tk, NULL, 10) : 0; + if (rx) *rx = vr; + if (tx) *tx = vt; + found = 0; + break; + } + fclose(f); + return found; +} + +/* 全局 /proc/net/dev RX/TX 累计(所有接口求和)。 */ +static void netdev_totals(long *rx, long *tx) +{ + long trx = 0, ttx = 0; + FILE *f = fopen(g_netdev, "r"); + if (f) { + char line[256]; + while (fgets(line, sizeof line, f)) { + char *c = strchr(line, ':'); + if (!c) continue; + char *save = NULL; + char *tk = strtok_r(c + 1, " \t", &save); + long vr = 0; + if (tk) { + vr = strtol(tk, NULL, 10); + for (int i = 0; i < 8; i++) tk = strtok_r(NULL, " \t", &save); + if (tk) ttx += strtol(tk, NULL, 10); + } + trx += vr; + } + fclose(f); + } + if (rx) *rx = trx; + if (tx) *tx = ttx; +} + +/* iface IP:优先 `ip -o -4 addr show dev `;ip 缺失/无输出 → fib_trie 兜底(作用于主口)。 + * 内核不提供 per-iface 主机 IP 的 /proc 文件,fib_trie 兜底无法映射接口——仅作用于 wifi 主口。 */ +static int ip_parse_addr(char *out, size_t cap, const char *buf) +{ + const char *p = buf; + while (*p) { + while (*p == ' ' || *p == '\t' || *p == '\n') p++; + if (!*p) break; + const char *tk = p; + while (*p && *p != ' ' && *p != '\t' && *p != '\n') p++; + size_t tl = (size_t)(p - tk); + const char *dot = memchr(tk, '.', tl); + if (!dot) continue; + const char *sl = memchr(tk, '/', tl); + if (!sl) continue; + size_t len = (size_t)(sl - tk); + if (len >= cap) len = cap - 1; + memcpy(out, tk, len); out[len] = 0; + return 0; + } + return -1; +} + +static int fib_trie_first_ip(char *out, size_t cap) +{ + FILE *f = fopen(g_fibtrie, "r"); + if (!f) return -1; + char line[160]; + char last_local[48] = ""; + while (fgets(line, sizeof line, f)) { + char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (!isdigit((unsigned char)*p)) continue; + char ip[48]; int i = 0; + while (p[i] && p[i] != ' ' && p[i] != '\n' && i < 47) { ip[i] = p[i]; i++; } + ip[i] = 0; + if (strchr(ip, '.')) snprintf(last_local, sizeof last_local, "%s", ip); + } + fclose(f); + if (last_local[0]) { snprintf(out, cap, "%s", last_local); return 0; } + return -1; +} + +static void iface_ip(const char *ifname, int want_fallback, char *out, size_t cap) +{ + out[0] = 0; + char cmd[192]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s -o -4 addr show dev %s 2>/dev/null", + g_ip, ifname); + char buf[256]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0 && ip_parse_addr(out, cap, buf) == 0) return; + if (want_fallback) (void)fib_trie_first_ip(out, cap); +} + +/* /proc/net/route → 该接口网关(RTF_GATEWAY,优取默认路由)。 */ +static void iface_gw(const char *ifname, char *out, size_t cap) +{ + out[0] = 0; + FILE *f = fopen(g_net_route, "r"); + if (!f) return; + char line[256]; + char best[40] = ""; + while (fgets(line, sizeof line, f)) { + char ifc[32]; unsigned long dest = 0, gw = 0, flags = 0; + int n = sscanf(line, "%31s %lx %lx %lx", ifc, &dest, &gw, &flags); + if (n < 4) continue; + if (strcmp(ifc, ifname) != 0) continue; + if (!(flags & 0x2)) continue; /* RTF_GATEWAY */ + unsigned char b0 = (unsigned char)(gw & 0xff), b1 = (unsigned char)((gw >> 8) & 0xff); + unsigned char b2 = (unsigned char)((gw >> 16) & 0xff), b3 = (unsigned char)((gw >> 24) & 0xff); + snprintf(best, sizeof best, "%u.%u.%u.%u", b0, b1, b2, b3); + if (dest == 0) break; /* 默认路由:最佳 */ + } + fclose(f); + if (best[0]) snprintf(out, cap, "%s", best); +} + +/* resolv.conf → nameserver 列表(≤DNS_MAX)。 */ +static int dns_read(char out[DNS_MAX][40], int *n) +{ + FILE *f = fopen(g_resolv, "r"); + *n = 0; + if (!f) return -1; + char line[160]; + while (fgets(line, sizeof line, f) && *n < DNS_MAX) { + char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (strncmp(p, "nameserver", 10) != 0) continue; + p += 10; + while (*p == ' ' || *p == '\t') p++; + char ip[40]; int i = 0; + while (*p && *p != ' ' && *p != '\t' && *p != '\n' && i < 39) ip[i++] = *p++; + ip[i] = 0; + if (strchr(ip, '.')) { snprintf(out[*n], 40, "%s", ip); (*n)++; } + } + fclose(f); + return *n > 0 ? 0 : -1; +} + +/* 扫描 /sys/class/net。detail=1 补 ip/gw(触发 ip popen);detail=0 仅 name/kind/up/rx/tx(节拍用,无 popen)。 */ +static void enum_ifaces(int detail) +{ + DIR *d = opendir(g_net_dir); + g_iface_n = 0; + if (d) { + struct dirent *de; + while ((de = readdir(d)) && g_iface_n < IFACE_MAX) { + if (de->d_name[0] == '.') continue; + iface_t *f = &g_ifaces[g_iface_n]; + memset(f, 0, sizeof *f); + snprintf(f->name, sizeof f->name, "%s", de->d_name); + int ki = iface_parse_kind(de->d_name); + snprintf(f->kind, sizeof f->kind, "%s", kind_str(ki)); + char p[192]; + snprintf(p, sizeof p, "%s/%s/operstate", g_net_dir, de->d_name); + char os[16] = ""; + read_file_txt(p, os, sizeof os); + /* lo 的 operstate 常为 "unknown"(真机实测),但回环恒 up。 */ + f->up = (ki == 0 || strcmp(os, "up") == 0) ? 1 : 0; + long rx = -1, tx = -1; + netdev_iface(de->d_name, &rx, &tx); + f->rx = rx >= 0 ? rx : 0; + f->tx = tx >= 0 ? tx : 0; + if (detail) { + iface_ip(f->name, ki == 1, f->ip, sizeof f->ip); /* fib_trie 兜底仅 wifi 主口 */ + iface_gw(f->name, f->gw, sizeof f->gw); + } + g_iface_n++; + } + closedir(d); + } + /* 排序稳定:lo 首、wifi 次、usb-eth 末(冒泡,量小)。 */ + for (int i = 0; i < g_iface_n; i++) + for (int j = i + 1; j < g_iface_n; j++) { + if (iface_parse_kind(g_ifaces[i].name) > iface_parse_kind(g_ifaces[j].name)) { + iface_t t = g_ifaces[i]; g_ifaces[i] = g_ifaces[j]; g_ifaces[j] = t; + } + } +} + +/* ---------------- WiFi:wpa_cli 包装(popen 有界) ---------------- */ +static void wifi_status(wifi_t *w) +{ + memset(w, 0, sizeof *w); + w->rssi = -1; + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s status 2>/dev/null", g_wpa_cli); + char buf[512]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) return; + char *ln = buf; + while (ln && *ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + if (!strncmp(ln, "wpa_state=", 10)) { + snprintf(w->state, sizeof w->state, "%s", ln + 10); + if (!strcmp(ln + 10, "COMPLETED")) w->connected = 1; + } else if (!strncmp(ln, "ssid=", 5)) { + snprintf(w->ssid, sizeof w->ssid, "%s", ln + 5); + } else if (!strncmp(ln, "ip_address=", 11)) { + snprintf(w->ip, sizeof w->ip, "%s", ln + 11); + } else if (!strncmp(ln, "signal=", 7)) { + w->rssi = atoi(ln + 7); + w->has_rssi = 1; + } + if (!nl) break; + ln = nl + 1; + } +} + +/* ---------------- 蓝牙:hciconfig/bluetoothctl 包装 ---------------- */ +static void bt_status(bt_t *b) +{ + memset(b, 0, sizeof *b); + b->paired_n = -1; + char p[192]; + snprintf(p, sizeof p, "%s/hci0", g_bt_dir); + if (access(p, F_OK) == 0) b->available = 1; + if (!b->available) return; + /* powered:/sys/.../power(on/off)优先,回退 hciconfig 行首 UP/DOWN */ + snprintf(p, sizeof p, "%s/hci0/power", g_bt_dir); + char pw[8] = ""; + if (read_file_txt(p, pw, sizeof pw) > 0 && pw[0]) { + b->powered = !strcmp(pw, "on"); + } else { + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 2>/dev/null", g_hciconfig); + char buf[256]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) { + char *ln = buf; + while (ln && *ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + char *q = ln; + while (*q == ' ' || *q == '\t') q++; + if (!strncmp(q, "UP", 2)) { b->powered = 1; break; } + if (!strncmp(q, "DOWN", 4)) { b->powered = 0; break; } + if (!nl) break; + ln = nl + 1; + } + } + } + /* address:/sys/.../address 优先,回退 hciconfig "BD Address:" */ + snprintf(p, sizeof p, "%s/hci0/address", g_bt_dir); + read_file_txt(p, b->address, sizeof b->address); + if (!b->address[0]) { + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 2>/dev/null", g_hciconfig); + char buf[256]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) { + char *q = strstr(buf, "BD Address:"); + if (q) { + q += 11; + while (*q == ' ') q++; + int i = 0; + while (*q && *q != ' ' && *q != '\n' && i < (int)sizeof b->address - 1) b->address[i++] = *q++; + b->address[i] = 0; + } + } + } + /* paired 数:bluetoothctl paired(BlueZ 5.54);失败/空 → -1。 */ + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s paired 2>/dev/null", g_bluetoothctl); + char buf[512]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) { + int n = 0; + char *ln = buf; + while (ln && *ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + char *q = ln; + while (*q == ' ' || *q == '\t') q++; + if (!strncmp(q, "Device", 6)) n++; + if (!nl) break; + ln = nl + 1; + } + b->paired_n = n; + } +} + +/* ---------------- rfkill:统一软开关 ---------------- */ +static void rfkill_list(void) +{ + int n = 0; + DIR *d = opendir(g_rfkill_dir); + if (d) { + struct dirent *de; + while ((de = readdir(d)) && n < RFKILL_MAX) { + if (strncmp(de->d_name, "rfkill", 6) != 0) continue; + char p[448]; + rfkill_t *r = &g_rfkill[n]; + memset(r, 0, sizeof *r); + snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name); + read_file_txt(p, r->type, sizeof r->type); + snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name); + r->soft = read_file_int_def(p, -1); + snprintf(p, sizeof p, "%s/%s/hard", g_rfkill_dir, de->d_name); + r->hard = read_file_int_def(p, -1); + n++; + } + closedir(d); + } + for (int i = n; i < RFKILL_MAX; i++) memset(&g_rfkill[i], 0, sizeof(rfkill_t)); +} + +/* 找某类型 rfkill 设备并写 soft=block(1)/unblock(0)。返回 0=ok;-1=无该类型/写失败。 */ +static int rfkill_set_block(const char *type, int block) +{ + char norm[16] = ""; + if (!strcmp(type, "wifi")) snprintf(norm, sizeof norm, "%s", "wlan"); + else if (!strcmp(type, "bt")) snprintf(norm, sizeof norm, "%s", "bluetooth"); + else snprintf(norm, sizeof norm, "%s", type); + DIR *d = opendir(g_rfkill_dir); + int rc = -1; + if (d) { + struct dirent *de; + while ((de = readdir(d))) { + if (strncmp(de->d_name, "rfkill", 6) != 0) continue; + char p[448], t[16] = ""; + snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name); + if (read_file_txt(p, t, sizeof t) <= 0 || strcmp(t, norm) != 0) continue; + snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name); + int fd = open(p, O_WRONLY); + if (fd < 0) continue; + char one = block ? '1' : '0'; + if (write(fd, &one, 1) == 1) rc = 0; + close(fd); + break; + } + closedir(d); + } + return rc; +} + +/* ---------------- udhcpc:续租/释放(SIGUSR1/SIGUSR2,不改启动路径) ---------------- */ +static int udhcpc_ctl(const char *op, const char *ifname) +{ + char pidfile[160]; + const char *iff = (ifname && ifname[0]) ? ifname : g_wifi_if; + snprintf(pidfile, sizeof pidfile, "/var/run/udhcpc.%s.pid", iff); + char buf[32]; + if (read_file_txt(pidfile, buf, sizeof buf) <= 0) return -1; + int pid = atoi(buf); + if (pid <= 0) return -1; + int sig = (strcmp(op, "release") == 0) ? SIGUSR2 : SIGUSR1; + return kill(pid, sig) == 0 ? 0 : -1; +} + +/* ---------------- IPC 帧读写(同 svc_power 约定,全小端) ---------------- */ +static int x_read_full(int fd, uint8_t *buf, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, buf + off, n - off); + if (r <= 0) return -1; + off += (size_t)r; + } + return 0; +} +static int x_write_full(int fd, const uint8_t *buf, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, buf + off, n - off); + if (w <= 0) return -1; + off += (size_t)w; + } + return 0; +} + +static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) +{ + 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_id, 4); + memcpy(hdr + 12, &rlen, 4); + if (x_write_full(fd, hdr, MEOW_IPC_HDR_LEN) < 0) return -1; + if (len && payload && x_write_full(fd, payload, len) < 0) return -1; + return 0; +} + +static int send_ack(int fd, uint32_t req_id, uint32_t code, uint32_t status) +{ + m_ack_t a; + memset(&a, 0, sizeof a); + a.code = code; + a.status = status; + return send_frame(fd, MSG_ACK, req_id, &a, sizeof a); +} + +static int send_err(int fd, uint32_t req_id, uint32_t code, const char *msg) +{ + m_err_t e; + memset(&e, 0, sizeof e); + e.code = code; + uint16_t ml = msg ? (uint16_t)strlen(msg) : 0; + if (ml > 63) ml = 63; + e.msg_len = ml; + if (ml) memcpy(e.msg, msg, ml); + return send_frame(fd, MSG_ERR, req_id, &e, sizeof e); +} + +/* json/text 结果通发(m_net_status_result_t / m_net_iface_result_t / m_net_cmd_result_t 布局同构 + * code(4)+len(2)+pad(2)+payload,payload ≤ MEOW_IPC_MAX-8)。 */ +static int send_payload_result(int fd, uint16_t type, uint32_t req_id, uint32_t code, + const char *payload, size_t pl) +{ + m_net_cmd_result_t r; /* 复用布局最大的那个(payload 区 4088B)。 */ + memset(&r, 0, sizeof r); + r.code = code; + if (pl > sizeof r.text - 1) pl = sizeof r.text - 1; + r.text_len = (uint16_t)pl; + memcpy(r.text, payload, pl); + r.text[pl] = 0; + return send_frame(fd, type, req_id, &r, (uint32_t)(8 + pl + 1)); +} + +static int send_net_status_result(int fd, uint32_t req_id, const char *json) +{ + return send_payload_result(fd, MSG_NET_STATUS_RESULT, req_id, MEOW_OK, json, strlen(json)); +} +static int send_net_iface_result(int fd, uint32_t req_id, uint32_t code, const char *json) +{ + return send_payload_result(fd, MSG_NET_IFACE_RESULT, req_id, code, json, strlen(json)); +} +static int send_net_wifi_result(int fd, uint32_t req_id, const ctl_result_t *r) +{ + return send_payload_result(fd, MSG_NET_WIFI_RESULT, req_id, (uint32_t)r->code, r->text, strlen(r->text)); +} +static int send_net_bt_result(int fd, uint32_t req_id, const ctl_result_t *r) +{ + return send_payload_result(fd, MSG_NET_BT_RESULT, req_id, (uint32_t)r->code, r->text, strlen(r->text)); +} + +/* ---------------- 守护进程态 ---------------- */ +static int g_mode_meow = 0; /* meow=1 全功能;0=只读降级 */ +static long g_net_last_rx = -1, g_net_last_tx = 0, g_net_last_mono = 0; +static long g_net_rate_rx = -1, g_net_rate_tx = -1; /* B/s;-1=未知 */ +static char g_prev_if[IFACE_MAX][32]; +static int g_prev_if_up[IFACE_MAX]; +static int g_prev_if_n = 0; + +/* ---------------- JSON 构建:status/iface ---------------- */ +static void iface_json_obj(jw_t *w, const iface_t *f) +{ + jadd(w, "{\"name\":\"%s\",\"kind\":\"%s\",\"up\":%s,", f->name, f->kind, + f->up ? "true" : "false"); + jadd_str_or_null(w, "ip", f->ip); + jadd_str_or_null(w, "gw", f->gw); + jadd(w, "\"rx_bytes\":%ld,\"tx_bytes\":%ld}", f->rx, f->tx); +} + +static void build_status_json(char *out, size_t cap) +{ + jw_t w = { out, cap, 0 }; + out[0] = 0; + jadd(&w, "{\"mode\":\"%s\",\"boot_ts\":\"", g_mode_meow ? "meow" : "readonly"); + { + char ts[64]; get_bootts(ts, sizeof ts); + for (const char *p = ts; *p; p++) w.buf[w.off++] = *p; + } + jadd(&w, "\",\"ts\":%ld,", (long)time(NULL)); + + enum_ifaces(1); + jadd(&w, "\"ifaces\":["); + for (int i = 0; i < g_iface_n; i++) { if (i) jadd(&w, ","); iface_json_obj(&w, &g_ifaces[i]); } + jadd(&w, "],"); + + wifi_t wi; + wifi_status(&wi); + jadd(&w, "\"wifi\":{"); + jadd_q(&w, "state", wi.state); + jadd(&w, "\"connected\":%s,", wi.connected ? "true" : "false"); + jadd_str_or_null(&w, "ssid", wi.ssid); + jadd_str_or_null(&w, "ip", wi.ip); + if (wi.has_rssi) jadd(&w, "\"rssi\":%d,", wi.rssi); + else jadd(&w, "\"rssi\":null,"); + jadd(&w, "\"networks\":null},"); + + bt_t bt; + bt_status(&bt); + jadd(&w, "\"bt\":{"); + jadd(&w, "\"available\":%s,\"powered\":%s,", bt.available ? "true" : "false", + bt.powered ? "true" : "false"); + jadd_str_or_null(&w, "address", bt.address); + jadd_int_or_null(&w, "paired", bt.paired_n); + jadd(&w, "\"dns\":["); + { + char ns[DNS_MAX][40]; + int n = 0; + dns_read(ns, &n); + for (int i = 0; i < n; i++) { if (i) jadd(&w, ","); jadd(&w, "\"%s\"", ns[i]); } + } + jadd(&w, "],"); + + rfkill_list(); + jadd(&w, "\"rfkill\":["); + for (int i = 0; i < RFKILL_MAX && g_rfkill[i].type[0]; i++) { + if (i) jadd(&w, ","); + jadd(&w, "{\"type\":\"%s\",\"soft\":%d,\"hard\":%d}", g_rfkill[i].type, + g_rfkill[i].soft, g_rfkill[i].hard); + } + jadd(&w, "],"); + + jadd(&w, "\"rate\":{\"rx_bps\":"); + if (g_net_rate_rx < 0) jadd(&w, "null,"); + else jadd(&w, "%ld,", g_net_rate_rx); + jadd(&w, "\"tx_bps\":"); + if (g_net_rate_tx < 0) jadd(&w, "null}}"); + else jadd(&w, "%ld}}", g_net_rate_tx); +} + +/* 单/全接口详情 JSON。name 空=全部(数组);否则单对象。返回 MEOW_OK / MEOW_ERR_NOENT。 */ +static int build_iface_json(const char *name, char *out, size_t cap) +{ + enum_ifaces(1); + jw_t w = { out, cap, 0 }; + out[0] = 0; + if (!name || !name[0]) { + jadd(&w, "["); + for (int i = 0; i < g_iface_n; i++) { + if (i) jadd(&w, ","); + iface_json_obj(&w, &g_ifaces[i]); + } + jadd(&w, "]"); + return MEOW_OK; + } + for (int i = 0; i < g_iface_n; i++) { + if (strcmp(g_ifaces[i].name, name) == 0) { + iface_json_obj(&w, &g_ifaces[i]); + /* 去掉尾 } 补 dns(期 1 不分接口,统一读 resolv.conf)。 */ + if (w.off >= 2 && w.buf[w.off - 1] == '}') w.buf[--w.off] = 0; + jadd(&w, ",\"dns\":["); + char ns[DNS_MAX][40]; + int n = 0; + dns_read(ns, &n); + for (int i2 = 0; i2 < n; i2++) { if (i2) jadd(&w, ","); jadd(&w, "\"%s\"", ns[i2]); } + jadd(&w, "]}"); + return MEOW_OK; + } + } + return MEOW_ERR_NOENT; +} + +/* ---------------- 节拍:模式热切换 + 接口变化检测(日志)+ 全局速率差分 ---------------- */ +static void daemon_tick(void) +{ + int fm = boot_mode_is_meow(); + if (fm != g_mode_meow) { + svc_log("mode -> %s", fm ? "full(meow)" : "readonly"); + g_mode_meow = fm; + } + /* 接口热插拔/状态变化:节拍检测 → 日志(读侧无 popen)。 */ + enum_ifaces(0); + int cur = g_iface_n; + for (int i = 0; i < cur; i++) { + int found = -1; + for (int j = 0; j < g_prev_if_n; j++) + if (strcmp(g_prev_if[j], g_ifaces[i].name) == 0) { found = j; break; } + if (found < 0) + net_log("info", "iface", "add kind=%s name=%s", g_ifaces[i].kind, g_ifaces[i].name); + else if (g_prev_if_up[found] != g_ifaces[i].up) + net_log("info", "iface", "state name=%s up=%d", g_ifaces[i].name, g_ifaces[i].up); + if (found >= 0) g_prev_if_up[found] = g_ifaces[i].up; + } + for (int j = 0; j < g_prev_if_n; j++) { + int gone = 1; + for (int i = 0; i < cur; i++) if (strcmp(g_prev_if[j], g_ifaces[i].name) == 0) { gone = 0; break; } + if (gone) net_log("info", "iface", "del name=%s", g_prev_if[j]); + } + for (int i = 0; i < cur; i++) { + snprintf(g_prev_if[i], sizeof g_prev_if[i], "%s", g_ifaces[i].name); + g_prev_if_up[i] = g_ifaces[i].up; + } + g_prev_if_n = cur; + + /* 全局速率差分(2s 拍):/proc/net/dev 累计。 */ + long rx = -1, tx = 0; + netdev_totals(&rx, &tx); + if (rx >= 0) { + long now = mono_now(); + if (g_net_last_rx >= 0 && now > g_net_last_mono) { + long dt = now - g_net_last_mono; + if (dt > 0) { + g_net_rate_rx = (rx - g_net_last_rx) / dt; + g_net_rate_tx = (tx - g_net_last_tx) / dt; + if (g_net_rate_rx < 0) g_net_rate_rx = 0; + if (g_net_rate_tx < 0) g_net_rate_tx = 0; + } + } + g_net_last_rx = rx; g_net_last_tx = tx; g_net_last_mono = now; + } +} + +/* ---------------- IPC 鉴权(连接级,MPK_SIGNATURE_DESIGN §6.2;同 svc_power) ---------------- */ +typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t; +typedef struct { + caller_kind_t kind; + char pkg[MEOW_APP_ID_MAX]; + uid_t uid; +} caller_t; + +static int is_sys_identity(const caller_t *c) +{ + if (c->kind == CALL_SYS) return 1; + if (c->kind == CALL_PKG && !strncmp(c->pkg, "os.meow.", 8)) return 1; + return 0; +} + +static void classify_exe(const char *exe, caller_t *out) +{ + static const char *mp = "/usr/data/mnt/sd_0/MeowAPPs/"; + if (strncmp(exe, mp, strlen(mp)) == 0) { + const char *p = exe + strlen(mp); + if (*p == '/') p++; + const char *end = strchr(p, '/'); + int plen = end ? (int)(end - p) : (int)strlen(p); + if (plen > 0 && plen < MEOW_APP_ID_MAX) { + memcpy(out->pkg, p, (size_t)plen); out->pkg[plen] = 0; + out->kind = CALL_PKG; + return; + } + } + char tmp[MEOW_APP_ID_MAX]; + if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher"); + } else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) { + size_t l = strlen(exe + 9); /* "/usr/bin/" 长 9 → 取 "meow_xyz" */ + if (l >= sizeof tmp) l = sizeof tmp - 1; + memcpy(tmp, exe + 9, l); tmp[l] = 0; + } else if (strncmp(exe, "/usr/data/meow/", 15) == 0) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.core"); + } else { + return; /* CALL_OTHER */ + } + snprintf(out->pkg, sizeof out->pkg, "%s", tmp); + out->kind = CALL_SYS; +} + +static void classify_caller(int fd, caller_t *out) +{ + memset(out, 0, sizeof *out); + out->uid = (uid_t)-1; + struct ucred cred; + socklen_t cl = sizeof cred; + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cl) != 0) return; + out->uid = cred.uid; + char procpath[64], exe[256]; + snprintf(procpath, sizeof procpath, "/proc/%d/exe", (int)cred.pid); + ssize_t n = readlink(procpath, exe, sizeof exe - 1); + if (n <= 0) return; + exe[n] = 0; + classify_exe(exe, out); +} + +/* CTL 写级分类(设计 §10/HibyOS:写级仅系统身份 + MeowOS 模式才允许)。 */ +static int wifi_cmd_is_write(const char *cmd, const char *arg) +{ + if (!strcmp(cmd, "status") || !strcmp(cmd, "networks")) return 0; /* 读级 */ + if (!strcmp(cmd, "rfkill")) return arg && strcmp(arg, "list") == 0 ? 0 : 1; + return 1; /* scan/connect/disconnect/on/off/udhcpc */ +} +static int bt_cmd_is_write(const char *cmd) +{ + if (!strcmp(cmd, "status") || !strcmp(cmd, "paired")) return 0; + return 1; /* on/off */ +} + +/* 写级授权决策(纯函数,宿主自测用;handle_conn 复用):读级任意;写级仅系统身份 + MeowOS 模式。 + * 返回 MEOW_OK(放行)或 MEOW_ERR_PERM(拒绝)。 */ +static int wifi_write_authorize(const caller_t *c, int mode_meow, const char *cmd, const char *arg) +{ + if (!wifi_cmd_is_write(cmd, arg)) return MEOW_OK; + if (!is_sys_identity(c)) return MEOW_ERR_PERM; + if (!mode_meow) return MEOW_ERR_PERM; + return MEOW_OK; +} +static int bt_write_authorize(const caller_t *c, int mode_meow, const char *cmd) +{ + if (!bt_cmd_is_write(cmd)) return MEOW_OK; + if (!is_sys_identity(c)) return MEOW_ERR_PERM; + if (!mode_meow) return MEOW_ERR_PERM; + return MEOW_OK; +} + +/* ---------------- WiFi/BT/rfkill/udhcpc 动作执行(dæmon 侧;写级已在上游鉴权) ---------------- */ +static const char *str_capsafe(const char *s, char *tmp, size_t cap) +{ + size_t n = strlen(s); + while (n > 0 && (s[0] == ' ' || s[0] == '\t')) { s++; n--; } + while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\n')) n--; + if (n >= cap) n = cap - 1; + memcpy(tmp, s, n); tmp[n] = 0; + return tmp; +} + +static void wifi_scan(ctl_result_t *r) +{ + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s scan 2>/dev/null", g_wpa_cli); + char buf[128]; + run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd); + struct timespec ts = { 0, 300 * 1000000L }; /* 触发后稍候再读结果(有界)。 */ + nanosleep(&ts, NULL); + char bufout[MEOW_IPC_MAX - 16]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s scan_results 2>/dev/null", g_wpa_cli); + int n = run_cmd(bufout, sizeof bufout, RUNCMD_TMO_MS, cmd); + if (n <= 0) { ctl_sets(r, MEOW_ERR_NODEV, "scan: no results (wpa_cli 空/失败)"); return; } + ctl_sets(r, MEOW_OK, bufout); +} + +static void wifi_networks(ctl_result_t *r) +{ + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s list_networks 2>/dev/null", g_wpa_cli); + char buf[MEOW_IPC_MAX - 16]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd); + if (n <= 0) { ctl_sets(r, MEOW_ERR_NODEV, "networks: no output (wpa_cli 空/失败)"); return; } + ctl_sets(r, MEOW_OK, buf); +} + +static void wifi_connect(const char *arg, ctl_result_t *r) +{ + char ssid[96] = "", psk[96] = ""; + if (arg && arg[0]) { + const char *comma = strchr(arg, ','); + if (comma) { + size_t sl = (size_t)(comma - arg); + if (sl >= sizeof ssid) sl = sizeof ssid - 1; + memcpy(ssid, arg, sl); ssid[sl] = 0; + snprintf(psk, sizeof psk, "%s", comma + 1); + } else { + snprintf(ssid, sizeof ssid, "%s", arg); + } + } + char tmp_ssid[96], tmp_psk[96]; + str_capsafe(ssid, tmp_ssid, sizeof tmp_ssid); + str_capsafe(psk, tmp_psk, sizeof tmp_psk); + if (!tmp_ssid[0]) { ctl_sets(r, MEOW_ERR_INVAL, "connect: ssid 缺失"); return; } + + /* 找既有网络:list_networks → 匹配 ssid(剥引号比较);命中则复用其 id。 */ + char cmd[200]; + char list[1024]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s list_networks 2>/dev/null", g_wpa_cli); + int n = run_cmd(list, sizeof list, RUNCMD_TMO_MS, cmd); + int id = -1; + if (n > 0) { + char *ln = strtok(list, "\n"); + while (ln) { + char *tab0 = strchr(ln, '\t'); + if (tab0) { + int this_id = atoi(ln); + char *ss = tab0 + 1; + char *tab1 = strchr(ss, '\t'); + if (tab1) *tab1 = 0; + char clean[128], norm_clean[128]; + size_t cl = strlen(ss); + if (cl >= 2 && ss[0] == '"' && ss[cl-1] == '"') { + memcpy(clean, ss + 1, cl - 2); clean[cl - 2] = 0; + } else { + snprintf(clean, sizeof clean, "%s", ss); + } + str_capsafe(clean, norm_clean, sizeof norm_clean); + if (strcmp(norm_clean, tmp_ssid) == 0) id = this_id; + } + ln = strtok(NULL, "\n"); + } + } + char out[128]; + if (id < 0) { + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s add_network 2>/dev/null", g_wpa_cli); + if (run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd) <= 0) { + ctl_sets(r, MEOW_ERR_NODEV, "connect: add_network 失败 (wpa_cli 不可用?)"); + return; + } + id = atoi(out); + if (id < 0) { ctl_sets(r, MEOW_ERR_INVAL, "connect: add_network 无 id"); return; } + snprintf(cmd, sizeof cmd, + "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d ssid '\"%s\"' 2>/dev/null", + g_wpa_cli, id, tmp_ssid); + run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd); + if (tmp_psk[0]) { + snprintf(cmd, sizeof cmd, + "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d psk '\"%s\"' 2>/dev/null", + g_wpa_cli, id, tmp_psk); + } else { + snprintf(cmd, sizeof cmd, + "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d key_mgmt NONE 2>/dev/null", + g_wpa_cli, id); + } + run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd); + } + { /* enable + select 该网络(复用/新建皆用)。 */ + char en[256], sel[256]; + snprintf(en, sizeof en, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s enable_network %d 2>/dev/null", + g_wpa_cli, id); + run_cmd(out, sizeof out, RUNCMD_TMO_MS, en); + snprintf(sel, sizeof sel, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s select_network %d 2>/dev/null", + g_wpa_cli, id); + run_cmd(out, sizeof out, RUNCMD_TMO_MS, sel); + } + char msg[140]; + snprintf(msg, sizeof msg, "connect: ssid=%s network_id=%d (已 enable+select)", tmp_ssid, id); + ctl_sets(r, MEOW_OK, msg); +} + +static void wifi_onoff(const char *cmd, ctl_result_t *r) +{ + int block = strcmp(cmd, "off") == 0; + int rc = rfkill_set_block("wlan", block); + if (rc == 0) { + char msg[64]; + snprintf(msg, sizeof msg, "wifi %s: rfkill wlan %s", cmd, block ? "blocked" : "unblocked"); + ctl_sets(r, MEOW_OK, msg); + } else { + ctl_sets(r, MEOW_ERR_NODEV, "wifi on/off: 无 wlan rfkill 设备或写失败"); + } +} + +static void rfkill_ctl(const char *arg, ctl_result_t *r) +{ + char tmp[64]; + str_capsafe(arg ? arg : "", tmp, sizeof tmp); + char *sp = strchr(tmp, ' '); + if (!sp) { ctl_sets(r, MEOW_ERR_INVAL, "rfkill: 用法 (如 `rfkill wlan on`)"); return; } + *sp = 0; + char *type = tmp; + char *state = sp + 1; + int block = strcmp(state, "off") == 0 ? 1 : (strcmp(state, "on") == 0 ? 0 : -1); + if (block < 0) { ctl_sets(r, MEOW_ERR_INVAL, "rfkill: state 须 on/off"); return; } + int rc = rfkill_set_block(type, block); + if (rc == 0) { + char msg[64]; + snprintf(msg, sizeof msg, "rfkill %s %s: done", type, block ? "blocked" : "unblocked"); + ctl_sets(r, MEOW_OK, msg); + } else { + ctl_sets(r, MEOW_ERR_NODEV, "rfkill: 无该类型设备或写失败"); + } +} + +static void udhcpc_ctl_svc(const char *arg, ctl_result_t *r) +{ + char tmp[96]; + str_capsafe(arg ? arg : "", tmp, sizeof tmp); + char *sp = strchr(tmp, ' '); + char op[16], iff[32] = ""; + if (sp) { *sp = 0; snprintf(op, sizeof op, "%s", tmp); snprintf(iff, sizeof iff, "%s", sp + 1); } + else snprintf(op, sizeof op, "%s", tmp); + if (strcmp(op, "renew") != 0 && strcmp(op, "release") != 0) { + ctl_sets(r, MEOW_ERR_INVAL, "udhcpc: op 须 renew/release"); + return; + } + int rc = udhcpc_ctl(op, iff[0] ? iff : NULL); + if (rc == 0) { + char msg[80]; + snprintf(msg, sizeof msg, "udhcpc %s %s: sent SIGUSR%s", op, iff[0] ? iff : g_wifi_if, + strcmp(op, "release") == 0 ? "2" : "1"); + ctl_sets(r, MEOW_OK, msg); + } else { + ctl_sets(r, MEOW_ERR_NOENT, "udhcpc: 无运行进程 (pid 文件缺失)"); + } +} + +static void wifi_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r) +{ + ctl_reset(r, MEOW_OK); + if (!strcmp(cmd, "status")) { + wifi_t w; + wifi_status(&w); + char msg[256]; + snprintf(msg, sizeof msg, "wifi state=%s connected=%d ssid=%s ip=%s rssi=%d", + w.state[0] ? w.state : "unknown", w.connected, w.ssid[0] ? w.ssid : "-", + w.ip[0] ? w.ip : "-", w.rssi); + ctl_sets(r, MEOW_OK, msg); + } else if (!strcmp(cmd, "scan")) { + wifi_scan(r); + } else if (!strcmp(cmd, "networks")) { + wifi_networks(r); + } else if (!strcmp(cmd, "connect")) { + wifi_connect(arg, r); + } else if (!strcmp(cmd, "disconnect")) { + char cmd2[160]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s disconnect 2>/dev/null", g_wpa_cli); + char buf[128]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV, "wifi disconnect: 已下发"); + } else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) { + wifi_onoff(cmd, r); + } else if (!strcmp(cmd, "rfkill")) { + rfkill_ctl(arg, r); + } else if (!strcmp(cmd, "udhcpc")) { + udhcpc_ctl_svc(arg, r); + } else { + ctl_sets(r, MEOW_ERR_INVAL, "wifi: 未知子命令"); + } +} + +static void bt_onoff(const char *cmd, ctl_result_t *r) +{ + int up = strcmp(cmd, "on") == 0; + char cmd2[160]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 %s 2>/dev/null", + g_hciconfig, up ? "up" : "down"); + char buf[128]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + rfkill_set_block("bluetooth", up ? 0 : 1); + ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV, + up ? "bt on: hci0 up + rfkill unblocked" : "bt off: hci0 down + rfkill blocked"); +} + +static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r) +{ + (void)arg; + ctl_reset(r, MEOW_OK); + if (!strcmp(cmd, "status")) { + bt_t b; + bt_status(&b); + char msg[192]; + snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d", + b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n); + ctl_sets(r, MEOW_OK, msg); + } else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) { + bt_onoff(cmd, r); + } else if (!strcmp(cmd, "paired")) { + char cmd2[160]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s paired 2>/dev/null", g_bluetoothctl); + char buf[MEOW_IPC_MAX - 16]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + if (n <= 0) ctl_sets(r, MEOW_ERR_NODEV, "bt paired: 无输出 (bluetoothctl 不可用/未开)"); + else ctl_sets(r, MEOW_OK, buf); + } else { + ctl_sets(r, MEOW_ERR_INVAL, "bt: 未知子命令"); + } +} + +/* ---------------- 处理单条连接 ---------------- */ +static void handle_conn(int c) +{ + caller_t caller; + classify_caller(c, &caller); + for (;;) { + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (x_read_full(c, hdr, sizeof hdr) < 0) break; + uint32_t magic, rlen, req_id; + uint16_t ver, type; + memcpy(&magic, hdr, 4); + memcpy(&ver, hdr + 4, 2); + memcpy(&type, hdr + 6, 2); + memcpy(&req_id, hdr + 8, 4); + memcpy(&rlen, hdr + 12, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { + send_err(c, req_id, MEOW_ERR_INVAL, "bad magic/ver"); break; + } + if (rlen > MEOW_IPC_MAX) { send_err(c, req_id, MEOW_ERR_INVAL, "payload too big"); break; } + uint8_t payload[MEOW_IPC_MAX]; + if (rlen && x_read_full(c, payload, rlen) < 0) break; + + if (type == MSG_HELLO) { send_ack(c, req_id, MEOW_OK, 0); continue; } + + if (type == MSG_NET_STATUS_REQ) { + char json[MEOW_IPC_MAX]; + build_status_json(json, sizeof json); + send_net_status_result(c, req_id, json); + continue; + } + if (type == MSG_NET_IFACE_REQ) { + m_net_iface_req_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + char json[MEOW_IPC_MAX]; + int rc = build_iface_json(q.name, json, sizeof json); + send_net_iface_result(c, req_id, rc == MEOW_OK ? MEOW_OK : MEOW_ERR_NOENT, json); + continue; + } + if (type == MSG_NET_WIFI_CTL) { + m_net_cmd_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + ctl_result_t r; + int wr = wifi_write_authorize(&caller, g_mode_meow, q.cmd, q.arg); + if (wr != MEOW_OK) { + ctl_sets(&r, MEOW_ERR_PERM, + !is_sys_identity(&caller) + ? "permission denied (wifi ctl write needs system identity)" + : "readonly (HibyOS mode): wifi write denied"); + net_log("warn", "write", "denied wifi cmd=%s arg=%s caller_kind=%d uid=%d", q.cmd, + q.arg, (int)caller.kind, (int)caller.uid); + send_net_wifi_result(c, req_id, &r); + continue; + } + wifi_ctl_execute(q.cmd, q.arg, &r); + if (wifi_cmd_is_write(q.cmd, q.arg)) + net_log("info", "write", "wifi cmd=%s arg=%s rc=%d", q.cmd, q.arg, r.code); + send_net_wifi_result(c, req_id, &r); + continue; + } + if (type == MSG_NET_BT_CTL) { + m_net_cmd_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + ctl_result_t r; + int wr = bt_write_authorize(&caller, g_mode_meow, q.cmd); + if (wr != MEOW_OK) { + ctl_sets(&r, MEOW_ERR_PERM, + !is_sys_identity(&caller) + ? "permission denied (bt ctl write needs system identity)" + : "readonly (HibyOS mode): bt write denied"); + net_log("warn", "write", "denied bt cmd=%s caller_kind=%d uid=%d", q.cmd, + (int)caller.kind, (int)caller.uid); + send_net_bt_result(c, req_id, &r); + continue; + } + bt_ctl_execute(q.cmd, q.arg, &r); + if (bt_cmd_is_write(q.cmd)) net_log("info", "write", "bt cmd=%s rc=%d", q.cmd, r.code); + send_net_bt_result(c, req_id, &r); + continue; + } + send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1仅 STATUS/IFACE/WIFI_CTL/BT_CTL)"); + break; + } +} + +/* ---------------- 守护主循环(单线程 poll;节拍 2s) ---------------- */ +static volatile sig_atomic_t g_stop = 0; +static void on_sig(int sig) { (void)sig; g_stop = 1; } + +static int run_daemon(const char *sock) +{ + signal(SIGPIPE, SIG_IGN); + signal(SIGTERM, on_sig); + signal(SIGINT, on_sig); + int lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (lfd < 0) { svc_log("socket fail: %s", strerror(errno)); 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); + unlink(sock); + if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) { + svc_log("bind %s fail: %s", sock, strerror(errno)); + close(lfd); + return 1; + } + chmod(sock, 0666); + if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; } + + g_mode_meow = boot_mode_is_meow(); + log_open(); + svc_log("meow_netmgrd listening on %s (mode=%s, poll=%ds, log=%s)", + sock, g_mode_meow ? "full" : "readonly", g_poll_sec, g_logfile); + + long next_tick = 0; + while (!g_stop) { + long now = mono_now(); + if (now >= next_tick) { + daemon_tick(); + next_tick = now + g_poll_sec; + } + int wait_ms = (int)((next_tick - mono_now()) * 1000); + if (wait_ms < 50) wait_ms = 50; + if (wait_ms > g_poll_sec * 1000) wait_ms = g_poll_sec * 1000; + struct pollfd pfd = { lfd, POLLIN, 0 }; + int pr = poll(&pfd, 1, wait_ms); + if (pr > 0 && (pfd.revents & POLLIN)) { + int c = accept(lfd, NULL, NULL); + if (c >= 0) { handle_conn(c); close(c); } + } + } + svc_log("meow_netmgrd exit"); + close(lfd); + unlink(sock); + return 0; +} + +/* ---------------- CLI(IPC 薄壳) ---------------- */ +static int ipc_connect(void) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct timeval tv = { 2, 0 }; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + return fd; +} + +/* 收发一轮:req(可有可无)+ resp type/code + payload 文本(json/text)。返回 0=OK。 */ +static int ipc_xchg(int fd, uint16_t req_type, const void *req, uint32_t req_len, + uint16_t *resp_type, uint32_t *resp_code, char *out, size_t outcap) +{ + if (send_frame(fd, req_type, 1, req, req_len) < 0) return -1; + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (x_read_full(fd, hdr, sizeof hdr) < 0) return -1; + uint32_t rl; uint16_t rt; + memcpy(&rt, hdr + 6, 2); + memcpy(&rl, hdr + 12, 4); + if (resp_type) *resp_type = rt; + if (rl > MEOW_IPC_MAX) return -1; + uint8_t pl[MEOW_IPC_MAX]; + if (rl && x_read_full(fd, pl, rl) < 0) return -1; + if (rl >= 8) { + uint32_t code; uint16_t jl; + memcpy(&code, pl, 4); + memcpy(&jl, pl + 4, 2); + if (resp_code) *resp_code = code; + if (out && outcap) { + if (jl >= outcap) jl = (uint16_t)(outcap - 1); + memcpy(out, pl + 8, jl); + out[jl] = 0; + } + return 0; + } + return -1; +} + +static int cli_ipc_json(uint16_t req_type, const void *req, uint32_t req_len, char *json, size_t cap) +{ + int fd = ipc_connect(); + if (fd < 0) return -1; + uint16_t rt; uint32_t code; + int rc = ipc_xchg(fd, req_type, req, req_len, &rt, &code, json, cap); + close(fd); + return rc == 0 ? 0 : -1; +} + +static int cli_ipc_cmd(uint16_t req_type, const char *cmd, const char *arg, char *out, size_t cap) +{ + m_net_cmd_t q; + memset(&q, 0, sizeof q); + snprintf(q.cmd, sizeof q.cmd, "%s", cmd); + if (arg) snprintf(q.arg, sizeof q.arg, "%s", arg); + int fd = ipc_connect(); + if (fd < 0) { snprintf(out, cap, "meow_netmgrd 守护不可达 (%s)", g_sock); return -1; } + uint16_t rt; uint32_t code; + int rc = ipc_xchg(fd, req_type, &q, sizeof q, &rt, &code, out, cap); + close(fd); + if (rc != 0) { snprintf(out, cap, "IPC 往返失败"); return -1; } + return (int)code; +} + +static int cli_cmd_result(uint16_t req_type, const char *cmd, const char *arg) +{ + char out[MEOW_IPC_MAX - 8]; + int code = cli_ipc_cmd(req_type, cmd, arg, out, sizeof out); + printf("%s\n", out); + return code == MEOW_OK ? 0 : 1; +} + +/* ---- 简版 JSON 字段提取(自产 JSON 固定形状,不做通用解析;同 powermgr) ---- */ +static const char *jfind(const char *js, const char *key) +{ + char pat[48]; + snprintf(pat, sizeof pat, "\"%s\":", key); + const char *p = strstr(js, pat); + return p ? p + strlen(pat) : NULL; +} +static int jint(const char *js, const char *key, int *out) +{ + const char *p = jfind(js, key); + if (!p || strncmp(p, "null", 4) == 0) { *out = -1; return 1; } + *out = atoi(p); + return 0; +} +static void jstr(const char *js, const char *key, char *out, size_t cap) +{ + const char *p = jfind(js, key); + if (!p || strncmp(p, "null", 4) == 0 || *p != '"') { out[0] = 0; return; } + p++; + size_t n = 0; + while (p[n] && p[n] != '"' && n < cap - 1) { out[n] = p[n]; n++; } + out[n] = 0; +} + +/* 定位 `"key":{` 的子对象并切到其闭合 `}`(扁平对象)。返回切片进 out。 */ +static int slice_obj(const char *js, const char *key, char *out, size_t cap) +{ + char pat[64]; + snprintf(pat, sizeof pat, "\"%s\":", key); + const char *p = strstr(js, pat); + if (!p) { out[0] = 0; return -1; } + const char *q = strchr(p, '}'); + if (!q) { out[0] = 0; return -1; } + size_t n = (size_t)(q - p + 1); + if (n >= cap) n = cap - 1; + memcpy(out, p, n); out[n] = 0; + return 0; +} + +/* 在 json 中迭代 `"":"` 开头的扁平条目,取每个对象切片(到 `}`)。首调 *ctx=NULL。 */ +static int next_obj(const char *json, const char *key, const char **ctx, char *out, size_t cap) +{ + char pat[64]; + snprintf(pat, sizeof pat, "\"%s\":\"", key); + const char *p = *ctx ? *ctx : json; + const char *q = strstr(p, pat); + if (!q) { *ctx = NULL; return -1; } + q += strlen(pat); + const char *e = strchr(q, '}'); + if (!e) { *ctx = NULL; return -1; } + size_t n = (size_t)(e - q + 1); + if (n >= cap) n = cap - 1; + memcpy(out, q, n); out[n] = 0; + *ctx = e + 1; + return 0; +} + +static int cli_status(void) +{ + char js[MEOW_IPC_MAX]; + if (cli_ipc_json(MSG_NET_STATUS_REQ, NULL, 0, js, sizeof js) != 0) { + fprintf(stderr, "meow_net status: 守护不可达(%s)\n", g_sock); + return 1; + } + char mode[16]; + jstr(js, "mode", mode, sizeof mode); + printf("mode=%s src=daemon\n", mode[0] ? mode : "?"); + /* ifaces 数组 */ + const char *ctx = NULL; + for (;;) { + char tmp[256]; + if (next_obj(js, "name", &ctx, tmp, sizeof tmp) != 0) break; + char name[32]; int i = 0; + while (tmp[i] && tmp[i] != '"' && i < 31) { name[i] = tmp[i]; i++; } + name[i] = 0; + char kind[12] = "", ip[40] = "", gw[40] = ""; + int up = strstr(tmp, "\"up\":true") != NULL; + jstr(tmp, "kind", kind, sizeof kind); + jstr(tmp, "ip", ip, sizeof ip); + jstr(tmp, "gw", gw, sizeof gw); + printf("iface:%s kind=%s up=%s ip=%s gw=%s\n", name, kind[0] ? kind : "?", + up ? "true" : "false", ip[0] ? ip : "null", gw[0] ? gw : "null"); + } + /* wifi */ + { + char obj[256]; + slice_obj(js, "wifi", obj, sizeof obj); + char ssid[64], wstate[16], wip[40]; + int wc = strstr(obj, "\"connected\":true") != NULL; + jstr(obj, "state", wstate, sizeof wstate); + jstr(obj, "ssid", ssid, sizeof ssid); + jstr(obj, "ip", wip, sizeof wip); + int rssi = -1; + jint(obj, "rssi", &rssi); + printf("wifi connected=%d state=%s ssid=%s ip=%s rssi=%d\n", + wc, wstate[0] ? wstate : "unknown", ssid[0] ? ssid : "-", + wip[0] ? wip : "-", rssi); + } + /* bt */ + { + char obj[192]; + slice_obj(js, "bt", obj, sizeof obj); + char baddr[32]; + int bavail = strstr(obj, "\"available\":true") != NULL; + int bpow = strstr(obj, "\"powered\":true") != NULL; + jstr(obj, "address", baddr, sizeof baddr); + int bpaired = -1; + jint(obj, "paired", &bpaired); + printf("bt available=%d powered=%d address=%s paired=%d\n", + bavail, bpow, baddr[0] ? baddr : "-", bpaired); + } + /* rfkill 数组 */ + { + const char *rf = strstr(js, "\"rfkill\":["); + if (rf) { + const char *ctx2 = NULL; + for (;;) { + char tmp[96]; + if (next_obj(rf, "type", &ctx2, tmp, sizeof tmp) != 0) break; + char type[16]; int i = 0; + while (tmp[i] && tmp[i] != '"' && i < 15) { type[i] = tmp[i]; i++; } + type[i] = 0; + int soft = -1, hard = -1; + jint(tmp, "soft", &soft); + jint(tmp, "hard", &hard); + printf("rfkill:%s soft=%d hard=%d\n", type, soft, hard); + } + } + } + return 0; +} + +static int cli_iface(const char *name) +{ + char js[MEOW_IPC_MAX]; + m_net_iface_req_t q; + memset(&q, 0, sizeof q); + if (name) snprintf(q.name, sizeof q.name, "%s", name); + if (cli_ipc_json(MSG_NET_IFACE_REQ, &q, sizeof q, js, sizeof js) != 0) { + fprintf(stderr, "meow_net iface: 守护不可达(%s)\n", g_sock); + return 1; + } + printf("%s\n", js); + return 0; +} + +static const char *usage(void) +{ + return "status | iface [name] | wifi scan|networks|connect [,psk]|disconnect|on|off" + " | bt status|on|off|paired | rfkill [ ] | udhcpc renew|release [if] | help"; +} + +static int cli_help(void) +{ + printf("meow_net — MeowOS 网络管理 CLI(meow_netmgrd 的 IPC 薄壳)\n"); + printf("usage: meow_net <%s>\n", usage()); + printf("\n读级任意调用者;写级(wifi/bt on|off、connect、rfkill set、udhcpc)仅系统身份,且 HibyOS 模式只读。\n"); + return 0; +} + +static int cli_run(int n, char **v) +{ + const char *cmd = v[0]; + const char *arg = (n > 1) ? v[1] : NULL; + const char *arg2 = (n > 2) ? v[2] : NULL; + if (!strcmp(cmd, "status")) return cli_status(); + if (!strcmp(cmd, "iface")) return cli_iface(arg); + if (!strcmp(cmd, "help") || !strcmp(cmd, "-h") || !strcmp(cmd, "--help")) return cli_help(); + if (!strcmp(cmd, "wifi")) { + if (!arg) { fprintf(stderr, "wifi 子命令缺失(%s)\n", usage()); return 1; } + if (!strcmp(arg, "on") || !strcmp(arg, "off")) return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL); + if (!strcmp(arg, "connect")) { + if (!arg2) { fprintf(stderr, "connect 需 [,psk]\n"); return 1; } + return cli_cmd_result(MSG_NET_WIFI_CTL, "connect", arg2); + } + return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL); + } + if (!strcmp(cmd, "bt")) { + if (!arg) { fprintf(stderr, "bt 子命令缺失(%s)\n", usage()); return 1; } + return cli_cmd_result(MSG_NET_BT_CTL, arg, NULL); + } + if (!strcmp(cmd, "rfkill")) { + if (!arg) return cli_status(); /* 读 rfkill 表 → status JSON 的 rfkill[] */ + if (!arg2) { fprintf(stderr, "rfkill 需 \n"); return 1; } + char combined[64]; + snprintf(combined, sizeof combined, "%s %s", arg, arg2); + return cli_cmd_result(MSG_NET_WIFI_CTL, "rfkill", combined); + } + if (!strcmp(cmd, "udhcpc")) { + if (!arg) { fprintf(stderr, "udhcpc 需 renew|release [if]\n"); return 1; } + char combined[64]; + snprintf(combined, sizeof combined, "%s %s", arg, arg2 ? arg2 : ""); + return cli_cmd_result(MSG_NET_WIFI_CTL, "udhcpc", combined); + } + fprintf(stderr, "usage: meow_net <%s>\n", usage()); + return 1; +} + +/* ---------------- main:单一二进制双形态 ---------------- */ +int main(int argc, char **argv) +{ + const char *e; + if ((e = getenv("MEOW_NET_DIR")) && e[0]) snprintf(g_net_dir, sizeof g_net_dir, "%s", e); + if ((e = getenv("MEOW_NETDEV")) && e[0]) snprintf(g_netdev, sizeof g_netdev, "%s", e); + if ((e = getenv("MEOW_NET_ROUTE")) && e[0]) snprintf(g_net_route, sizeof g_net_route, "%s", e); + if ((e = getenv("MEOW_FIBTRIE")) && e[0]) snprintf(g_fibtrie, sizeof g_fibtrie, "%s", e); + if ((e = getenv("MEOW_RESOLV")) && e[0]) snprintf(g_resolv, sizeof g_resolv, "%s", e); + if ((e = getenv("MEOW_RFKILL_DIR")) && e[0]) snprintf(g_rfkill_dir, sizeof g_rfkill_dir, "%s", e); + if ((e = getenv("MEOW_BT_DIR")) && e[0]) snprintf(g_bt_dir, sizeof g_bt_dir, "%s", e); + if ((e = getenv("MEOW_BOOT_MODE")) && e[0]) snprintf(g_boot_mode, sizeof g_boot_mode, "%s", e); + if ((e = getenv("MEOW_LOG_DIR")) && e[0]) snprintf(g_log_dir, sizeof g_log_dir, "%s", e); + if ((e = getenv("MEOW_BOOTTS_CACHE")) && e[0]) snprintf(g_bootts_cache, sizeof g_bootts_cache, "%s", e); + if ((e = getenv("MEOW_WPA_CLI")) && e[0]) g_wpa_cli = e; + if ((e = getenv("MEOW_HCICONFIG")) && e[0]) g_hciconfig = e; + if ((e = getenv("MEOW_BLUETOOTHCTL")) && e[0]) g_bluetoothctl = e; + if ((e = getenv("MEOW_IP")) && e[0]) g_ip = e; + if ((e = getenv("MEOW_WIFI_IF")) && e[0]) g_wifi_if = e; + if ((e = getenv("MEOW_NET_SOCK")) && e[0]) g_sock = e; + if ((e = getenv("MEOW_NET_POLL_SEC")) && atoi(e) > 0) g_poll_sec = atoi(e); + + int i = 1; + for (; i < argc; i++) { + if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i]; + else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + printf("meow_netmgrd / meow_net (%s)\n", usage()); + return 0; + } + else break; + } + + /* argv[0] basename=meow_net → 强制 CLI(双形态);meow_netmgrd 空参 → 守护常驻。 */ + const char *base = strrchr(argv[0], '/'); + base = base ? base + 1 : argv[0]; + if (!strcmp(base, "meow_net")) { + if (i >= argc) return cli_help(); + return cli_run(argc - i, argv + i); + } + if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */ + return cli_run(argc - i, argv + i); +} diff --git a/patch_v2/meow_netmgrd/meow_netmgrd.conf b/patch_v2/meow_netmgrd/meow_netmgrd.conf new file mode 100644 index 0000000..77193cb --- /dev/null +++ b/patch_v2/meow_netmgrd/meow_netmgrd.conf @@ -0,0 +1,16 @@ +# meow_netmgrd 板载服务配置(svcd 托管;由 S89meow_svcd 首启预置到 /usr/data/meow/services.d/) +# +# trigger=boot(NETMGR_DESIGN §1/§13,用户定案 2026-09-01):开机即常驻,接口统一层(WiFi/BT/ +# USB-eth/rfkill/IP/路由/DNS)的查询与控制权威。HibyOS 只读降级在进程内门控: +# /tmp/meow_boot_mode != meow(含开机早期文件缺失)→ 读级查询放行,一切写操作拒 + 记日志; +# 文件落地为 meow 后下一采样拍自动转全功能(热切换,无需重启)。 +# 与 audiod 的 trigger=manual+exit(0) 门控不同:本服务读侧天然无害,HibyOS 下挂只读查询器零干扰。 +# restart=on-fail:崩溃只丢接口层缓存,svcd 退避重拉,不级联(FRAMEWORK §10)。 +exec=/usr/bin/meow_netmgrd +args= +enabled=1 +restart=on-fail +backoff_ms=2000 +max_restart=5 +trigger=boot +cap= diff --git a/patch_v2/meow_netmgrd/meow_netmgrd_host b/patch_v2/meow_netmgrd/meow_netmgrd_host new file mode 100755 index 0000000000000000000000000000000000000000..51522a4dca381e85c0360e00f5a2f622c863b0f6 GIT binary patch literal 77656 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 bdj2#_^dFm_6*G3&jJGUcQM#Q7saUQ6sb!ka literal 0 HcmV?d00001 diff --git a/patch_v2/meow_netmgrd/netmgrd_authtest b/patch_v2/meow_netmgrd/netmgrd_authtest new file mode 100755 index 0000000000000000000000000000000000000000..0d0cb99ef61c762d68ad2242c623c7ba876b5b7c GIT binary patch literal 144816 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 +#include +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond) do { if (!(cond)) { printf(" FAIL: %s (line %d)\n", #cond, __LINE__); g_fail++; } \ + else { printf(" ok : %s\n", #cond); } } while (0) + +static void wf(const char *p, const char *s) +{ + FILE *f = fopen(p, "w"); + if (f) { fputs(s, f); fclose(f); } +} + +int main(void) +{ + static const char *T = "/tmp/nmg_authtest"; + char p[192]; + caller_t c; + + printf("== classify_exe ==\n"); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/com.example.tool/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "com.example.tool") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/sub/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/meow_net", &c); + CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "meow_net") == 0); /* /usr/bin/meow_* 前缀 → 系统 */ + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/meow_launcherd", &c); + CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "os.meow.launcher") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/wpa_cli", &c); + CHECK(c.kind == CALL_OTHER); /* 非 meow_* 系统二进制 */ + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/justadirty/k", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "justadirty") == 0); /* 第三方包(非 os.meow.*) */ + CHECK(is_sys_identity(&c) == 0); + + printf("== is_sys_identity ==\n"); + c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.tst"); + CHECK(is_sys_identity(&c) == 1); + c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "com.example.tool"); + CHECK(is_sys_identity(&c) == 0); + c.kind = CALL_SYS; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.launcher"); + CHECK(is_sys_identity(&c) == 1); + c.kind = CALL_OTHER; c.pkg[0] = 0; + CHECK(is_sys_identity(&c) == 0); + + printf("== wifi_cmd_is_write / bt_cmd_is_write ==\n"); + CHECK(wifi_cmd_is_write("status", NULL) == 0); + CHECK(wifi_cmd_is_write("networks", NULL) == 0); + CHECK(wifi_cmd_is_write("scan", NULL) == 1); + CHECK(wifi_cmd_is_write("connect", "ssid") == 1); + CHECK(wifi_cmd_is_write("disconnect", NULL) == 1); + CHECK(wifi_cmd_is_write("on", NULL) == 1); + CHECK(wifi_cmd_is_write("off", NULL) == 1); + CHECK(wifi_cmd_is_write("rfkill", "list") == 0); + CHECK(wifi_cmd_is_write("rfkill", "wlan on") == 1); + CHECK(wifi_cmd_is_write("udhcpc", "renew wlan0") == 1); + CHECK(bt_cmd_is_write("status") == 0); + CHECK(bt_cmd_is_write("paired") == 0); + CHECK(bt_cmd_is_write("on") == 1); + CHECK(bt_cmd_is_write("off") == 1); + + printf("== authorize 决策(系统身份 × MeowOS 模式)==\n"); + caller_t sys; memset(&sys, 0, sizeof sys); sys.kind = CALL_SYS; snprintf(sys.pkg, sizeof sys.pkg, "%s", "meow_net"); + caller_t pkgm; memset(&pkgm, 0, sizeof pkgm); pkgm.kind = CALL_PKG; snprintf(pkgm.pkg, sizeof pkgm.pkg, "%s", "os.meow.panel"); + caller_t third; memset(&third, 0, sizeof third); third.kind = CALL_PKG; snprintf(third.pkg, sizeof third.pkg, "%s", "com.example.tool"); + caller_t other; memset(&other, 0, sizeof other); other.kind = CALL_OTHER; other.uid = 1000; + /* 读级:任意放行,与身份/模式无关 */ + CHECK(wifi_write_authorize(&other, 0, "networks", NULL) == MEOW_OK); + CHECK(bt_write_authorize(&other, 0, "status") == MEOW_OK); + CHECK(wifi_write_authorize(&third, 1, "rfkill", "list") == MEOW_OK); + /* 写级:only 系统身份 + meow 模式 */ + CHECK(wifi_write_authorize(&sys, 1, "on", NULL) == MEOW_OK); + CHECK(wifi_write_authorize(&pkgm, 1, "connect", "ssid") == MEOW_OK); + CHECK(bt_write_authorize(&sys, 1, "off") == MEOW_OK); + CHECK(wifi_write_authorize(&third, 1, "scan", NULL) == MEOW_ERR_PERM); /* 第三方包 → 拒 */ + CHECK(wifi_write_authorize(&other, 1, "on", NULL) == MEOW_ERR_PERM); /* CALL_OTHER + uid → 拒 */ + CHECK(wifi_write_authorize(&sys, 0, "on", NULL) == MEOW_ERR_PERM); /* HibyOS 只读 → 拒 */ + CHECK(bt_write_authorize(&sys, 0, "on") == MEOW_ERR_PERM); /* HibyOS 只读 → 拒 */ + CHECK(bt_write_authorize(&pkgm, 1, "off") == MEOW_OK); + + printf("== 接口层动作(假 sysfs + 桩命令)==\n"); + /* 搭假 /sys/class/net + rfkill + bt(先建祖先目录) */ + mkdir(T, 0755); + snprintf(p, sizeof p, "%s/net", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/net/lo", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/net/wlan0", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/rfkill", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/rfkill/rfkill0", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/bt", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/bt/hci0", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/net/lo/operstate", T); wf(p, "up\n"); + snprintf(p, sizeof p, "%s/net/wlan0/operstate", T); wf(p, "up\n"); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/type", T); wf(p, "wlan\n"); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/soft", T); wf(p, "0\n"); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/hard", T); wf(p, "0\n"); + snprintf(p, sizeof p, "%s/bt/hci0/address", T); wf(p, "00:1A:7D:DA:71:13\n"); + snprintf(p, sizeof p, "%s/bt/hci0/power", T); wf(p, "on\n"); + snprintf(p, sizeof p, "%s/netdev", T); wf(p, "Inter-| Receive | Transmit\n lo: 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0\n wlan0: 5000000 5000 0 0 0 0 0 0 1000000 1000 0 0 0 0 0 0\n"); + snprintf(p, sizeof p, "%s/route", T); wf(p, "Iface\tDestination\tGateway \tFlags\nwlan0\t005EC20A\t6F3DC20A\t0003\nlo\t0100007F\t00000000\t0001\n"); + snprintf(p, sizeof p, "%s/resolv", T); wf(p, "nameserver 223.5.5.5\nnameserver 10.78.193.1\n"); + snprintf(p, sizeof p, "%s/boot_mode", T); wf(p, "meow\n"); + /* 桩工具 */ + snprintf(p, sizeof p, "%s/wpa_cli", T); wf(p, + "#!/bin/sh\n" + "case \"$1\" in\n" + " status) echo 'wpa_state=COMPLETED'; echo 'ssid=MyWiFi'; echo 'ip_address=10.78.193.50'; echo 'signal=-45';;\n" + " scan) echo OK;;\n" + " scan_results) echo 'bssid / frequency / signal / flags / ssid'; echo 'xx:xx / 2412 / -50 / WPA2 / TestAP';;\n" + " list_networks) echo 'network id / ssid / bssid / flags'; echo '0\tMyWiFi\tany\t[CURRENT]';;\n" + " add_network) echo 5;;\n" + " set_network) echo OK;;\n" + " enable_network) echo OK;;\n" + " select_network) echo OK;;\n" + " disconnect) echo OK;;\n" + "esac\n" + "exit 0\n"); + snprintf(p, sizeof p, "%s/hciconfig", T); wf(p, + "#!/bin/sh\n" + "echo 'hci0: Type: Primary Bus: USB'\n" + "echo ' BD Address: 00:1A:7D:DA:71:13 ACL MTU: 1021:8'\n" + "echo ' UP RUNNING PSCAN'\n"); + snprintf(p, sizeof p, "%s/bluetoothctl", T); wf(p, + "#!/bin/sh\n" + "echo 'Device 00:1A:7D:DA:71:13 My Headset'\n" + "echo 'Device 11:22:33:44:55:66 Speaker'\n"); + snprintf(p, sizeof p, "%s/ip", T); wf(p, + "#!/bin/sh\n" + "if [ \"$6\" = \"wlan0\" ]; then echo \"2: wlan0 inet 10.78.193.50/24 brd 255.255.255.255 scope global wlan0\"; fi\n" + "exit 0\n"); + snprintf(p, sizeof p, "%s/wpa_cli", T); chmod(p, 0755); + snprintf(p, sizeof p, "%s/hciconfig", T); chmod(p, 0755); + snprintf(p, sizeof p, "%s/bluetoothctl", T); chmod(p, 0755); + snprintf(p, sizeof p, "%s/ip", T); chmod(p, 0755); + + g_net_dir[0] = 0; snprintf(g_net_dir, sizeof g_net_dir, "%s/net", T); + g_netdev[0] = 0; snprintf(g_netdev, sizeof g_netdev, "%s/netdev", T); + g_net_route[0] = 0; snprintf(g_net_route, sizeof g_net_route, "%s/route", T); + g_resolv[0] = 0; snprintf(g_resolv, sizeof g_resolv, "%s/resolv", T); + g_rfkill_dir[0] = 0; snprintf(g_rfkill_dir, sizeof g_rfkill_dir, "%s/rfkill", T); + g_bt_dir[0] = 0; snprintf(g_bt_dir, sizeof g_bt_dir, "%s/bt", T); + g_boot_mode[0] = 0; snprintf(g_boot_mode, sizeof g_boot_mode, "%s/boot_mode", T); + g_wpa_cli = ""; { char x[160]; snprintf(x, sizeof x, "%s/wpa_cli", T); static char wc[160]; snprintf(wc, sizeof wc, "%s", x); g_wpa_cli = wc; } + g_hciconfig = ""; { char x[160]; snprintf(x, sizeof x, "%s/hciconfig", T); static char hc[160]; snprintf(hc, sizeof hc, "%s", x); g_hciconfig = hc; } + g_bluetoothctl = ""; { char x[160]; snprintf(x, sizeof x, "%s/bluetoothctl", T); static char bc[160]; snprintf(bc, sizeof bc, "%s", x); g_bluetoothctl = bc; } + g_ip = ""; { char x[160]; snprintf(x, sizeof x, "%s/ip", T); static char ip[160]; snprintf(ip, sizeof ip, "%s", x); g_ip = ip; } + + /* rfkill_set_block:wlan → soft=1 */ + CHECK(rfkill_set_block("wlan", 1) == 0); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/soft", T); + char rb[8]; read_file_txt(p, rb, sizeof rb); + CHECK(strcmp(rb, "1") == 0); + + /* enum_ifaces(detail=1):ip/gw/rx/tx */ + enum_ifaces(1); + CHECK(g_iface_n >= 2); + int wlan = -1; + for (int i = 0; i < g_iface_n; i++) if (!strcmp(g_ifaces[i].name, "wlan0")) wlan = i; + CHECK(wlan >= 0); + CHECK(strcmp(g_ifaces[wlan].kind, "wifi") == 0); + CHECK(g_ifaces[wlan].up == 1); + CHECK(strcmp(g_ifaces[wlan].gw, "10.194.61.111") == 0); /* 0x6F3DC20A LE → 10.194.61.111 */ + CHECK(g_ifaces[wlan].rx == 5000000); + + /* wifi_networks(读) + wifi_scan(写执行路径) */ + ctl_result_t r; + wifi_networks(&r); + CHECK(r.code == MEOW_OK && strstr(r.text, "MyWiFi") != NULL); + wifi_scan(&r); + CHECK(r.code == MEOW_OK && strstr(r.text, "TestAP") != NULL); + + /* bt_status:available=1 powered=1 address=... paired=2 */ + bt_t b; + bt_status(&b); + CHECK(b.available == 1 && b.powered == 1); + CHECK(strcmp(b.address, "00:1A:7D:DA:71:13") == 0); + CHECK(b.paired_n == 2); + + /* 写级执行(wifi_onoff off → rfkill block wlan)已在 rfkill_set_block 覆盖;此处联调 wifi_ctl_execute */ + wifi_ctl_execute("networks", NULL, &r); + CHECK(r.code == MEOW_OK && strstr(r.text, "MyWiFi") != NULL); + /* connect:现有 ssid 复用(enable+select,network_id=0);新 ssid 走 add/set/enable/select(id=5)。 */ + wifi_ctl_execute("connect", "MyWiFi", &r); + CHECK(r.code == MEOW_OK && strstr(r.text, "ssid=MyWiFi") != NULL && strstr(r.text, "network_id=0") != NULL); + wifi_ctl_execute("connect", "NewAP,secret", &r); + CHECK(r.code == MEOW_OK && strstr(r.text, "ssid=NewAP") != NULL && strstr(r.text, "network_id=5") != NULL); + wifi_ctl_execute("disconnect", NULL, &r); + CHECK(r.code == MEOW_OK); + + if (g_fail) { printf("\nNETMGR AUTH TEST: %d FAIL\n", g_fail); return 1; } + printf("\nNETMGR AUTH TEST: ALL PASS\n"); + return 0; +} diff --git a/patch_v2/meow_netmgrd/test_netmgrd.sh b/patch_v2/meow_netmgrd/test_netmgrd.sh new file mode 100755 index 0000000..0ab5784 --- /dev/null +++ b/patch_v2/meow_netmgrd/test_netmgrd.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# test_netmgrd.sh — meow_netmgrd 宿主桩测试(假 sysfs/procfs 节点 + 桩后端命令,MEOW_* env 覆盖)。 +# 用法:./test_netmgrd.sh [host二进制路径](默认 ./meow_netmgrd_host;可传 mipsel 二进制 + QEMU 前缀) +# QEMU="qemu-mipsel" ./test_netmgrd.sh ./meow_netmgrd # qemu 验证 +set -u +cd "$(dirname "$0")" +BIN="${1:-./meow_netmgrd_host}" +Q="${QEMU:-}" +T=$(mktemp -d /tmp/netmgrdtest.XXXXXX) +trap 'kill $DPID 2>/dev/null; rm -rf "$T"' EXIT +PASS=0; FAIL=0 +ck() { # ck <名> <期望子串> <实际> + if echo "$3" | grep -qF "$2"; then PASS=$((PASS+1)); echo "PASS $1"; + else FAIL=$((FAIL+1)); echo "FAIL $1: 期望含 [$2] 实际=[$3]"; fi +} + +# ---- 搭假节点 ---- +NET="$T/net"; RF="$T/rfkill"; BT="$T/bt" +mkdir -p "$NET/lo" "$NET/wlan0" "$RF/rfkill0" "$RF/rfkill1" "$BT/hci0" +echo up > "$NET/lo/operstate" +echo up > "$NET/wlan0/operstate" +echo wlan > "$RF/rfkill0/type"; echo 0 > "$RF/rfkill0/soft"; echo 0 > "$RF/rfkill0/hard" +echo bluetooth > "$RF/rfkill1/type"; echo 1 > "$RF/rfkill1/soft"; echo 0 > "$RF/rfkill1/hard" +echo 00:1A:7D:DA:71:13 > "$BT/hci0/address" +echo on > "$BT/hci0/power" +cat > "$T/netdev" <<'EOF' +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + lo: 1000 10 0 0 0 0 0 0 1000 10 0 0 0 0 0 0 + wlan0: 5000000 5000 0 0 0 0 0 0 1000000 1000 0 0 0 0 0 0 +EOF +cat > "$T/route" <<'EOF' +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +wlan0 005EC20A 6F3DC20A 0003 0 0 0 00FFFFFF 0 0 0 +lo 0100007F 00000000 0001 0 0 0 000000FF 0 0 0 +EOF +printf 'nameserver 223.5.5.5\nnameserver 10.78.193.1\n' > "$T/resolv" +echo meow > "$T/boot_mode" + +# 桩后端命令(popen stubbing) +cat > "$T/wpa_cli" <<'EOF' +#!/bin/sh +case "$1" in + status) echo "wpa_state=COMPLETED"; echo "ssid=MyWiFi"; echo "ip_address=10.78.193.50"; echo "signal=-45";; + scan) echo OK;; + scan_results) printf 'bssid / frequency / signal level / flags / ssid\nxx:xx / 2412 / -50 / WPA2 / TestAP\n';; + list_networks) printf 'network id / ssid / bssid / flags\n0\tMyWiFi\tany\t[CURRENT]\n';; + add_network) echo 1;; + set_network) echo OK;; + enable_network) echo OK;; + select_network) echo OK;; + disconnect) echo OK;; +esac +exit 0 +EOF +cat > "$T/hciconfig" <<'EOF' +#!/bin/sh +echo "hci0: Type: Primary Bus: USB" +echo " BD Address: 00:1A:7D:DA:71:13 ACL MTU: 1021:8 SCO MTU: 64:1" +echo " UP RUNNING PSCAN" +EOF +cat > "$T/bluetoothctl" <<'EOF' +#!/bin/sh +echo "Device 00:1A:7D:DA:71:13 My Headset" +echo "Device 11:22:33:44:55:66 Speaker" +EOF +cat > "$T/ip" <<'EOF' +#!/bin/sh +for a in "$@"; do + if [ "$a" = "wlan0" ]; then echo "2: wlan0 inet 10.78.193.50/24 brd 255.255.255.255 scope global wlan0"; fi + if [ "$a" = "lo" ]; then echo "1: lo inet 127.0.0.1/8 scope host lo"; fi +done +exit 0 +EOF +for f in wpa_cli hciconfig bluetoothctl ip; do chmod +x "$T/$f"; done + +mkdir -p "$T/logs" "$T/pid" +ENVS="MEOW_NET_DIR=$NET MEOW_NETDEV=$T/netdev MEOW_NET_ROUTE=$T/route MEOW_RESOLV=$T/resolv + MEOW_RFKILL_DIR=$RF MEOW_BT_DIR=$BT MEOW_BOOT_MODE=$T/boot_mode MEOW_BOOTTS_CACHE=$T/bootts + MEOW_IP=$T/ip MEOW_WPA_CLI=$T/wpa_cli MEOW_HCICONFIG=$T/hciconfig MEOW_BLUETOOTHCTL=$T/bluetoothctl + MEOW_NET_SOCK=$T/net.sock MEOW_NET_POLL_SEC=1 MEOW_LOG_DIR=$T/logs" +ln -sf "$(readlink -f "$BIN")" "$T/meow_net" # argv[0] 双形态 → CLI + +# ---- T0: 守护起 ---- +env $ENVS $Q "$BIN" --sock "$T/net.sock" >"$T/daemon.log" 2>&1 & +DPID=$! +for i in $(seq 1 30); do [ -S "$T/net.sock" ] && break; sleep 0.2; done +[ -S "$T/net.sock" ] || { echo "FAIL T0: daemon socket 未出现"; cat "$T/daemon.log"; exit 1; } +PASS=$((PASS+1)); echo "PASS T0 daemon up" + +# ---- T1: status(读级,任意调用者)---- +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T1a "mode=meow src=daemon" "$OUT" +ck T1b "iface:lo kind=lo up=true" "$OUT" +ck T1c "iface:wlan0 kind=wifi up=true" "$OUT" +ck T1d "ip=10.78.193.50" "$OUT" # 若 iface 行 ip 解析成功(ip 桩已修)——实际由 wlan gw 行给,见下 +ck T1e "gw=10.194.61.111" "$OUT" +ck T1f "wifi connected=1 state=COMPLETED ssid=MyWiFi" "$OUT" +ck T1g "bt available=1 powered=1 address=00:1A:7D:DA:71:13 paired=2" "$OUT" +ck T1h "rfkill:wlan soft=0 hard=0" "$OUT" +ck T1i "rfkill:bluetooth soft=1 hard=0" "$OUT" + +# ---- T2: iface 单/全 ---- +OUT=$(env $ENVS $Q "$T/meow_net" iface wlan0 2>&1) +ck T2a '"name":"wlan0"' "$OUT" +ck T2b '"kind":"wifi"' "$OUT" +ck T2c '"ip":"10.78.193.50"' "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" iface 2>&1) +ck T2d '"name":"lo"' "$OUT" + +# ---- T3: wifi networks(读级)---- +OUT=$(env $ENVS $Q "$T/meow_net" wifi networks 2>&1) +ck T3a "MyWiFi" "$OUT" + +# ---- T4: wifi scan(写级;宿主 CLI=CALL_OTHER → 拒)---- +OUT=$(env $ENVS $Q "$T/meow_net" wifi scan 2>&1) +ck T4a "permission denied" "$OUT" + +# ---- T5: bt status / bt paired(读级)---- +OUT=$(env $ENVS $Q "$T/meow_net" bt status 2>&1) +ck T5a "available=1 powered=1" "$OUT" +ck T5b "paired=2" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" bt paired 2>&1) +ck T5c "My Headset" "$OUT" +ck T5d "Speaker" "$OUT" + +# ---- T6: HibyOS 只读降级(读可查,写拒)---- +echo hiby > "$T/boot_mode" +sleep 2 # 等守护下一拍热切换 +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T6a "mode=readonly" "$OUT" +ck T6b "iface:wlan0" "$OUT" # 读侧仍可查 +OUT=$(env $ENVS $Q "$T/meow_net" wifi networks 2>&1) +ck T6c "MyWiFi" "$OUT" # 读级子命令仍可用 +OUT=$(env $ENVS $Q "$T/meow_net" wifi on 2>&1); RC=$? +ck T6d "permission denied" "$OUT" +echo meow > "$T/boot_mode" +sleep 2 +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T6e "mode=meow" "$OUT" # 热切换回全功能 + +# ---- T7: 守护日志(每次开机新文件;接口 add/写拒绝 必记)---- +sleep 1 +LOGS=$(ls "$T/logs"/net_*.log 2>/dev/null | head -1) +[ -n "$LOGS" ] && { PASS=$((PASS+1)); echo "PASS T7a log file: $(basename "$LOGS")"; } || { FAIL=$((FAIL+1)); echo "FAIL T7a 无日志文件"; } +[ -n "$LOGS" ] && { grep -q "iface add kind=wifi name=wlan0" "$LOGS" && { PASS=$((PASS+1)); echo "PASS T7b iface add 已记"; } || { FAIL=$((FAIL+1)); echo "FAIL T7b 未记 iface add"; }; } + +# ---- T8: 守护停 → CLI help 本地可用;status 报守护不可达 ---- +kill $DPID 2>/dev/null; wait $DPID 2>/dev/null; DPID="" +sleep 0.5 +OUT=$(env $ENVS $Q "$T/meow_net" help 2>&1) +ck T8a "usage: meow_net" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T8b "守护不可达" "$OUT" + +# ---- T9: 无参 → help;未知子命令 → usage ---- +OUT=$(env $ENVS $Q "$T/meow_net" 2>&1) +ck T9a "usage: meow_net" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" bogus 2>&1); RC=$? +ck T9b "usage: meow_net" "$OUT" + +echo "==============================" +echo "PASS=$PASS FAIL=$FAIL" +[ $FAIL -eq 0 ]