From 2a6c4e97c562e9f2b51a4abaea31bf964959822c Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 19:07:47 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=9F=BA=E5=BB=BA=E7=BA=BF(a?= =?UTF-8?q?gent-139):=20svcd=20subreaper=E5=8A=A0=E5=9B=BA=20+=20meow=5Fsy?= =?UTF-8?q?sui=E6=80=BB=E7=9B=91=20+=20=E5=90=AF=E5=8A=A8=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - svcd: 审计确认现网已子进程化(fork+execv+setpgid+waitpid+SIGCHLD+set_cloexec,无system()); 增量 prctl(PR_SET_CHILD_SUBREAPER) 代收孤儿孙进程 → fb2d608b - meow_sysui(新增): 系统进程总监, 仿svcd成熟模式(信号自管道/poll/双探测kill/waitpid/subreaper/状态机), 监护自己fork+exec进程, 按sysui.d/.conf逐条拉起, 无级联/有限次兜底/主循环绝不exit/空配置空转 → 173013cc - 启动链 S70meow_sysui → S89meow_svcd → S90meow_bootmenu; svcd拉起权判归(sysui在→交监管,不在→回退现网重挂循环) - 宿主集成测试过(常崩/稳定隔离/无级联/subreaper收尸/端到端); meow_ui.a未动 - 需加到固件打包(build_fw_apps_m267.sh): sysui→/usr/bin/meow_sysui, svcd覆盖(fb2d608b), S70+S89; 校验 svcd 762e84d0→fb2d608b, 新增 sysui 173013cc --- patch_v2/meow_svcd/S89meow_svcd | 36 +- patch_v2/meow_svcd/meow_svcd | Bin 118524 -> 118780 bytes patch_v2/meow_svcd/meow_svcd.c | 6 + patch_v2/meow_svcd/meow_svcd.md | 2 + patch_v2/meow_sysui/S70meow_sysui | 92 +++ patch_v2/meow_sysui/build_sysui.sh | 28 + patch_v2/meow_sysui/meow_sysui | Bin 0 -> 113100 bytes patch_v2/meow_sysui/meow_sysui.c | 1105 ++++++++++++++++++++++++++++ patch_v2/meow_sysui/meow_sysui.md | 130 ++++ 9 files changed, 1391 insertions(+), 8 deletions(-) create mode 100644 patch_v2/meow_sysui/S70meow_sysui create mode 100755 patch_v2/meow_sysui/build_sysui.sh create mode 100755 patch_v2/meow_sysui/meow_sysui create mode 100644 patch_v2/meow_sysui/meow_sysui.c create mode 100644 patch_v2/meow_sysui/meow_sysui.md diff --git a/patch_v2/meow_svcd/S89meow_svcd b/patch_v2/meow_svcd/S89meow_svcd index 7e7e42b..9738e07 100755 --- a/patch_v2/meow_svcd/S89meow_svcd +++ b/patch_v2/meow_svcd/S89meow_svcd @@ -1,16 +1,19 @@ #!/bin/sh # -# S89meow_svcd - MeowOS 服务管理器开机自启 + 主机名应用 + 板载服务预置 +# S89meow_svcd - MeowOS 服务管理器协同启动 + 主机名应用 + 板载服务预置 # -# 位置:S84ssh 之后、S90meow_bootmenu 之前(S80 < S84 < S89 < S90)。 +# 位置:S70meow_sysui 之后、S90meow_bootmenu 之前(S70 < S84 < S89 < S90)。 # * rcS 顺序执行 /etc/init.d/S??*,而 S90meow_bootmenu 里 `exec /usr/bin/meow_bootmenu # --fork` 会阻塞 rcS(选单后 exec meow_launcherd 或 fork 播放器监督),故任何 S90 # 之后的 init.d(如 S91/S92)**都不会执行**——svcd 必须排在 S90 之前。 -# * 本脚本只装「稳定框架/KB级设置」:meow_svcd(固件 /usr/bin/)+ 板载服务配置 -# (/usr/data/meow/services.d/,Data 区,首启预置 mdnsd.conf)+ 应用主机名。 +# * 本脚本只装「稳定框架/KB级设置」:主机名 + 板载服务配置 +# (/usr/data/meow/services.d/,Data 区,首启预置 mdnsd.conf)。 +# * **svcd 拉起权**:若 meow_sysui(系统进程总监)已运行 → svcd 由 sysui 监管拉起 +# (sysui.d/svcd.conf),本脚本仅在其已运行时 `meow_svcd reload` 让 svcd 补载 mdnsd.conf, +# 不重复拉起、不重挂(避免与 sysui 双重监护 svcd)。仅当 **sysui 未运行**时才回退到 +# 现网的「重挂循环」自监护(旧固件/无 sysui 场景,向后兼容)。 # -# 红线:不 killall、不碰播放器信号、不刷机。svcd 以 -f 前台被本脚本重挂循环拉起 -#(失败降级不死——若 svcd 死,其所托管服务已是独立子进程、被 init 收养继续运行); +# 红线:不 killall、不碰播放器信号、不刷机。svcd 以 -f 前台被 sysui(或重挂循环)拉起; # 服务本身(mdnsd)由 svcd 按 restart=on-fail 托管。 # case "$1" in @@ -54,9 +57,26 @@ EOF echo -n "skip mdnsd.conf (/usr/data/meow not writable) " fi - # 3) 拉起 meow_svcd(-f 前台,重挂循环防死;/tmp/meow_svcd.stop 标记防停循环) + # 3) svcd 拉起权判归:sysui 运行 → 交给 sysui 监管;否则回退重挂循环。 rm -f /tmp/meow_svcd.stop - if [ -x /usr/bin/meow_svcd ]; then + sysui_alive=0 + if [ -x /usr/bin/meow_sysui ] && ps | grep -q "[m]eow_sysui"; then + sysui_alive=1 + fi + if [ "$sysui_alive" = "1" ]; then + # svcd 由 meow_sysui 监管拉起;此处仅补载刚预置的 mdnsd.conf(若 svcd 已在跑) + if [ -x /usr/bin/meow_svcd ]; then + if [ -f /tmp/meow_svcd.pid ] && kill -0 "$(cat /tmp/meow_svcd.pid 2>/dev/null)" 2>/dev/null; then + /usr/bin/meow_svcd reload 2>/dev/null + echo -n "supervised by sysui (svcd running, reload) " + else + # svcd 未在跑:交给 sysui 按兜底(有限次重试)拉起,此处不干预,防双重拉起 + echo -n "defer to meow_sysui (svcd down) " + fi + else + echo -n "MISSING /usr/bin/meow_svcd " + fi + elif [ -x /usr/bin/meow_svcd ]; then if [ -f /tmp/meow_svcd.pid ] && kill -0 "$(cat /tmp/meow_svcd.pid 2>/dev/null)" 2>/dev/null; then echo "already running" else diff --git a/patch_v2/meow_svcd/meow_svcd b/patch_v2/meow_svcd/meow_svcd index b27e072e080389feaf4859f87fe745efc5044a49..3a7575f1ff4f693ec0b6b6c34b7044e411ba1152 100755 GIT binary patch delta 25143 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 z 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 rE~Hb(C diff --git a/patch_v2/meow_svcd/meow_svcd.c b/patch_v2/meow_svcd/meow_svcd.c index 6d23e31..5ce3ecd 100644 --- a/patch_v2/meow_svcd/meow_svcd.c +++ b/patch_v2/meow_svcd/meow_svcd.c @@ -66,6 +66,7 @@ #include #include #include +#include #include /* ---- 默认路径 / 常量 ---- */ @@ -1143,6 +1144,11 @@ static void shutdown_all(void) static int start_daemon(void) { if (setup_signals() != 0) return 1; + /* 设为子进程回收者:被监管服务若派生出孙进程,在服务(直接子进程)退出后 + * 由 svcd 收尸而非 reparent 到 init,强化「服务崩溃隔离(僵死/孤儿)」一环。 + * 失败不致命:仅记录并继续。 */ + if (prctl(PR_SET_CHILD_SUBREAPER, 1) != 0) + svcd_log("PR_SET_CHILD_SUBREAPER failed: %s (continue)", strerror(errno)); if (setup_socket() != 0) return 1; write_pidfile(); diff --git a/patch_v2/meow_svcd/meow_svcd.md b/patch_v2/meow_svcd/meow_svcd.md index e2b0e5a..86dc771 100644 --- a/patch_v2/meow_svcd/meow_svcd.md +++ b/patch_v2/meow_svcd/meow_svcd.md @@ -128,6 +128,8 @@ FAILED 崩溃且不再重启(超 max_restart / restart=never) - **不读服务输出**:子进程继承 svcd stdio(stdout/stderr 落同一 sink),svcd **不 设管道读服务体**——服务写多少都不进 svcd 内存,天然规避「输出撑爆 → svcd 崩」。 - **后退避**:重启间隔受 `backoff_ms` 约束,防崩溃-重启热循环拖垮系统。 +- **孤儿/僵死隔离**:`start_daemon()` 设 `PR_SET_CHILD_SUBREAPER`——服务若派生孙进程, + 在服务(直接子进程)退出后由 svcd 收尸而非 reparent 到 init,避免游离/僵死进程;失败仅记日志继续。 - 服务崩**绝不**连带 supervisor:svcd 是 supervisor 的子进程且自身不死;服务是 svcd 的子进程,服务死只影响 svcd 内部一张表。 diff --git a/patch_v2/meow_sysui/S70meow_sysui b/patch_v2/meow_sysui/S70meow_sysui new file mode 100644 index 0000000..93a6c54 --- /dev/null +++ b/patch_v2/meow_sysui/S70meow_sysui @@ -0,0 +1,92 @@ +#!/bin/sh +# +# S70meow_sysui - MeowOS 系统进程总监(Supervisor Tree 顶层) +# +# 位置:**最早**(早于 S84ssh / S89meow_svcd / S90meow_bootmenu)。 +# * 定案启动链:meow_sysui 先起(S70)→ meow_svcd(由 sysui 拉起)→ 各项服务子进程。 +# * S90meow_bootmenu 里 `exec /usr/bin/meow_bootmenu --fork` 会阻塞 rcS,故任何 S90 之后 +# 的 init.d 都不执行——sysui 必须排在 S90 之前(S70 满足)。 +# +# 职责:监管系统进程(launcher / meow_svcd / meow_mdns / …),一个崩只拉起它、绝不级联。 +# 本脚本只做三件事: +# 1) 预置板载监管配置 /usr/data/meow/sysui.d/*.conf(首启建,已存在不覆盖); +# 2) 拉起 meow_sysui(默认后台 daemon); +# 3) stop 时优雅停 sysui(其会 SIGTERM 被监管的 svcd 并收尸)。 +# +# 红线:不 killall、不碰播放器信号、不刷机、不碰 recovery_all。sysui 自身最小依赖, +# 失败兜底(某被监管进程起不来 → 仅记日志 + 有限次重试,不阻塞其它/不拖垮自身)。 +# +case "$1" in + start) + echo -n "Starting meow_sysui (S70): " + + # 1) 预置板载监管配置(Data 区可写才建;已存在不覆盖) + if [ -d /usr/data/meow ] && [ -w /usr/data/meow ]; then + mkdir -p /usr/data/meow/sysui.d + if [ -f /usr/data/meow/sysui.d/svcd.conf ]; then + echo -n "svcd.conf exists " + else + cat > /usr/data/meow/sysui.d/svcd.conf <<'EOF' +# meow_sysui 监管条目:meow_svcd(服务管理器)。系统进程总监 → svcd 被监管 → 服务为 svcd 子进程。 +# 语义同 services.d:key=value,'#'/';' 注释,未知键忽略。无 trigger——enabled=1 即常驻监护。 +exec=/usr/bin/meow_svcd +args=-f +enabled=1 +restart=always +backoff_ms=3000 +max_restart=5 +EOF + chmod 644 /usr/data/meow/sysui.d/svcd.conf + echo -n "provisioned svcd.conf " + fi + # launcher 当前由 S90meow_bootmenu 的 exec 拥有生命周期;待 bootmenu 让位后交 sysui。 + # 此处仅登记(enabled=0),避免双重拉起。mdns 是 svcd 的服务子进程,由 svcd 负责, + # 本阶段不直接监管(或作兜底条目,默认关)。 + if [ -f /usr/data/meow/sysui.d/launcher.conf ]; then + echo -n "launcher.conf exists " + else + cat > /usr/data/meow/sysui.d/launcher.conf <<'EOF' +# 预留:launcher(系统 UI 渲染宿主)。现网由 S90meow_bootmenu exec 拥有 → 启用前需 bootmenu 让位。 +# 真把 launcher 交给 sysui 监管后,把 enabled=1(并停用 bootmenu 的 exec launcher 路径)。 +exec=/usr/bin/meow_launcherd +args= +enabled=0 +restart=always +backoff_ms=3000 +max_restart=5 +EOF + chmod 644 /usr/data/meow/sysui.d/launcher.conf + echo -n "provisioned launcher.conf " + fi + else + echo -n "skip provision (/usr/data/meow not writable) " + fi + + # 2) 拉起 meow_sysui(默认后台 daemon;S89meow_svcd 会因 sysui 在而把 svcd 拉起权交还 sysui) + rm -f /tmp/meow_sysui.stop + if [ -x /usr/bin/meow_sysui ]; then + if [ -f /tmp/meow_sysui.pid ] && kill -0 "$(cat /tmp/meow_sysui.pid 2>/dev/null)" 2>/dev/null; then + echo "already running" + else + /usr/bin/meow_sysui + echo "started" + fi + else + echo "MISSING /usr/bin/meow_sysui" + fi + ;; + stop) + echo -n "Stopping meow_sysui: " + touch /tmp/meow_sysui.stop + for p in $(ps | grep "[m]eow_sysui" | awk '{print $1}'); do kill "$p" 2>/dev/null; done + echo "OK" + ;; + restart|reload) + ;; + *) + echo "Usage: $0 {start|stop}" + exit 1 + ;; +esac + +exit 0 diff --git a/patch_v2/meow_sysui/build_sysui.sh b/patch_v2/meow_sysui/build_sysui.sh new file mode 100755 index 0000000..be8c234 --- /dev/null +++ b/patch_v2/meow_sysui/build_sysui.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# build_sysui.sh — meow_sysui 交叉编译(mipsel 静态)+ 宿主编译可选。 +# +# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。sysui 极简无外部依赖, +# 仅 POSIX + Linux 基础接口(同 svcd),单文件满足「固件内、不入卡」。 +# +# 用法: +# ./build_sysui.sh # 交叉编译 → patch_v2/meow_sysui/meow_sysui(mipsel 静态) +# ./build_sysui.sh host # 宿主编译 → patch_v2/meow_sysui/meow_sysui_host(集成测试用) +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" + +if [ "$1" = "host" ]; then + HOST_CC="${CC_HOST:-cc}" + "$HOST_CC" -Os -Wall -Wextra meow_sysui.c -o meow_sysui_host + echo "== host build ==" + ls -la meow_sysui_host + file meow_sysui_host + exit 0 +fi + +"$CC" -Os -Wall -Wextra -static meow_sysui.c -o meow_sysui +echo "== meow_sysui (mipsel static) ==" +ls -la meow_sysui +md5sum meow_sysui +file meow_sysui diff --git a/patch_v2/meow_sysui/meow_sysui b/patch_v2/meow_sysui/meow_sysui new file mode 100755 index 0000000000000000000000000000000000000000..298f9d67d3302c0191d3dc6c3321a0f18b1a6a74 GIT binary patch literal 113100 zcmd444SZbHb?Cd!nUOS>KNuSUl5yaSbViyW8IO%f1R3ziA7Cej2;&gv;snW-jNRCh zBFV(GyfQK-&uLVRFizs6G>nY_Csj2z7;x_;L7bA zxAXpMpK~-LVUwi4-+R9+INtm0&$ZWHdwuP_&pfz%)h(7~i6!2jEfEuMPuS8-(!dLr zltrbtSsa-r3AsdONjd47*Gx%$94hM*CQYB9EiY!tnuM4~^E6HK+eyMwk4^-qUJ6Tm z>wG| zmA8Im<*HTAy?q@Ux}1T{Tf2I;4anAx?f3L`^$&FP4NR@Dp{IM}=6jtDn>xDh?doiD zf?^$8KHG7Bzs#Rct$}U*LPMXur~m%`ZJQlGCBCI|Yrm}T*zoC|jT`SF+R=A!zue!| zFY#-y;h+9^|K!fJV&A}xPVeTUrH2fx1+p?v}>Fn$2Wkhm! zvExtb`;_r^ZQnfLBzk+cY~FA`jWto^{m%TGP05}OpYEbNDy`q#?V*SGmd*VGUEL;` zYFkXi8$5My}4JqdnRFj)3$-mp3inqt>aIeHc7O% zixTLtLS82d3RD1Z{Xhr zon7}W=w_azgQur^zT!)IKCR>e)(N)s+$%b=-p$?jO5e8bZVlu83*!CPD#Z!Zy_sOm zf7-lJ?z!i_&iu7M2;Na@((y=srt?C;sqMe~r5P8Kon-F0tY z&$jMPi6^hwvW39>+ZZ?T#%r5NqPPc5SEneb1i=)rh|E+-U)PqN4vKBQH{QuhN3XN3 z`_tVZ+NamjIoQ^Fqr>DvaE^{wL!YRNcl>+Rl)wBEUNWlEo|;~42y9R9RmYhTYCqbIQ(g%*@V*HyISw-+S+rUw$2jrSlj!hZS_5O zE?;xc(iJOLExYH=k1bifytQrl>XJq4Bq%ztxqDmJyV`@Fu35hNHdu^dTJOs5?dw_J z6-<6-R|h>WzjO7{74Mh0q~e`7LmBaIT|Mz@lby<-W%=sWj*jO3N)v@W&+pkde=BhJ zkp$IlbEfdT%E?SnDbC|-Q!`3AlVKqD!TgjLO)1#fL2unoch7+1FHI-GOnD`BnR}r5 z^gn+?&(^IS-JM=ychCI(Z5uZ6Patkh_ zyEb-g+cNMWXa2?orQFK-bXQkzzq9dP2M*?J?%q7`ewy-!bfW=c{>CO}QWf9=ock%Q zm3+466;)Z|qBs zd%8sWdbSvb*M-dW2j(mo*xGBF@U(+#l{_a}@sL*nmyb>(X)oS{sTMQ>l1=eqE2Ma` z`l2mRBB+ienmiDvV@rRJ)6t8Z?{qYGpeHDB(e^0z{z^37x5AeEcb>a=ZsYkrPmIUn znc;_X2#dJCDLJ9o5ef7@R8aGGJA%Hi&kz7Jy0t?IT2^k^(sggg7H9qa16@vk*JrkM zb#LgBWj$Lvn0QlMR(JJ7RNe~i>V=5Ax{dNAx3i`@oPmM+rLC)P>*oG`8gx3lx;J-q z%12qTrUz4xm#v#&tewW_PoI)W*MvO66{&$d2Ft>4yv zzpU&YV07Dhfysd!;rR~Ye@n5HN`aofj=s%X?ngLx+=nDr*-gd!I<`O){asz3mZg0? z{r&S%7wFE}vbp=y)bHq=-_uPcLq}fIvMd;-!G__;vb3kWo1rVt6sH4Bfz#WC{#Hbt zY9pPFD%Fz;Z`_6kE-QNa2b|6QXjo`p!OOO8vfHv(EF{!er4MD4#X@VK4}{14 zzFJMb8N5FSjMPj?XIKA*zRkVR9I#GI>k_2tlx(G|4VyM27Xp&hLIHZT`yxfkMBhpU z{aRJgSK7k_{i)y%1V=w-E(6LXUxA_B};1$vFyQvD`%UjU8K+hfFRTtj!B$8b;uwX<`v; z_2L}d-Zk)9wFQIaAH0_?|4A&v=P%t}y;644vECaO6TN=h#*L5z_8i=-*qJ|K`a&!# z%PL|)p(-XW?dUe6R9V-t5&jy;@BYAyEV8zaEs$5IQ7>3s_bQ_*VSG&eL@FzTm1fo^ zeuGXJ+iDm(i>1iK?%39YEC_4|rCIpq=51SK&G7{r91ls0JYDRe4J|kM~ z(+|3mH(5mle1sGwfaKWXo<-xpdKid#R%G+Po{U>77WcUsHsHFl@H$9~f;*mp?3eS7Hd~9S67zo3IDbIUkG!PTE0yF zKJ=nd8-52R;+x^W2>(;~=izG4NoHCO{`vb7$5oIfvY_mivi7p>vZ1oG)^lZlS@z${ z{#V)b@~m}Lc}w}Kk{W+*;vM{Z(0bAOYwHc`q4Is@FO-xJ`Loau%YR;eRmBYzD=R)# z@oeZ1LoGWh&~1!cKrR3G{A;hW-%E|yfRJH4-?n-;L&_dE3LPQv~y3}(n9$?CG8LSeXsCCt)u-r zl*_$jONM6;&j3&F*K8RjJj#DiwxoClcuw$M{jx2acnz`Z~hXgf%ippDAL?G)Nl~KFlGjU!hxH$s<|XrQ zX?{sjqH{`KXKUR2GtEopU;dv<>fE{d?}h&+3XPk8rg_QyTbf@| zl<4Ipua{}u{4>o<=3o9#E?jh7^YtJ2V9O1yOO`HMe#_NY@t^n4`x5`dmc*(5|DHA4 z-y60ZARPam*Y7;uPyd}QCrRs_0#EwCzZ!+Qj|54ue&_)~i=l_wm z{t0*ps|eFP*NWV^ty^xxDqFLyOFoWJS+i-I+|svM?(7)Qyv~mMHCqEp^lM0|ZZAgJ zK9fWR&7sb=K8<`Br|T_U>*cnNK56akBP5NEKD=#f8DOtqVJ9>w2|au;nzQEiZuvy-4eA(srA~JDkAp z=)^x!+~l&PXTT3$adYO%!SLT}=eu%TL{X|0g~vpSrvI_D`CP0X*5>U*>xW z$sE5;xe753i~da= zeO(KF8xJ<_@blEtOZ*2TG!a*zl*aaubj3J z)b)Dv(!KFcZcE=RzXy}A?wpQYndP&{~M6lMak zb!F&787AwafO2*^x(M-2^DQ||c$84k?9Q{-*hIMZL+oV|o*-0>Z$^r}MZ#Xf1BB-Z zj}xxFnfiqD7E_=3ZE3Y6K{#)TB`XLMOD)+%IIzr;5yJC?hX_yJYRRkgoBJnA-rzlT z#*%jkw-ZK@^5cX}gy#uY5U%^7CF=;c6ZR4wB^)Aj-?C(su#@mO;cJAa2~+=U$vMKc zgi-Xn(}XVJ-XB?_8sQuN%aQ>?=f{>jMtFkoDB+=>01x5mcPu$inEokyo@lP837ZKc z|4Mzr6k#XffuC8DA$CdNra8kw+S7>YAA38;bVlIgrkHH z5S}L7LpV?1+k|Ptal-9{uM?gke24Jua$9B~!6OyuPK0T~X2Jo&6@-rwt|JUbkX?jL zggXfj67D4&m`3}AnHjdcMtF+w3}JY-EzwHwc^Ud2;ab8~gad?lxFvbHE!zo42!{wG zSD^c8KH(9=dQ-k$T> z1bxJKSvG5Y#-_)uui0M?eJT9KvOVRGR_u;EGHrzR=4m$1$xif6U5?9Uor|J^V$BaoWuCqsCYyk)AXEt_@fj*Il;l0C|E{w{Q8ub-3Y z&d04U+g}QOF}$bj(em9Dk3>d{M)Vrx_OA8L5YT4zvd1l>AiYic3DVPqLAt_^3sq)( z0=#&in0KbofPpVjtBOKU<&8%hT=;G0Lu+v4R_m(qdORX*%i zyt}UEtst+Hyk5WVs>iKI?2(Xxqm#VX$lIiO{HavjPFgDB!{*bd!b}xeOS+*o-W7Ms z-%pcv1lWN&eWAJ2awjOacX~)*=~^y%v2yQF?hOX}UgdO5^TP1sSs}CTN^#|o*FxS2 z@=p7GO;wGgSIqJkVyxspf}BHpOv$HAe7eUk@xmx zA%RsJd~Uih3pDeoGVz@&Cc~ugwUb_59g>}1-)$FQS*HFj$~{(tZb%(?WJGzE*3T&n zq&Immthi8p9f!)=>Us2|^_R_Z0$eJ-RQ^6jp3)b26Zkq$`Y7owUb^X1%U7wqoszC~ zn@FGcK7Wi_jwh`$nY7Y26rQ!DZztW*yq%s{$XZ9`{HZB*wEZE{->xgJS53>27IFRd zdDpZfq@_t~Ca!;)cAT^U(sbVRPt#Oyc#O1Q-D%oc(!%k{<-+I%O{6`b75US0s$(1^ z?Sx73)?pKA1N9-pC&1$c*P+&3L%GcRi@ZqZaT95$NOQxyyvN!cpHKNhInt8X6=Br084>soY1_3Bf10K`Ql#1E ziGEr$X#=DgS|d&8TXm>&qy;jvl>St&+Ijs1T#nMV)=@nxNBRhH;7tVU(&2S4dC3oi z1hr4|TGl;oebL?%dNjPd?2&Siq_Dk68MlQp#4q$0mf7L>S&?0v|E)02ptxqPDhNZu`fHt&=_lb=_9IA2|Ui1r5)pR^pER*7eF65o{} zX1QnSf4*GdVtJ(wg%_D{Rp>esUOheLo+dv`o^wOYeT}%5TN#SEr~G(29CP0e%G?-p zPx|Sz?U;MUPah)vgrEKt<=+U>KNNG1`{}om{(6v}in*^6e@C8UyqCL~$mJ4?Sa7Un z$l+_dBl2D2uCios_eQb7S*;ltW89q-N2gNaPR73EW$>3s+l`W*lGbj|mb9s3$#?6o ziTv1}>DS9duJHS67-H<%%9aLsO%7yF$y3=6K8gR&Ds$t9PT9}>owaM@6@rp z@gHpS__1#jLn2-jF-pVG>xB}-hLC(0PQ zJyey1elB(6j*wr*IxBZC*Ey96T?g4e5J%aFye)_FC#~EQO6wgJ&Hh^WfP2zY;z_ZmNjj|UHbz!Uxla?ZqaPPe zTGVU5KPp^T6_)4fEtw5WQQ8j^mboh)SJ=wkU4H%eNE#jM*#^ArA5i+%F}&~3WyTiD zEVB+j?XAsvXovNBW0G-ZY^5y&M~m2TCk91u^0L2H_l;a4b$}yMfXL@qQ#)Nvx8zAT zfafc`EAElB%OJmKNw-aqw1B$ykIiVZ)_!dJ8K*DQQC3%||6ceC){EPOZ% zKhDCJ4bM)OE6;|vR=LpLWSY}?lEtx+e(~1!SIm5iESFY8FZ&+T`PaW?LkIjgbOqg5 zsphZ9jM*XA{e{mDE?Abi)Ut20mIfKimn-6~j#FE_VEGoW{NGV7sH5~7;7I2mMZ$fl zkOt@AVpkejXQhU2blu{YeR}LI)C0IJVM6;p%cXzBesFTR)U`G$&!LUT6QK zw6n+IOu@Tjdw6&30Omg|_jq=O_Oxz*uNl84zLtQmnM2|+!!C0Vk9n00GUwAd^MB}<9LSm_QWoXhHo7iEArV^~5JvW8xH~Dl;d^kk;GiDyk zaaDOdLGiceCU+Bm@?=Yq$TUaGPw?G%Fu%6T&iw`ssJoKSBLv6ZO;f zaVwX<#MGCG`u3FfnY^!}ZN-^Od>newc%{!vE=Z3hwEnl{uFu!5b8j#_q#cj(yLsgm zza}l5u-*4w3nhKeg>IAv4b8)Y6~{^sc!b`?hvB6Ye0VbdZTI=FW1sfAwPRKF|3sZ73Ydr<@z(?l1gx z2W<^&TgneLRKB!7;_)TrzrPB$@6&F;LxMR8=3~sKeZ`xu0mWNbu1JL!ze%5VO6Byt zvSl6}-4HK<&y$%Ec=20dZ!N2C9O$*t#W8mtby!0l5v$TdDMtg}5WE)H7C5 zC$6oCxxM6T`u`J(xexg9NSUYmYWfDssE(`g@1pze@#9~O#N6$EJdO^%%a3qq_ghiPYxAFrVOHb4F>Fs$<99khAUkN+|0EByFT;>-MaH}SL| zf0B60kN-CD7C-)%#GC#2-w;pw@enXH`SH6+ANS*%foG>5Zvy^9eq8O1z)ol;KIEr6 z#7F(OMcsozoH<=$FL4i`-+d9BBswlA>qeu(jDSTM`sAbi~nJGy{{9oNse80B&)h8 z`{&T%$S`)za6ED{bf{;~Y7oJrf8FpW{4rE_hpsarm>)o+C{z~b)LV0+0+`*uI zKj(Oz1G40Umb_*?ogGhCNan+q)U~-*JXvnpw^HU46%tQxvSPe9CLWep`oq!~wXIm< z+p=S(C7DI!M~+EdBrc7PYt=LrZ03J$9+Cm5>kr z?%|WxlMPd0o(gaE`G{MCS0(B&ngsGc`HVCIgTigMoawEL4AN%@Bd!x1QQSh1Uw!QgQUO9puptAaJkq;_^@3p-)RBsA++)3*h_&>4)`@-l| z^o`wVbnVyV*?cUr1bOY`zN50h$nMsWYM-XG9hF;ucg&L=HNNZ>N$2hbFP5yH#D9Uw z{c32?=*|E7h=(`r`&DRD?So2S{OX98PT$#!HnlW=k+mB&r8>GD}1;j62RrOuMb?LXd&Mnm8<-#@52;ls{f`>u|XIK*P9CPf_|gN!qJ5ONJvp z|BXVo@01;A7%3?;lJ$6&rtd6CAE)kV(hilRy$gZZ!rIEw_Ot%l zVs6S0PJ-v4JfDrXjN4kTxL&+^-uh~{-zl41=U0_KeUb9T^)k8r*QS&|Q36wO%}lN{ z3XUEFKYO9o%9d6+z#7PDJl%?H%uL|>Lih;Am6jFQ#l^D0xQgpYO4d=%(6!RD>HtdL z>MvCv)HRVEveM`!tD*6z3vbOT?{19Du`7IfiX}$ux@4Q@k3;sJE2QVKhQzbWGUy{! zhPSH@6Yzc4=lgTu`wO$_B5w>qbNNa^G`KTr1ZP*1XRvJ4(ZH&wkwJXBb+cKK1 zTf53?Ooy#lbUk*(pJO**S2(YF_QW-6PwYc~cS-*UVZ!WL82ciz3i~3CeqYDqqCYQb znhvju`@WCg1YQHvB4BEHRbp#zl_iVS20EA>X9F?b(qP$)LGB>V7-GDu?en($QNB@O zPW-@w^+xg|Z~C^+s|M!4_VH}DH;2CiE!ohKCyy?5Paq3!kWU#|^qaz4YNt)uXDih{ zOH9~j2G1+N?TVGyX|)%z)54SNw6nnUPR-I`&t|G651Wa)-=5~-cZZMP<2B2$buxzL zz6woFLv9{tUFiBa1#j}{%>GMX=hyPBetDVOI*i;m;|>$A^yAu&+AjDt4eb{FIITm! zD>s`ol}{a#{67k3=fK(d3*c-(B z3N0vHZ!@k&r%vkChK3F*;o);6V_^4uV*S|09rh}p@BPT1Z>R$Y_I-Un7`2D5GJS!Dle7*6P27bD>SLQUU42~MP*3lP>?z5rop)Edb zAFuhit8{z`bgX_C^<}7?6Zl_BeHkKe4u8t;Z>k?A6Y!(xKgb(ppK_Pre|gRNdUm{R znl!F`ITTN?uZ-34DMs|EHpKVyN*|P6Zc!;n40z7B4fya`_4ULQ;mWcZ>k^e530v}N` z`)&FKr5L^;+q%T(3nHh76^DWBQ@mXwt%kOS#yuIVX@6O8jI8{f-JWi)^~$I4C@=5S z7$;xG!i$gL)Ai{03qF08(vkX7LXt6dw90FxU!{qmi=^wGg2O(RmeKeS{&a8ODe5+u z`r(Oiw%_g<&T~|8o+HYBaXgb@?8W_^V6VnvUq@xP?(L{wh_YGgApgJFpl3Q1F6X=7 zYnvt)3;P-p#PZ9m#^lwg7$f-w2quBEHv|Q zDRx?*>y%4t)BpDIss1AMC;hVEx|f)<6gm5!3u(g(eu*8pSZbRV<4>5rw^lOKI5&|y z=G%;EyVclx?RG5ksLcp-uQ0Ec{6qa7AD$oa9^Z9}yx9il;l+!F@S)6A*u3@G+80)z z{o*C=jJZqPSamBo>Cq_he=96CFnri{%vcV}zJ_HpYw=U>&!-93YADTa+N#B;y+41D zv;?90S~uZ~J%k^>mr!x_sc_Qt@7%D+Jvw(^!vQ~i0RQbgKYkZ}-CcfM&%UT{dHzN# z)zckoet`HE+UeLFYu--$sJpKr@C|RLtoneppGw+mBku8~q7VN2R?MyT%UqAYIpxP! zQ&)ZJb>P>)iN8AVW8XwufiHTD`hl#P4c>BkolZ}Q{crMydgogB`ukt4wV ze7+O<9g=7AnQ50v?Cdc+Dlg>^z_Z2H{UvX&iM?_mWF7*4a?f2H=mjgf3NLn_rCX_~1e^5U`bS`_vysPi+ zG2fQP#tMPo?7F%8^58tXmJqzS1B7LSTL@29SnejmHFIUVh4r+@W} z9(51+Yi7yFaX_&e;8n}Fjbzg+xKn$haHkF@V&#lB?yX}jEb z(EU>R5arjQ=Zh@mWLdvyw@kWBI1{^^2IQGXJw8_ny`k$u;b-*+$k+2L z@o0w9USC>I#M;d{71M^ovKO41wF#_&Use5nK^i)>DZ8vBZJVFgQj+$dpO!31`@Eml zRFd`uKTU1SppVb`I?}VRD6bi}@7g1gEpzwh4E(G4S4js0dy@_FW+KpAuaJK+ZARsXCTs#;wc!i0DLG9l%!NjMUqrgLH%s0&?F4D@ zWINvuWU=UM$e(d;=at=+Ew9+xPGxnSHM!hdrjMEO&y)JnG7rkWMA)CpSma?^tIO$` z6u!(d^4HjXmi(*hr8~K6B4y;ivinMK7K^q@wX`<9;PdH^88}7S=I%$Xv?ea~+tl^f z#(Gfv!2gU5pP9U-CgvlUyGBmI==`mrp3cjvS^ME>2RQdsTuaybGGMdcuj+T|d%Ms) z?<%2*P0+-)eV)ueBl{k`%ZGE*+(TK;QIsn_H?ek+0IcWdv%PYd^kXKyP`F&vpee_0 zgZ6CrL(*6Y0%F6GL z&hV-e;A(lg6C`9Wk&3$fxcD@XsdU!~rb4Y0=Ecdzb z>T&yDIa?KXP65Mg_)f;?LkDNJ)hE=q36J;aYTO?2*I)+!RiUt_{#+q*d4{v19$g&H z2cha4s#|ULb*s9)bI`A<@awIa(67{{IQxSne1~%k>=|>d;c5OFEfl^} zfA$Aac-^sj=A!8w_Mz(EpY?HgyvD-!>=>Vd!l^tsz@e^9j}`@A=rj1*?n00BY|2=) z&t^@UdUkS6Z**>1eS_+bv{5LmuKzggQ)lc3AAV1N(zBPIUbHr(@K&W1)`$=5s0WLg z4~2_#-&ZNnrrHdB_D|?PIyYLs*N49p#;Nds-oUVX7@ysH>Zf4|gTpdlYvl@^WB5$2 z&^i?Oa>t2>#Wiq^7nYBnFQi9bA!3fGoB^7Mmc8ZIH!A z$YLvGu^G%6!&8ZGHXQn&-)vard@H}gc_F{dc|M39yUe%C zqM|aS++B-(spll(k)_x>9=++=I?4h|xx2v6pW*8-nvSm1P=n5=dheBA^7y4`AKOs# z%o!{4z-1PkX2ES19B09G7My3neU>@MG8blU+MI7T1a{(5cp77ULfhMOQw*HBLs7p! z^$V+As5YV6hCv-ItNb*wxHh-uH>e-nHK<0fuad*QjXEyzT{*Er#dBf7KAD~^R^2tu z8Ngut!8t3>W<88<@eKUggHi7yypH|rV*fhW5%c6(=CjzB!W~|G9ph`C3*Y0cXa=6A zKAAiW;aJkO!eLn87tL=D{bA`w+#^J;8Y?eN=^lk8!m;}qA ze2BBY)Pwh0Ze*^7U(+5|J^(K>d?q9z_X_cB$k~!J>r~4=PM=}Q|GVDXK<;4hu}Up_KdkagnkBZ%z)UnxA{xn=Mg%N5gqXiVohI2+^nqw1Sg?&!Im;5<(i z@-Ir-64J(#mSk=uOov5QhB>d3uFPP5L|RE{-9 zm+=0S?1+aYQ%OGlb?;2inMr=_<27M?#nxES-({uj3Ku_c`EbCi6(6dT1+-Z7bJ)mc zD|M;VHH(CI|E%z^+OSOlp9@F!i;K^ZbFl`u|ATfcN03GjlG+a z=khJkY%?^Qgl6G^`Gg!o4;sV=;n4{1S<i*O4ssveeJgj-k61(4C$^R$80& zUgu7eX{E(f9e&CHx2y&C(l1LyX$l{6G`=ehy?Hctyk_=TbqT%b-j(We)xa?x;o8H# zOR+!fRP5lOoq6U`NiVsjS?TC)=kT#e`ju_&}O|5DC}d1dRO`#G=mw{~50IeF{h^{sNAvg+eA zW8-|9j!Wl3&;39jZkzF+mAMCNE=BKc_jQ`t*vig?eO=+}wmP4dEQdch_Fw&hw_m39 zI7@)szSCCUR7Muor)741dS+Su?<@W(TikCbo|V^e2j@%=ai(3*0w`Y}E+o;{OH#mW7U)~;!_A;Y>dHvFgm;8s|rV{?riT3{l)xeJZCwBCir z9JHmOj#+u@iJHNQy=O1~*TcnectJC~APFyMf)^y<1une6VeaQK_tiT0BaRh6C>D3U zkeTBW&*iu`ml24Hz>44>n+;ux#ntf#u@oyf?v znFsY7IYtk4hZ`dgNx(a~F9)r}#$u5$G!{2}Q+e=D{kX)fY>@z%2&#mit-!)FocQ!hGjI-4nXSK^X zGyXX1fH?%rPHwUu;K8A~$RXx*6?3{mUdk_%7v7W4s}AJp2XoxlYj%&mR`W3D8Zx6l z`4iO_!`QSTY+4&WY7M{Nmruy?;YGwBEG$}dsAe{Hkot7vo?o0bi>_*DK+~^>S18Yj zliuy0UEJd9m?zOi)Gu=qp7|=Wwo%5-`no1^3qG1aNA=`@w$&1DP+Cvjgm1U9MbEuj zsWR?D1ac(t6>p85sF`DUzViN)$N()jS|V?}eL1m%^5h$RM&*Q-ZGlcTH2oyKe%4%| zpYfX6tmC3CGY`6`hAvb-1$vFjBKs2DW3#Jd$<>$1cuN&}4DW~4zkHl?x!;%g(N-Oc zfuU4RwJ=Whwt!=&TN&9cCu=IeS2(q;e5V^r{KRfdekYV!a+%bvxXOCv#LAwpR?+WJiChUDfY3x!Si*ARX@RA+eeK2dYgJ`f0W9v07ojL zEp)(2?(M0ZR{Xq*AD*<-i)Bkpy2iDBOZe6z{``5`kIbFaE^F%II>ifFXqU5&9!if8=n(+VH-t-N1Kv!cS)9hBDE8%W*zLpyv|e&S~Zm zp0rYT_!_LtYR+7&z151X1&_`y_7s08$~U9I*$UFkKIS#lkL)wLXQOkgReG)*eK#Lx zjVMn@>Rv;}(~}NWEgI7FFQ}a6obxmLbSE}pss zpBfTjvYfwoKll@eX2_1AYtYXwfv?0*WndILS*s5>NTl5Tov7E|E0@?(&)%Azudl4W z6+5}z$dIT!l%F9P;{%RGc7zgAwiH{Fx6-|$ zz352%%1j@r?Kgn_N9>CRWVTRd1x9nK-_a7GVczg1@R zo`jXsyDSmt_59!x^zJ8+rAtROPs^SgOj5^Ay))Q0ioH|6fcY;Jy4e3YU|i!lU$w~}ufn!= zy6UnXztDZ!YH%3G-=_By)i1JcDs5oPRYq<_9>>DkAAh;4q`bad<{3HIz@16k(Yk|= zIuc6;`o@z!%}&tR1=@+oV+}tpeC`TuRB@wo22XWusm(F(S>~VflAPW1`Qpio=o~sO zfeyWJq5C6n(>Ri};R$oJZIK&>^*an23u8ZUpT+)#Z!capoUO z1IwV%2r~Hm;7;^P-Tw%Wkv3-dhCio5I}!GPqqK2$umj#>jd6x@> zmb7GADkQc`4b5lp1E%N1GK119m)7g|0JyIsvD~0Y=CbB0cm!wruz{~<-Ui3?JtDUZ zhh_P&u7#M>VT-ID{%Ik7c^H3GK$F#m=hd>;Fneq=4-RatBk0J_vCh2lB^h7fH>rXT zf5_+SGvpzBVmslRWvSP0J_ygLaP_RR82Y^i`kC$ybXuu9e$dpcYcam59XQ8+KP-Qa z0;OwY-|yf42NJ1k>A(k)HlK@z&kuLmutpR;x-hAmPk+~T^`uh4E8?LNtzJ~3JcXLDIcF~?qp&$E6?G3;{1J8RJ!2xdRKsZ z&XEduD#QbBbsb0LAU6Hi^L;*DtIaUMmpCK$C~#Lgx0>-Lq5rp8Yl`n<<{K>Pn`t#` zha`-zA`KULgeTt>UuOnu881E^WR||49;_SEvuh$7!P~0_zhyFRcry&i=TE)B9tGwn z-Jce6$gmAAUKx`Y!DoGX&>dD<{vGs4Sa*~Tw3Sc)_X}de3d(HroZ40o9cz$86$_a{2BWu z_>Uknpd{$=bM+=qc_inyl@C`be`IgrpA1g;e!9mm?RX?DuNygFyFd2xw2Z?1Ubyk( z0IP`kjSgNngx&=F__d=s=D7<$K+eFccVIk!w`2bl-04}A;M{$fa_aLwXXd-#@S5hw zB%D+4@ zx6v+u8T!%i5~WY-#n=lISvVSIFWVoB3Ev*PZuAmEf3tx zK2G?8#t`3BGWZG08uupOuNL4RzG>Fp`NH6EEV>A6v~o`8I=}p2J^Ql5KVeSdIY;*T zYcs>+{X5NAG;w&-BR8Y=*v@i&{SPWb)AT>|D7+?Oz1)9 z*^e&OgI;7!C~aJbPXuFO{CO=G&t&8!zx=|dyn6p&)|A*^@_Fn&U;jJpGC$I^BT-eXO+}5T9Fl#@I#|!JFgR15PG9(~*sJsTw)(B08TTXpIj<_>GksmTTDarLqkD)CMD?2( zdajD^b?8~(mP|<7R6VQw554tN9=6;@*=OiUVJQz|$6f>;h2<^Q$}RH9r1C1~F9g4q z|7b~BwRzsF?J~)Pb^i36smkP_O@&$01DM0IkLx=-K3I85?>zl#`~~$K(K~yp%a%>F zT`EHr-e8aCEp)eeGB`2LE2hA%eD{?r)b_09j>1v{TV=FCe%&#+*f({^LkHW+=03W@ zSjnBksbdZKI0@`$&3_BsAsACp2lZ{tM27E(KAjiKT70X%9$*&rZx6f468LhK}P56@y-b&Y;v6;>EwxMTv zdbHjtQaxJvfSwZ!)(IeQT1 ze3QdnxOrx5PobwjnGN)&_KWCDic6jI(tXbWmH_rQecCJKTS1=st)MA=8s4E?lz3@5 z2@f`X**iB1@)MmI}=VWh2j6Z)Q2VZ!y;d|Id>VHug@(uiEEj!$aJMg+sA@cE| z+)XjglE=d8i+RP|%|{}Vci^U*!C{pf_>DWmPvqOf`*{vDG>4yWxbZo7MPl4me*9pW z=U-h>#kVHH&zU)ia~E3I(l?JPZyAS2cU)V^nIVN`-HzA@p0*@%8JPbjbgS7z6H&bg zKmN5U8}7visrs;<2UH*F=CYk`YR3jQ$+&b+-aB9MB)m-RtNnZ%U{K~k3kuI|`23pX z27>ens}pV!j>O82K1J)H|gR zn(a>C)k?gXJ=|74;~YW(W&T4V60yt5i&_kPWjOTik`wm1dAH)_HY-;>k1KnDuv_!dwAyS5R)U%CbZ zo&tU1PtUPmoWbTU@;>EzE0B}Q3$%?=UJ$^bJX84U>Qq~7<*(o?zbPCa{*`d(+29Mq za{F%pOQ3J5o=^)7tFCaNb20Hq5oayPs}!=Tc;@Np{2Ji(^a{twt&8LD;&9*f8^9gR zhw5XheeXifCww`tw3b1Kb0dSHI)4rQtN`yTkd_z> z#nB!9BmG5v8LsVX+kqcrs-BaP!RvxN{gzhd0y7Z-GZ~z#oYFSnKiH15&AH*Aoi7`nDx$W; z-t2utw*rHH2S@b>wvJ(% z|BaJhJnr$W8Im(F{LFu6ZSkH#PwB7!0crZ(lbijzwz^mN3*IaIwiOPr<3^_B2k-^q zZhwA)cYL7b+8*ByH9kJ(lJmre`BuO)S>`y)TxXf{tbY3d8Zi4rhe~Lu+#jRPw~ntJ zxwOoOLH%7-g9nO!Rled`pIm0?#*_L?%}D6e9rjVbHv}swvWEf%w~s-$d!f-i&?q)u11=q7!)sloDTR0cRGR@QiOy#Z`&TS&I9;;Jy>wx5Fbir=DL6{?&hU zBR&fCaR@$ZY>R6#%GjsN;UkUJ7p@K&-+VY)E9VOvnqQ8nkF*Yg52ZTM@nY2-*ueVN zIUaX*Xn5*;A!F92rh7D_dnqxzN1i>QX#>7qse6K2ThH0+KI}YXn96mPWqMXo^{aP} z-`g)3*ioC?DLb+fReszmfxQy+t7rJO@2FG%k-X*8|Bp>L$JKf%bkQ`pNTCZxxGNE6 zPf~qWQSj}^i|9+4P-QYIamPXTOrs-Lxa<9SoUk{cOXVd>o1E)3JjxaB7_jf)5biYl z4`F<2#kK>Q1^?`i9nLZzS?0y8<4w@*aptL+c}l|1n}CnKM)c-G`BQ~K)>y15@Hb_6 zA1uV0uJCcFb6E}^YmkGwH&n&AytU=|;M#Xw=j!^3^`#ioj)sL#_<}B@$`nn$hoa{dw&H^{Sj%O$NI%dE>BZh}&MwOR(Yr%Gp|2yNg z7_Y?{NQ*O&mTve{37pWi0AXtGJHqqay{$Z=huN=cB`E=*PqLN9S`LmCtue`i-geO!^5`E z+gs!z_^&4ex$R6|2RLoTKJ@N_v=~|4I{J0Lemiy5SE_AksBOoica)qJ2=f;ZYam@_o$U@KZ%GXl9RkW8Q%8!XG z8TqU~w$H#5bgl4xU^AcO?Jr1ca)x}6@pAuz`$#7X<7R$7=hvAgchI(u*V-LrO$W5Y z-4D~JNP55GAmr2CqYYzkxkidjG-fm$61gp3JB& zulI;Il)0|!?@|2SexDbNq?w-Od?UcIpXWQh?>aM4qEjd@tHIeQuzT>QvKM*ctx>H% z)gCfwPtXrOZ0GgUTw zKRO3}v{R1{PVd9%Tq+K>Q%1szK2SMQ%if%q)PX z+Wc8R&KZ~H4*mWpGUj417G*|}f12~C7nqL^`tzatYJ0> ze@fpv1|28o)eS!3{lFRH=ZkbsLVP2%gF5zDP*%r=uf=Qg0&}GGJE;3U|DK7?kDkK` zeD@Rjj( z`vjpePA`q6IODKatokP6;G6H=`TeCi3pEgLMphfy|DCD&FZ!vT6T&tz@KwTt+GqnE zGd1$17&?@l+VZ6(grO89Ip&hL;)@;Vd|sKz7h~*eduLF*b3ie*b4**RU#ubz1I+M+ zi}B$=H!syM{_6$wiwo0n&O6@?op|@5lwazeOIm)c>xgek)^ipVeE}X?d(kyg+*5cD z8GxRs=P7g_H$IY)D&@hEW$@F5Oq^rr#YJz?HKO{JcaF%h&zSiW0k6ZZk#(1HU*6+y zv4gq~U&UN}GIxplrfKI=_e!77>V6Ee2VCpgw}qd^iKf}Z5BnH-CU!Hw6&e|+wY}QyT`kD9Ou^xZXGc&&u zcY9Caw?c-3zj!W#9DQ>LeIsm-e7o0^`R#W6U>bho!LncHjeUQ3{+xI&(EHkvE4z*C zs5!vCQD*kL%4^wkh22JOTq%#(?^upKCnHnNUk18cAS;UFIEO6}#(ofV<-)hD6gFM+ zD$W79iB@Ct=dv4yTIC7mnLGBJsk#KN!I{c&e8+~~kxBY~>KEJx)*Q|623CvmTmwkO zGY^=wr`b;o>IOVX^_0@OTd1dPxYg@S9lk-~^^x@6MIIWjk@a3$^*ALTqTlw3em~*Y z*L9F#J*W?Fc&a_-ul+X)3r#!9H%rUi?6;w?md4F^uwB5Fu@NHBfR<65C_az_gb!ic zdhiH5$YsAn%LMmv2I))pJ1+*))2xTGiF&YH(zNh*T)z*6eUy91K%ntlECH};qAI(#{Ps^3&Ux+WE8>P}WFN97! z{Z?TM^xJVm6P|t>hgW!KrXBXIJ-x}R>%xZzxsz-|E45}`^-P+c5uWbrU|u=(7c2#y zx>4ob$fg|n7waVI{ttit{u$h;ZWt#0mH!(r0kEkamA;v??h|-$eViB*uZ?Q@TQxjJZJdWuNoXx9sOSl$4nmSLvQ)} zK#Kb-X_GngZS~?EOqFXq4Z0T>C0F+Ep zoXRgs`kv?CTdHCYuQ9Si9KUW8HYaxHn94={9%F>NOMDf67#-N)Sm%v1Bis#YgWs(m z*#pjy%eg1sW1O6GoTvCe=RnS(uSM4LR%^cDhpZgaHk96t{s)mT_AEHaCh)72p6v|i zSI4URa(dTX?cvgWpmAhC3V%`ywjg`0;B2yA$&+Il_GAaybI?8d9Yb~4Z@MQw4?J?G zHXqB?!_4PEpU*bTz|Ko?M=SXD^`iH{=@qiW$gcNI@PUqD{VsDQ=iRjr&hxr@4k_kz zg1d)@^f!_SN9%Iz`Hn2be$(%MKVfsnll-N?)j@vi=%0We-9v9;9`GYz=j)u*BIj$N zi`dwYG#|Wy%VEc?kJwnLPe9iZ{%T-jf1bU8I?h$R+jn&&3(m6O&d7u`GNGu`9|kwN ze>HB-BnG;r%UWSAj<6<;zN&PpxQe7bowW*`^*!`ekH?^6wlN0P!PRCup!?zYBIz&7 z4jpvh!J|H!70j9HfS$dreeW1jT{-ZDvc6dlgQHbEN%B1W4337y^Th=|I0t)1d5FF% zp9$7(AcsIYe{K3xmS#S0_w}_Fi4LDHh|ytGmhAbQJEVD9T6yxxnm1|7qrTKR62$K$b|_=ZG?^`QJp&%j$_QFI0T;<#s7Q|-g`i4JWH4@z$Ol0NxNQBYWROLKK6a-$5Q(g`t$gIl(=V`Ugl1tj<$=AfQ+@q zn&dL{gMAHZ!#x5~vfaDpqf|7Kw`IPwS|tnlogQaY!7V3Crx zb!~{5*UFJrAhCwy(S?Ne7<`cL#LtHBwO?MjtJN<0og5pSX2ES19B09G7My3neU>>e zb0YZ6kDlk-1tGp&5PB(}3cZkT2|b^04n3DohK}T$LeJ(CA>?!jIUPEb&vDNE0D3|E zApG%cPEPQhm4vmw!R9xMW=NeoB2VOZK^Nz-0ow?b55Gz{%X*^W6lcV0WSP5$I#&?3 z6HXWJ_l58~xa#|Vk^0k!j}zxxWA0wU!h@cDbDX&Px;~$XH6JB@VuqDEaU#}y#7{qZ zS^K{rr7pF?Wj}|4E4RQ-1sr(mVb5t}DFX z2s&}XO8wP|`OSaTYNfjV%KIkcS3hK>`Y~Y3SCH?={4u;~~EPgbv%m`Kjw5C1yr` zmo=~iy|QEE=Y{K{n*Wkd=Ng(iXWRK*oI9jGqcF^|r};2c`hHfupRV*5_>4T+mbN{+ zc-&?j*}q0N*Z%m99X$2bhfeZ6k4o-TU{~|oB}T7(lrL@oO9j6#bufDkcPfs^AHh3D zvorZ-VWCh7ZNK_Z$kex>r+xWXL$@FFc*w~DcaNz*-_+N0OA)>=vdOBPfUN^uY@nY`$~}xPDB}K1 z&x=<(@^;>9Bh9U+@dO%cWS_*+F5h7OAouuPnVw#5S!P~~aGqfdI=6Mc&F~Aq4e*%# z&lMg`%>ZVtC;Z+hIx*oB$ezRynBuIoRi-#!?Zdx@Fb-X4o~G-&jv>Q%bN+ZMB}^Oc zsHTSuJgS#@?bM|y?o@ve+IduY-nHn-+_+|*XEE+q9=c36`}BDXTB!pE9ZiisZunjw zw8FP?1(lV*<_TPf!1WX0TJ^|lhjKow{7$F+go$55+uy_gv%$1y$t7;gv^fhn!{EQu z!Y^iENHdOcM2F5>tXIEp4Q}y2Oz&+4w|@!^7vYLK;WCj&#A~69>RAz!iwJ$jN>8j6hC-M#v?X3<{O0Q_t-Q;=iuvLY3O^ve;zsrRkWHq z%D=SUU;n#TPxFJek`2kfV8?EbrNPZ`$w=ZA;??MJ)plqZa^!E|EiT_Bzl3`5{ub7cRmolC z=_~G&J0-A>5E@p=}0QCe$n>UlO?~EWd98N@5A!vf5cCcgP;FWi17>UG1CuryrJJ#gZr4d zXSbC2V^-`kub+L$>HS&N|L280m|h3puXC>E9D33lk^@gKWEXrxbs44MzkR%NkZ*U zVb*qZj8n@!;FpV|C#gQE=R>}9LS~$Oup-qsVoPcI=lyhr%SASNycCXOd3Ok!UPT{N zJ>xdn|9;^v%INn)PZ=Mzwt1+vBK3#t?Zh*V74)g+IHqT= zm*dP^ig|2?obAz zWZa#j9&Nl+Sjdz`Q~FKqIOo{aURIncK9lK+l=|TH+&AAWx;&m~=ia`@Bj%%T{+3-^ zrSoIxG0|#sg)L>~h2I?7{uhcv;e0qWptL~ShAwDd=SX4DdDZbKEcGLNGe(XrQhuqp z{OZ5yTAaiBu;2}E+HIE=dBZti@YX`}sPclF{#YWc;YZIi=5ydUjxR{hvdt1Fs^6B? zcm?}yjm|5qvsg1e{QWP(`_ot3$Z}gM6FVf%zFeWOuwL(Mz8qDaq&z2P*4Hxn11`gN z&KSN^=~mGvdYcsrjT@e#JV4i+!W`eFIE-V{qg4dZ1Vzpslt!`@3R z=Z!qR5bw>|v60otzmM4}=k#v-=gZRNA7x&2Js0WYDSodh?a@d2l>MPl+V~Uno}S_j zIX~PI^7;+>^nSbh4tr6^>nqSD(ZLK)Qn=!Yv;_3X?=sr|Vql2Yf1Yu&2%xEB^YIC= zFM7=K@zG3}-;NH6{D5B zFt@ZG8);00Ed9ov&W$|{wOi)~&R&_^KQT8yDU4Uyvuz3HW1$a!Fc(4l!F%Z(sytCy zqW!C!P?+L};Fx-zcGtKivaI;q+`W$dG<-w%>9x!`;KpvqR_n2!^KmHtb#{@mm%sc2Zze1_d=Wcu5oQg)3vNUZ5s4ugEPL5 z|Npl4F5q!p)uI1BMGnzB<*ixLatt1Ln@kn-p0--o00r%g0Me-{S zBt#@m$o=X|M)D(q3QIAe@ojVemHdbe*peOR;ob(2A9(;NHMh{xfAjU)5Fowf-nujp z119tTt$ogs&eoAMcFn7l`95jRoV6coueJ8tYp=cb-i$*6r{%*krgd4%HTc0kgPwdA z9v_|WSh6o`$6zI61oOb2WLFb?lYKL^Q~IFI-J#$cupNS40?u#eA{wZ*IkKSf~ zF?Oie@5S~B^ymoO8zV$_e~&%pBA?fD_AeQ!m-9^N$BoP*uHatHD~L&{JbZ++I2N%l z@phYaO)Y0KN0pdc{Eu5>Ri`+2?X|ZxGixftc5>8fiz?Y4URi~ulV8cXJ|%WCQ|*mK z*`eXPQqOX(RBhEY%68t@o^oRJvt*TxUp z=s`&jeob3{im(1`;TAUaus`L%b&PAz4t}*TbS1pEGg?mQ$)pXp(NzQ72RT%>Rvo>5 z8S|$M`gyINme|4cdiXp`rBnj$O8Yor2YK+)rQ(;+m(q2Uu!SofiMx5q4*cNEYkSJF z9rOW<{VE-5Zi54LS+~yh(N61=Wi8m4+~L8y{VsQ_+L@a67-GsjW0uUHgcnNBE6P~o z=u1>)Nvdub_Ha)(e`UEJrj%u9ytdq?uFRzhowvyIbn z?wdnyv6-zqk%^kbQSANZy4osE7~m{I?m7bYZhN?V<^*qYKcAd|GtZX#`M6Eoogn9= zSE~eXj-oG?qPOL2n;~v^N>))0Ivsxpvo|MEfqp!r=Ooj>i_;ODY4AnPA84JC?CMbN zzh30VPD`KK*;BbLMW5=&?z&3lTPQ0xj|%NiUaoFYZ&;3<;AEX!S$mDCSo#*VP18N# zugJ!LuQTBbbAo>MULgU!)6pYO4Wfsz@kYj$Kp$E3cVg^&NA$*yo=$9Ju?b@bJRL0Y zRiYatt?0VT{TTbK`Ybln;9&Z|kR|Wwvy9{Lmi`=^$(4iNLzsFp*Am_aEfzUlw)huU! z5|g%UL6-f!#J%m1e)(_2GizOnJ{8+P(9hv#HpRn_BynM$9OsrM8l^u}s3bm(OPM$B z)q63e{<8(^QAt~(Wlgri8Y>HQrcMI^wamIshUh1@5&o18n~ASSw0E8 zvY~@gBu2R(`S}cfFlWXL{6N^w=%!@vRmg5|zO&di>4sCPCU4m(&L5r){lRpToM0zJt*%NAQ&Y@asE(?{e%mKd$Dc_go#KO|#+pp!med2ME$sAq=ga^xJJUXSbt z{L|lwHHKwN_UcKyW9%O|Tj(1Q-G)Bxh91ZdXEc0iW4*MKvjAs^Z}88N#h7ZrwmYfq zW%hn~c9_^^zV4TI!}gkQvxn_7vCZqzU!2FH``&OYlhdYp6?gs7TReG4dGa9orE0B8 zajr|DuoFMPBXwDI9C_H)$9Rc6oJ`1=^D*BBy~sMaa|Qhl|IRfse{t0Eg|sD8i*68J zR3psng zYIvXQeNA|@H(O*({Kk%8Ow_ipPa8{pqvmY)Gkr1CO9M21*nW3I%0>1u3&-P|FZR$1Ss zn}T0h@ZtVe&R0!ifbN9nj$H|l(tG*dyO~z+u*b3`#ctK}joe9lv~9>4RrLMcJ%Mc~ zx~nF4igpj7L%exZFjpC!cZwx;r87KNN@WiY=My?fKJik?lJONyM2`>Glvg&H=$(`tB@dGe-sA|KG zXw^EMKF0SHbnQ-UtEIbInOE$B{*TJoQ71mD`SGI`{wgK5ocKhrv7n*&WkzgvX=|9q zf^T3~i%leBz?))yqt`9!Yek1koy)MF1A8%b;3L%aRa?$It~_wWs)@hfnjgPbeb#SN zvg&>OzE7nSIo3Q{Rb_7z`fj={^?sBzGLK*`XT8EaMdrFP2NqmgXuHhMM)+@SbI`59 z+`!{SFyB?;bMX1EeE#q8WQVe(T#<=1_L`ipEPJ6xcE#_6zr)piY5if7k|uGoQH z(-g96O0XX|nY;=Q6S7jvm=M@CtP!Sb6N{id_Dka3c6wdTuFXGXmx8pM1$(xz5__Zn zlRwrr=TqrB(F0ehBiax3Q*1urC-VUGUAHygIcl?3rsmSm3WZX)GSk5ryFZuJJ`*vE>}-t?+j_(E;twPr~lBmZG!rw>^gMR)B(>%Nw?;}$9yNh zH_Ck5r=9lCN)y_6y`?9I3vg%PA7eH zblUVoTd^fiT_yg+`SCjSx<9t+I)~b<+BjZH>`qHnbAE?K{nn)-RWCzJxuZmQ@DF|- zboBhn1D5)K{I*s3KJa5xFX6n4Bb*J4Zgm`sec_hK*evx~a~y1taqv#=G~LNKxCy=; zqrSb2h2LQ;e4!8BsD4mT(}-(SO#%I+Jp8Aemu6wDY1>usOF1b^`WEL4GX8CHZPk%E z%NSq4rbU0wuZ4GVKX)Z^A>-g%g%2@SuI|Ie8B85WTH*HX^xKzA9JS?rJ-Vg_T{s__ zJ>kRZ@DID#l;Y3KVH>5p@>V^%B-#6ZeAOemP5fwb-@5Lra<`bjP8IZV&tGZa*97gNV~FrXc>W20Z+?b4YvVs;{9??>b2m107GIXwC+QZ4`bxH5rdqXK@+17v zL)so$=kviGu}7RO*dwdq!!BfuGhfx6@ZlJI*a;u<@By2Ib)RW!rnYhAOcJ60boxh@ zwSmw2{28L(RFX#Cah5YQ-h^#>oVld*Bc-18)BTA4Oni*adx^Dk=VRn!9C}oAYcNL| zg7zual05w&hJD}UjJ~>wt zpA2-%Q||HMJq(4yqY=6U_*VKh5cbhka$jv_CQ1LvSsL%dj!%P=wzPMI4wu@S!1+zq z510?k`l^L<|6!d%2 zv;FdtU+KDI%;9U}U*oK7Cy{D+lX5=Ov!`$>CbWIPZ>ziuKTlWR6?@bY9X1_%NbEkX zGx84h*@$hxI`WJ({%^5;9b{8*mp<|+<;r{EIdW&o-z0S-W#)95-1jzYPYTZBBUDbN z0Ue_p>TD6aC*)TbyD@AdgnY_<^300ozGIipP~vBfaUNm_zAN+`ey^WL=X(Ww#rC>P z4QYNKr)(Fx39WYV9OtalKECm%vG&Z`4u0b+1HUo!FO&5N=4YI}StI+U-qhz=F0460 zxfXi_q-^nFN_k4%zOPV_v+MGLJFYhgg#yVorL9J?S9^rH5D) z>OWc1QbN2wjARdnu!(%kfF0j7t(e+(md7GrXpEHj6(Phq$e^)K=`@h&i%1Lt0ie=41 z?}>b#WH0w@_HtLTmwN_#xf6OXcV4A96IWS_a2;?bVs?=&d)X4K+f4EIyUITAK#vBt z7rK#!tiUF~oWa+ptmJ}`dm_xao|HE-UspDD_kcqWxb%Qik3$Tb$cx|^_hU_iIM+Ay zT;9C<%1bMIHuC5a+szXv6!Y6ojB;O4R#1kVb@F;o8+=W62Yp<{`|GCqd6uaDY+fbn znIrUdPAbJ-N!CN(z8eoJvDVz9y!)b6;?5z)MR1h&QqR$fQsy$rx;Z|L)k%vu#D0iv z*I%ZpwJyfz&N^eivM;h2FFanT+Wrmsc{?`bRdf9kwUfG6)t9J^eYWcC8^&XDsX86P zL*lIieY}G49=m6s;34r>`FZ^tmZ%++(_9}e!>%kqU$^gJZ>?N(}ai7n1Jh zh#w>_X0Dula|b?%Mzv#5{LZXj54Q3fYjj6?R4giW6GwKC7}TeFzzG}O8(W5qnC~&p z2JP(w-#mWR&BV@kbf>nb)otoAba5wlp0GDGZYu_Nwfi z@?ta7^ui0A0H}L)!GM;Zc!G$sfWc5J{3<%fAIF8 z;lFbKrEsU5jZfT;d^(4?;~d*zNS;srG_+rBPhqxSe-`;4imCjx#-AY5k~WWRCH}4P z_H31^M%`vLDqS*hf_2?ne4Mv{^HGmBdM$D@IQMDVBtkg9R-kS^|D^l1*A4}bygg?l zx=Gqo>E}aEbo{&I3-pb^J$k0K4Z<<%9ribQRvi=8!CXGi2__I15m`w2vT#y<56i+IhUj`z=y|4p*D^8An$`%8mYuw6V%0?2MGQcu zNsYztJ?uS}eZ#~x4$~n>7mNi#8$U+-L?*&IZDhaF8232k`_xy-Bf5fkaktU;4?F?g zm1m!7KCybdH5mVd1~S*jX4d%V{0{4P_z>&-Jq9X&apO<1QDyH?%I|NIk2V~YKEn66 zf<24r%&l5pJ=&&@vZqa#o3833N9tSQy_Up_Y-NmSNT4^<)G7Y`PwTU1xl+e3Uwk*= z^4+T2bor&e|2e?-n}zr4wpAhP(Yu^HJu7=|rCd)ws_T{72=7~c{Bl4>_JaO{=C|G- z>`IXLJoi!M1$#;wm@hFVG3OsS zhwlf-hR`~^w`V)$d*?Mh!+A|lb6(S-!P^)Mm#c&9v5>J6T#%$NJ6}=YY z$4O{%s{2oW$8IcqshRb(CbbHi;1cFT4*ON){zsWt{-LkWS-0KlfKRHg8vLIgl(oH> zTDjE)KRFXO#X6qEXR#K&H6!n~LnqZ%mH{7ZnFRuuyMHCVUGhs>iE~eGZzJyD(BSW> zobHQ9r*aq4-X=B>^=ThO4w&43cx>v$iutY{r=blrm6(3-Y{wg?dHXiR*YBNCsO&xX zre^#7NA6DaWF}c(5&ty)`~JaIhZLTTL)eoSyVwcCWo=E2T^8rhXgM1zt;l^)Zwkxl zUg{PHn_m>wt!0J9g@Mv*^r+I+NWMSd!_!oXC|84}0Jx zccwhm18<4ZdAg@n$sN>QpOCYlK2BR6g>Nq-3-#DbS?EjsVAeEap?#1CF9@+kU3pA*0u*b-kS$vMoWKAq+KD>?hP?J|^#Blmu$U&+5fBn6fRrHe~_@# zd5;|WcM$pYPV(mnf-hZL1^ABsJ%cjQ)B`g2f))c(2mHn+vys#2p1qO%ZQ=V|;2pG+ zIn_Uw%KgUN-%y%%?|EAKYR!7{!wQADJUTp83b0UR47hbx&-%J@hy)rUV#)wTL>DlA2%hSAjIp>|b z1P_~io{BwT+Mk{biGCbuzo)}Iysz&TnUQ_E+;8po#~s8ydb$HV2T&pdLTjPzUAmqS z?zF|jJ!pTYE`yO!=wY;9aP!8Z5u6Uwzn0Oza*Dl8ioH#Wy-mcrjMd)+U$L7RBeCK*#~2Za`m=U-Azy0z6RSIzqU_X|r$-fwTUG<==^y|>kRzn%}-?(GEX zO1ZcC<4BB{u`1Sh-ow~GTkS*N_oM%I&?m2ihL`e}tdlqkiIIPkI*}n_ikrIAj$%Eo ziIS4tQ;%}5L8i@?cXCG#W4tZp%3hG#_>qzZJ%@*Xldi<$++j~v&D)6S$i~?7F;^wC zZ}{sR_9R=$yRuj^`!D`G?&STG@B7d2W@|bxI+Ff=pii;iBDjB=cgp@2ww}~2a_+5d zvinGEg*f$stJkj7W9%7Vq#xN&#okXY2_gpgVd8xKx)SQfZejtod?gb%svr+*p_(sO zXqgmSv57OXB)(@X-H*?e^(y)V_6j~%e=KOB9|yLu>|YgIg!2=-mD<#Oo18a}uR7?v zqO%^mEXf(N)__YNeh>9Jd|2k zO{^vFR8tBijrq^7;{8%}#aktf>W@~{*ETFwHT7bp}ayXVulOdJmm+L@2E zAAH-N)F+8)Azo1S0^_@Qa8Gk+s*#>}J*Jl(81xq3ixxA;!MO(#Cr(DQ~?0!1wBJ`^et< z$*HQ=dz1syq6Wr791&mK}fjzGS=xIh(5zFsA!ZS>wSUzgU0kM|Ri0 z>nG*t%D8*Q9Z-2W_zZ7@C49xoI;-gsPm)IZiVL38?4wuX6|V;G z%w~1-Pu!Y&tW9+#w5Ku?vyznaskdUT?d)n#I**d~1!g>Jk+E;9W7iQw08Ns)6nETo zWbo&)H;x9v`#D-STaDWZ>DUK(^~qtrCwSf>tdGLOao7qts2Aa`jI4cUTYj>RW&8F0wXdDl@MWGjx+xxx!UlUF?DFO4iR({aP+|JQ{0! z;m{_2@6vp3zTQ%~|B~D=ltYu`ENkc{OXY7VSNYa*?EC+-;Elrnt*JQtS5|8IC#)oQ zZw|f7QbQFMbx&qo!e89VGh4o2TWiA;#`T#_k5j?Rv)TEaP?^ zHm}S*T_TQ^0mh$8+jK z<=q?`@*(^(=eHXF0a!`dHSS$jQ{6m2 zrXeWrp7K+a)!a~luF&!oKVnyGM{gwhbeZX@R$JOI1$p}>&n#t853+ls>~KD+W7 z?60P+mDnm0SB?L?u?D}iv^VWktCIiW(cUFZE$u&cHL_NbvCU31(zvF(xFOhu+ceS@pllEh|nXB@g*_Pkk*U*_K8H1iUPiDM!!_+n0D#gj0mpmArN{C9|c$8 zb0_oQ(L8-u`894Jln1c*4`>#^#VR4&g6wY>{K{PM93Z0eS9XSI>w{8~yQ0 z`lIL{(LF*ti4A1V&fS&e^otxlr`6CmfxeLXy*}GCtOF#EyuU1@zvMSERxi427+2cs z-P6Zj0q_zW5x4>AZ}NK%IEfrhMn~L+pQFM#Zhrv0rucV8T*dRuVL!o0U;fGjeL39c zkDp_@&ySH#^n%c9#6R#9v7LvAMSL2c%QHQUW7yr#VRw6WaE|eAblW_6OCxU`oD)-x zyxpJ(j6%j9=}uM6RUfh1c$Zd*95UE)qiSxMq>|YhtCRT1p%!-U@|;Q>MgG3!%b%A& z)sRuwVB<8)U8W7Mt4_XC%pZnwNGY^2nA9os0cP8R#y>Z&|kxyHRvkiul$N)s}rPXG1_c#^6=%ygMw{j<>bv zQ{uZzW~F|A-(I%YP7bfto-VXyZOVIl1@>XwZMP>=2Rc$wPs-V)a$48A;4SmjEvuBnPS1avl%3k5ub$5BQ+jErudk6D^cvJh8r6rBa4>KQvmYwmW>ajWB^bY1k z;5gII6VNgkJM_3gJ0;K1EZRpKwN34hC8FDH;FFABf{v>0``BNI4e__wN87eZ|HcoY zeFf6*z4N{lJh)Z+WLd-Bb{ai_1k<(>^q1(l>5NeuIVa^0vo(otlwfDo$()S!gU%C- z9}-){c|p2P=J532-96XiJCOL}66%)zTSvRmMQ*yUkMcHd`#f=nm$Eidsq=|H!qWTW z9CVV4{IcHT^Pjmtdo*hBW7i(GxO0zm>>HQ%-N17x{Xw2B%9Jxyub@7mll;wqzR-`k zLjRysU$TL?)w$5P=ZRWW9rkoY@t)!*Mk=CAEO*O^ZPnY z%Vu{y-;d$OJLUfwiNlL|2Ik;{T%KB&Et`@@NtO#JzF)hw_VPj40Mf*!-B`0 zN-5iFti}f^&+<;>coAvg$?%*fNE_ol{E57mGX9agiCJEG@_e!X{GQ9I@x2adc~PJzyhvLFj}bbgyXAgUu~z~dgEc$mrGuiU#6Kdn|F#!Su^mJCDb(Le#r$@etSPV-)j8aXT^q0 z>OG1_FXbduG@97W5``pVrHmuT=CJbGRgDZ)&!4Q)HOH=-rYgI3knV_*{jbCGJoNh9 z|G^wEgFTEd2LvZ;PD z`|{;Z=qh{%(;cNy;3}DIwrd;yr@lW^&Sy*}-T`imY516GGh4i8Y+lCst;en_1-B;f zTCFp3bRU=*qXe zlRxkN7KL3YF#_S(ACCsI_R>du-W|l}t+1XMyv=%gaJhA8@MdE5T8PzKMy#HceGl=t zA1dV>-8`c%zdyklXchTtX&vK^P5YW@LrG&rzDd3M_BGw5dOSZcIF)fQ?I^p#%ISV3 z<98Kt!P2Hzw^Zxz)2vU;;7m8ATK6gTfHK!-on#q%kymN>3C@p<`Ri_y?~|YBF2hIC z-NAj_cA}{+h>!4SEq9Y07?hYZ!Rh4NS92chGNBc{<-NS4ZqA&%*e~-(&`bD~Lx0sK zn((vcI0vDcV5$B(oAqJYev}x#Y7eh;@Ps)Abvc-Cwkg^1)$&cXXP;J<8j;4`UU#l(i+?2kz0l8<+ z>hna`w*z?*zkD+O0Oye8kb%YSJ-Y2Vm?CASU7ZHpg8jgv3w>BOGHZ-I?X0y*I}*gJ zGI|nE=+PlaFY+nyFkR$1Xv2vu&OVV(NniDd$m+ql(nmxlBvv)9E=E7J5B#P4hAz_Q z;%WwKaaQB=TdMatk8Fcpx#9H3x3ty7)FR$@Y-60&bdj|5OLvI$VmqPNv}^_U>iBlB zP8*i3xSwC@W}ocbr-e?seydTQhrs>xzb0y={MaK|%9e4iske@GnrZ$$L$arzI(7SA z-I8{vK@TY_NLxNey3?U_=$syOPY*h%2VK;IPU=B7^`N6z=RH}{s$+IVM=nBFG;OnK z=R$0)OBoe}F7Z;@Zpqwm$J{bvXRFlCJ+i)6rq2(*p++%Im#F?OY@_XcachG;IZ5xo zvDc2*;PXdh(LZnc_YNoH%h;dnl3vzkJCMPW#>69S`y92gS6zv%q&D}e#!`G4-&WOX zORs7!gI8t9)QhakawBZZUPjJO^u|>s{F}_*MU8Ssd0b6tOt;LpVu_pdm^I^(j{P!s z*6TguM^DUGU*EEF|6JA4<*JThJadfo;v-Ksq?jAOLw$40&i(ULN3YzfC-diw>grWD z%9xo^^X0kOd#+WRdc`h_KXPP0XP$A6qqj%#E&Si`g;?69^sA=cvxPMAldX(*?d~S{ z?a65wc$XrRefwgM4DE|^{M7SX)c&}d)QCLnKfdL&?)9ua$5akqemUh-@`rkZ&N21w z-iuY=Owq5?R9=4*^U<4ZZBxg$>_gu-^@=aqQt#+hQ!J(M=-A{@AH0aYIeV-Up3@Eg z2miucweEQpe|}3j>j!<)q`c{VJcBOJ5#PZw>i5c%e75?%US>@ERniY?_v`XEOjYW> zDJtFm*H(HR>lmDe5qspwKJm@TzSqC;*FmSU8X0mL7MW{A$6t&cE^kEc zcu%<{?%UBTN$^o>Zg0wu2XNGPb$?}isf+pjlzFM7>67w4368Sf;;19_&SY!DpEEbb zU*@0tUP9Tjem^>Ijm|4|5pCH!I`6+}n|YOcXt@1iCkbBt&`j-9JBBt{32q9CAojy zQhRwnlIKU@md+?EF$MokqN$zr&9sca3y101Px=mcF_LGxt|#u=x~x**UHjzR!=a4W zqW#Q8rJhM?VlFxY+;lxT$I6o~KM%V!C;L&;x~@y8i#R{a=G=U#i*MRgLz|A&P0@9g z({5>#^oz~Z#f(IJXz^3@VYV*le7ni>Bza!0`%yv7ls&ZKf0O;1S8<<^XLqK!^F!|K z_~M{q?4T}aT-__WTX^wq+RfTWBAxG3@8tQNzRyU~!B_XL&x_n`f#zHIeMIBRH}$5d zx2;ZS?%`VsZC(T}wAnI>HYxCxyvNALoRIydKl112*z@>VH&b>%bD@J*wpT7SjLTao zSMu%j%Uw&kL-5uCY4P8V#yL&e66)`-OG9Jv<$5@aj0?{A(tUqo65HLw`31_}O`bi& zIB(E2L6q?iOC9VpB28MCE9DE`68FNnow~mBDLWZwxlNzFxsh_)das6_+;2a_nJwkD zxiY?ryb#(z-4gHZjcsv%4O5UBnP>R-P{7+i7TcmLk6nXZfbIFy3GNdZ>%NFAGV}^E z^fGqoaeQw>$dbs6=%~DtuAk_RuURS=6TFyLD2W;U8_&+cpD-PNdJexxsk=h2P0QM< zcW0+!3=se8P0TT6JtU2PWQDdztO035aPLD+{3-Pc{HVw<20$zP;0oM&z_ABhd%#(b z{U@ML;O~g5yoXP=4<6$Wu1ve%1}Bv$cTf7fmAE`PGa&4{7@6;Uf%(pJ%y*t;zVi(8 zou`@a92(3q_i1GA(;)klWIZ@Pq|ztzO2xm+9DMKGLOJC_M~U;D;>RZ%F}{u$qxd`i z{6b?&d~}RhMu}tO&T985-!CWW zBrZR=Uu!s4k@>yEtpAg8V$$|1eJ57FL~Ki;%?sm{E%BIt=9hhiB{r#J&xCK-&fG`s z#aKQtM&1{{0N=oEyYO{7r{iKHDei^sLl)GcQuPEr$6drqyV+yPB@U;cN*1bj#rgi< zS@j3yoX<45QJucXb=$8}Zf3u7SKMu-6Yo@ROC9G198>l+*aW{DSM{p@ak1kv>?yC~ z+>0#dUd*Q4R?c$j<=of2`U}=ao@agJU{4O2S%&XS;$CmYceVoG*&X=Ky6~NKBTEl~ zYnm914a6m+6Ys))+k+4CMXOs)8t7t8d4rlW@Q}K4;BIy4z&drwz-rD^Z&fn}8r8)E z4Ql$p4DKOIsM!O(YSsYJ-~;Vy>c9#WAGnR*obx_#Gw)i+zl`@e^1JGy0Y^<9n5`6k`4?6E4!)m&SNLbO zuk&*M3@c?>Zqk>j-S9*tOJqx717D<;Ucx;!*{c~F*bl2^G2rQg6Y%sRe=pux`6iNu zu*{`7Lt#9b%lS5(#OKKw2@*@5!v}sFva_5$&l`}Phv0ELe7YOHujbs1JK%jIyl>#l zlX`uQ*WsRZ@V*t^uYmW*9-pR;{rPNlY|kuWWvZb0RA@g%^B@P`mcfS>c*kiR@M;!3 ztAKZt;NfI=SqV=okbzn7e>S|I2H)fGzZ|}o!GGab4BnT(N8y__(2ZPdKrZe^E`&G2 zC*hayP53E%6@J$9SI3|5RCtT>B7e*cVu#T;J?Nbt^iL0Zh&d3l%e)8u#2n~3=0IOC z=0NumpLzcpyYcF^C5>03WA9$Os{ZQsWc{kEo9ipp#=YgLb8m_2*gHk#`z}&j`Xshv zGT(8v@o-Fa9`?>p={PLs(qE)HnbY>Ezj`HAKTB;ypUQa>Tk7Q81UU~)_I05Tl%%y( zuFp%ilbE1AI?Wkno=Z_E+~~@hn2h0BwTY%L>-#r3k0LRIV0!CCa(2K}aFgGxzxIvn zm3em1WxYi94a{bpD13LN;ygR)&%{C5E%fQa)T4_ehBH`y3E~GYp={;?`+Ddvvd=Ks z4^WGZr+sGI`}8@P(nBVXN}KLN=l7!X^Ldp+e+Bj<)hbU`^Osd3 z7oy`$zulo8=+Xmy=qu0CXS{Jy_R5UsSAL82m@e!Xd|Rx4NDPm}E+&a%A)1)=)tT(6 zkumOh(rv^$g3M%z{M2&|j2L`HdhReFW32c^WnJ-*)I0^hc?)a>Ytm5 zyR25iVu>|)qOgrQ)iV4pQr|-Oak2KB$T-x?p5Gv@VrUYz%wp}6`D^YnvG?0#O_-N$ znOhTU~tY%u(VUv_WiHXmd+5wn8=cE-=*NGY{xBOEudGhr%*wX6RyPtMB`8 z1GM|JN4wev%5m_MOYEXvvu@*gF6#p0`>-3p_Zyt~XoIit>hEZ;LazjE3+$!Sg);Yl zjkXtwS*pN~@Z-W-&2LZU80mbul33dyzu0B##~3RlkBtA}Jb~>R&rNX05q46Lmpfo} z{Xzc~zdQZ6Th0_M#cGiG$t>u8yXI@#K4^?T-IIOkM?rn*%t`c(-j}7{iH%k_R(}iC zE%%GEPn`8l#zy9`GbApgs*^Pbhq#H6I)eVi8DQ87edspX2j=ZblDp|;PteY(o<2`I zIpz9!Wz8p`;kQQ9KZHk^enDNAFSQys{-eZecIfq&PORvu$XBje`d)RrtnH>W&VA_0 zzCqeCcL>QH4sN?_e&mLOnNl8*BT^(b+envs=*F%a~uVVm2t}l&AA2m9}}+-*l_I>`8B9 z?Lzh_f4w_-xC4LZ-?H{`a(cQq5AWoD>O<_ixkT)}L5bOvSn`0*tY6Q)PVfKn_kOW= zN$l&zzD*P8`JAfGtnZUWloc}CqoQ@t>_F%j387}iP`1qNZczekx?}xtb z2lFwa5EPo@#_rG0o#L-Sr>e@p(`HS2Wul z{s1Xc%7f3gE?b|QwgZ~uHxKW>Xy2qG#0tZ>vE;9=`yo| zuUDqv44wv^M{qp|uJEaA1kcIJ(Q96@VLWrn!&BB<`adNxB~(Tq?C&`ZzCsW0Tl_e( z2k`*-9yIWssy6F9o_!=d*hvW){OlX~p4&S~y*{J1Wi2r#Cv`gX&orsy$CNLAT4}eG zU8>(>pT%Dn+kyCm675s<;A&Xje+bPBjNxILo#6Z+xqDt>kf!*$=!^b6E#mtv@!OR- zs^VWF?VpEWq}O_CNFSMKpIjQh=%th|J|~gq8S03}f%q8L!FDMWI=8(^tS;WzVcg#d4iZ1}v%*rH zR(#3qv(nGvV-ED~8l7$?zCf{mKr+yinyGV>tjoQucv@=BRrM(XN<%^tM zDEH#>>>K7WLt&}l}3bpMO-P4<0!5u07|lmScl zypH4Rm-{l=N1@9(;nyYkh4%u}`1IxItCpLVZ*Ua+mZ)u6R%Iw(_AbcY=*`>0^~*d- zaC76&4%aPhm_fPnKFxV4m&p1)^BjB{j|cKMOD$q=>JIK0<7__W81kMqxSe)<(S6A- z*`JWKC0>y@zMY?eR>Ip6p3UG+)oDujRM~Y6wztPi_LRdPYnY$av_`p{L`Bq8b3lfx2U|7Nwp<4s?HDoP&nR0qJ)h%o-Dh9rC1%Vcr-R z+?PE5yz@U24O5ziC2BtBgw|je2Q+*Kw34&vM$_=WPC!Es_Vb~kF-|A)N_3%8%X;5r zP9D~UpVW9ZyFV*z)O}ENVI}ssrEXx(YPpkq|E=P#C$Zn;Or7hF(MKI+?<23uvmYbT zNAEqrc%9?hOY>bnPRFyKtLu@0KVe+MRyv^V1BrVQJumjm2*1ZT)1*sqm-V|8dc4iR z{dtZ1LiG5`VcZw^v8;l7-SGP0BIGndACvj)!MPQBpYeG8?2ysVT88_Xqx+e625|r5 z&BOg{hMnVbg`uj1o=@qxxln)OjPXxhOMi>e-%@$$Zz3zUmX$1JU@PoAq1*9Z;)ywX z%*S&wXO($)rpM6-CZ53^)XWoy@uV+5j-Jpuw@OWn=M?aq1fIm|?M!Ms#ZK|`0{xw{ z|ND3T#HY9LG<973VA*<>B;fz(`)3bfuS<-wrzgdi6wIf?zBj!NlFhR&k1oWv`na4o z-zz#Xr*T%dSAVQfZn^XPv3XwbetYs`_~`6fpEv6A>6}T1O1gNOYF^HFKGrBcJf~_^ zyThE;!8WSIFJ+&#vCZ4uC2Cz(&Kz;8TwCrsh-DTbV>Q`W%R=f-W*4jYRdZNP6}-yW zg;q%%VVZr26|9_A67MT(IBI)xLq$B!`!@AqXp!!6^|#nx_C?dwTP4_C<;iU9hqr7H zxP7)7*%LXo2lV;lRn91VIN}$O^pf@r_D`@rD`Q1mZF2wU1vOdj#R~izN2f5NGiK!` z;Xzi(dKtguTmbqbwz5~ItO5Rvd~{CL<(EC89}W)2ux$&4gV;+p`%aH#op|y-iOCmQ z)Y4BY*<0`y=W^F_XOzro*lVu$FnHrvQ}(ZkWy^OldyU_s8dR#AevWTDyKgG>wd32R zd~EWAy59$V{tu6M_{iKNnYe|qU)g^_Ic}y&eNN*zVy8;{li+?7xcEWB_Nw?;up{aJ zwr~HAPBqtxnN_} zT&_}_OWvvyd8Is@J3O3oKF%$ciY;Gg#ae5u*t)sa+!YnnKIMq}(3ipW`yQ_CU$N5d ze+8~nPMs+X`M6$f1-O34k8KI)C-f3pozeWuB8OIE>mx~*%`@F4DyMaHFb5eq!|?$7 zHJ)T_iMe^?de_Uy5;`#8HTT_RuNu~22j*_aZ<1$>(eZf5%A|u;>B+L)Ty$!E0>r$HBFzewr*E@9+&xRj5A{8MhsayOtDA0Q=ai*yV>K&Z;i4S ziX7MQE{)kk)0go(3m>P*cNu%BNh8n1TNFx?+1EKsfpy9~{5>vs%e%a)uB*&f(_gJd z`U3nB8t|v8hf38_&0mpsq+$5X5IH-tmcCHI*igl~jp$pUm7FIzGCqD`(8V|A;D4Hp z->C{;7IrrNtdY5Nf$?4FK_70HK0JrB6m>sF|5YYZ^yBx^kKfDr`??=5xK(+!H{&z- z&2*(N$GXH0dctWTY+Aabtchkp3e7t z9tRjRZf4Hhf}F@bNjc=C5jklE>$*f%M;TPjg) zOMi3wma0@@y`Ai;$Lsz+d@zoka+;LHZ&WxJL#+2}y#PLoE>eNdp#;9jJxM7&=Ezzc z@t-?yd{Ow67(Vy&5A<)DPd-SU)yT|Jh0knys@Jwh(>|Fh>UHo2?o*v2UOm{>Ou?4?o2^@gdfWpT?K^Ob;~1m--yO zRBxhM?Pj3H_-k-}EFgpu{wi-{5 z$oko8Z4WF_tFyJ>TA7u5*_NSCjspEqI;h}&QUW;#2>^JT^8Cdq?X&*aNIEZc7hmm*yy>bw}@)R<62$_2tnR^DA zdls2{4w>_0ttDjF1#}p(vkwf;Qk(Uhq&8uxOQ1XP%RBA-=JtSAll^v74*R#H-z~+~ zSq1J6c(=2#pQ{TduE;J1K!RP$7s+jM;w7Km>;IcOH z9^^s#Y}jTE@_qI^^F2L5`oR7O%KrNa(vF2w5MMS{8^k8_&IBE?Mdj>1&aBe170^Z2 z4!m^+yivwFgO^Wy*8zU}&J({X_TIe4-kX!eo4rcB*-uqRUA5o0WIqZzr!juH*tAX5 zo!xXfyq9r+J}@62n#QxBl8exz*hW6PAKA+j^rs-6f_or{%~LkG_JDH_xNHCY>)1Vv z_ryU|6Km8&jHQPg>xGSXC9?H-^n|oU_HupwvXQ-kf|JxGbJLH4C#!<46>rM0qtBK4 zRh3oq6IJ~-51(qaUF(2d=+}PkR;#WPJ74aaW=y32dH2lSPI^bya9I^#?T^}Ivw(6IFV7Oj1vcRp9RL-Xyd-)E(*WW%rP z?ZKI?vfcU}5+L6;>sa3Jg!@g#8ke!gVO-%HRmON(pOX1`p48simdsMzH1}NmV1l*# ztgl;y-?H}w8>RdAk*Ox%4=#Hg(~e_*-v>!Zu~C zvjo{%i;R{H$R5d>%u3EBlQXF{)mK#AK!ZyLYKg&?{e%*uQpuf7;>Tc*;Q+FYKhx$M zZ0=Ge279(1lcwYUweZth1MM#>X7<*A$4faU*5aJ)YIIY{RxpEWd2{NR%;m`h8vk$+Wk}FIVAod=zp@_B6j1aHBK^*9D9yDV*(zJ zHK*QBe}X}NtdTia5AYWK7s&Q7-~G6eO4S;Z{I#45G)e2Dgqw7jt7UicHlh2B^qpfG zZ-o!NRgL(TJsOJs9Fb#lPprq&BkY~lne*ZdhE%#2`QM($C_I%F zTa=zZ9JkfL{zGOf^%6JHz&f?8VTjEs{a5;kjK>l)@{S@gBNN5r=>CuH8EZYZ(q9Yq zCwhHmR-zLs3hv1zo5qr~NcV{vhdO-L5viSylRDiR6)YQjgRjE*fw@F7(?Om#+OZ0}#FpwOkHm!Ub{Bm~`Vp(R=UDbEb=-@af^)Of zYfAPU%RFrjc(YVLhmTf6d^v6zkT%6&oU4A3bz)275j8v zmXiqZ8_D@^^RAckJ96tLGaum2^QshnnR~1>e#)|{FGKI0R@u5&n8S1Sn)o5B2CT|; z>^tHdxpd;)=o{JRvvXf_hFgW%>lI_qavAp^i%pWomr+?IzRMQUb#}{~LvSi%5m!z# zDkCO3mRMfCHcX+O0n$D~nVmW=Ky1i!S&vn>AKD=6TQ%|TD3vd3RNHrMP}_gF zLEZkw2BmflT4z?)xcM6BBWvo<4BBU!>T1!aG2%R|GaWVdnPeSv0pw}AJ2ZW!YQ~oe zjJ5Ka_)>QD3@w_b=ugCkd}glrcvYVDhs*=&U5usa2IheA^{gH4ApK76{aeKR;dW@- zH^VyXVz?sT}@;T-;JeIfpNu`3}EJbX?QXsU^&3z6)LDEX>{bSDwVr zvSaZ3$X&W>NU761H=KUBjJVaVI(udbZC$GhI=;rIcO!|C3b_(~} zm3J^2dE>@uY!n$wJ-EgB9(ipC$9~qiLC*S^Pd@g*_o&m~*MozR2E4oE=h^)m5p2Sbl%e{67 z>&h|hpPKHrep$vQ;!3bb))l6hWlnTK*S;mHB|AsOuP>`AvC`-a zSyLbmXWzrnP}-78oKhn`zwu+2rJt$OJ2oWfXX)*I^o2Mwxw%fA{`Q7soU>Rjg3EDY z1mZi`!{yAV>3tpA3f<=}sA0{11pZys?DM)7ylb@1dsL8Ck0(;o^-e6ZI z)?nkO?aHprtbz1@Ol^N3efu{44tz}dG&+cW{p=uC+H}^V2XFAt(d8LG)$X3=In&gT zbBTg$jWrFoQvODMvu<-Kd*8f?~y=vy~Gp0-5PPNRibKHZ+*+-hMsrV$-+#bVs z?Lyz$Y;0Xh`z6X8*V1b?tvRbKzCcyBRw8HpR%QHMR>JI$%EZPFaqcw=lN+UC_P5 zN;}J}TI#HH?m|CKQfH*UIvwl{vHiYuBmMe&brlW$^tqF|FX%qkl~?zEg|xkGtmCa^ zKMAuEm8O5bSSRw`q=tsmdg+?%K=wXg$X_CU)e*USR?FQ!sk<+y#9xem&~+0#upwSn z#4*P7Uc$^~#vRTLjql>Sjp4>>l)HJC`E+TbIWr{A-idd#Yq?fQw~{!dGsKF-&=+%5 zrlHw^{@7~{YYnNqWtTZIE1emFci7XJa`k)ESy}ZGG%Q6Q-JtvXWM95&61UnaU&4FW zmNRtI*wtqqwi_$96IJOG?yHH1kpDRg3=$yci4isMya+J|^e9ijGRtA6)$YwGLx{ zSB)OyCFVrRO!hX>cP%%^P3x(6R+XX0=CGoPtU;e-HkLt`cJ1Qals06H_Vuo$FJT*2 z&cr-gd{Sf3CPe1s4__^u_Df3sMAEaK<59;(O z>{J{3t4!8r&~5lnWKH^X{|52l%kSC*HW|;}JZ$H1Z0LU{7DM!8c#iMcy^|VibDV1v zPgymIyYR=@Rt>sc=9cR8>l^Ibr`S8aTYnxET4tlAz`kxMBO%=G++TNnpY8b_Ij2(B_Gmi2}Cx$g|8rw}9k zNLpz}Ue_n%nwl})|tsJD${H7x4ICX8E_!#kO)RTA)6qtEc@r(T&eF~25`FI+Eca5Nj*-@bE0 zp`=Y>DYfl!>Zfh&DfZ$zg@)K_8*sBWE(SNDzde(42f%?Nw^bSKUc$Og8TJIRGT5e@ z8e-s^=1eSEGxpj@wSFC*zsz>OMIEQ8qs-@}_*RZ#$FUa-U*pM=8s<-9*2{RB!%8V* z9=${@qh9Eun*Dm=H8!F6k(u*nQi&4Q#M_2(81d_Peo5jeGAaDpwN)|ZZoB7-4}l1f zOrKP=VFx$u>oMJjWb+040)O-+D{%sIZ; zQ{!L{&<5!{DW3H?S>PsZ6F*(B)*@xdI?`BsdmLBX_>YxzRP)ZyHzTU1H05xUL?? z^=14}_4swiJGZu3;J?1j~x@}%N zZCBbU&$aRCwtS0?8H0D1tLZke3w9!pMcpT~*@JIe;Roc7BHgx}J!P0~G7st>kok~D zw|?rHfxowJy6Dgp`-%jfieC)RQ;Kv0%)vtZ9({(d=kshp9{(ErWPA(0ovLH#nw9u0 zb6WS&KIpzo#j-cj_tvOb2HPBeyM1#RblRJ>Ze_y@+o_5{z4|q+#|K3#}`bD)mQrG&n^$$pR&wrB7 z==QhY^`A)4;PL48-+k|$|4BISHM21K!)tlPU?INR;SWbH4?l+=djG^$ck|cXSss4P zdXxDHsxjZ5-><#D8`Bxi1+P~h_wag5@ba?hyw<2H1uuyny^v4t?NWiQoD;U1`v@b$ z*oEc$GMXy+v(yDU;kl&k5B*ldj&qe`~n>F6VPXQ`?=&FzkN@C zhCmY?Hez{B#RKC05wNV-^*^c zKMFVHBiw$DmyjaHt^b6EoBB;WKtcVJ|7ha;w~YZ0)30|7c)0zqjsXwnui7wi{p-en zhw0Nb20UE;i80_|{G9(P^^Y&V^&guUzuP>x@WA*4^u419e6@x@Z1C>@B?bA7LPI|S z_(S=L@tfTta8thecoXFB(r{CL!t^^{1g<)D{UOY}$Dn+-2)v~Te4Ph3=o99jU-7u` z?K;2N|JH@tpW7t(o8wzU2wt^$;`U`V+$JGoN{tWi?iF~63fuQ_-URab@}n9*-k6^N-ndQR8W!ZQ8WMQ)6Tn|RDsZ!Xgk22h z-*rsq9~b`*^_r56kOVoJ%`{tK5exxx!LHXbG;70l9 z!=E!3nBT+7Y+tzlm{^be3h=M`(!}k1)q{`QzB3v=e*Fz!nYjFiioo|4fxlb?UiFHu z-@rf2{}v55<$oPy3g~m&S0%sMzJ?I|RSh@oqj34+Uy8_oD8KVHfsd~rp4RYD{V$aN z^{+?shvjeiHv~R@{`SAuaDzU%Q2Fal3OxD==<~+6qU8^fJb?Q-jq;rk{EqMH@(p+x z|FQCi;jes8@|*Mv(|_{o0yp(T7{2|Vbp3ucBVQ)|CjD|G8wEG;56g#FqKTh@zpj6j zehiiW#QzrjO!;gL>CeOuCdU8!Mc~Wcko+co@lgGBKbpAwdJQ+_-wEaK(r~kVCqnR> z-_+$B^b6CcTfbzb~Jw&zw8+BaQ^LMz{B}p*6@PS{?-uwt7Fmn!{zsl z0S}jdya=4C#{2~I8xzRO-!QmA|1kajle9n5%-=Bn{)V^D9Dfqg{#iu6i{&@<6M@+0 z+y9OF!{cLwG5(8*lIG20hyf3f_g zeG$!X!lU(@_($O;em{S>*}f?KO#Gwm6ONdlQT#LHvl#wklwTyjS-1Q$>n}#XV)={0 z4gA9Wqp?)%i=y?9n?G8!_r`3WY2StG_wyGi-{gOo{tx|p@GnyTh2sCq%^#Itv;Kbv zJi_LQ_&Y}V#qt+{oBcaleld8Y_~HIHG=}_y<;%-y_K&dqp4IusFaM4*ZNEnnjH2JT z@P~AMv;HW4zi9pztKYN_*fu&$-@_Vzv;Ri<7is=*{wTzIW75woKT02S{K`;-ncsvv z5x7}?6#ruMG2vnP@mdr8Z{la>54X=PkLvF*+$-6f|A+bG!OiwLyfo`K`&YF5hYkIi zjpQ#XpB@^a|7QKg_)`pSwlB)RVsNwl;ry#L{l|xUO)|@m(rkS(5I2} zP52n}598-0H|Z0Ve^b7q`7adiBOUq*@b_Ms^4Y*k6K=N8DFTn;ZMMBrxsiNa0&8iv27@i+75BITR(i}K$rzaf&p7~Cx1iR3r? zM;N}nQsmdfKde7qWv{?QaA(TI`tKDDpJVioaQPHI=Evlp*}f`rjDknX_%(wY?f(Vf zZcRMElQ$)?X}tG5(qT+llbcgop8eZCZ4EjPlRq zZ&j%L+;qur(l@NXU(@jM^T#ii{NuwLHQX#eT>ma^a~$oT3dg^EpYxnXug{0&-+^n{#&RnD8Fj9lyBnagy1~# zG2zkri@{C#8l(LNe&O<;(D;qde-FRt|Db*Q#>gKo-_JR2{jZ%{{s;m={l&`v4S^SH z|G4nkS7`Yj7k-ZP88?5f_&E`{+5T|-UjBc-{_ib<|I-?Ne)UKB z=VKZA3h3*kgnq^BvrtC=G06XYoqv3I+|~SvBna~7iom^$Z(po}zGbe%F#4o!^A7h`^;( z^JCU;=5NnUTz)`k3h$rH zQ@@G7iC=pJKePQOBl%7E-Xi%AhvFkh%Ezq#A>NquG3DQhz)kvOqHx}rAG3UOe2CU> z=9OP&er3>C_-oSFq))Vb6W$leZ^}mwxLLmmkII)>erqJZ313kJUM&CeBKcb)a1(zg z0yp_v6@f1?=r8q;qQB8UqwO=xA47hO{L%VN{LeqXN&mhm{zm(HBkecy3w%`jjr{FJ z@)v^_D?du#B?kR+Md}{|Zscz`|NKV#qw;6+Kg!=ZM*ckYoAfpHSCoFHd_>!4!n2X~ z6~pgB;imiu{$~4%m2dJl${&+H=ifhy)qkOIv;9%|FyYbmU1S z(f_0U!|Xp!B!4mbn)GWWzbXHweCH$OoA4-p#pJ&j{QU42`b~s?E`oot`is%GSpHvk z{Ljz6bRzT{B|ogMne;c|?c|>bo{zwJ5&AehoA#es{`Lqwl-YlrDE}DxEmDA)zZm~b zc(i;I|6=r67ipiVAEM=({Ed!3zwY=&$UlnzzjC;tpQ|GLDc1il6kd$~#o!lO|Apo^ zZ;#Y(=8y8ngh%zKDPK|jSTN)-DxYTg z%fZi-A2WYL1plJ-8}iAOKcmYx;H{DJP5fSuz#lf+mkYt`&S`y?45L2hCm&}23e$h@ zIn{5r?=-lW^f!@n_!;20zDCxc$8Udr-npz#o8`-|p!_Jrdov1d&_CRMFSXge6TA%C zcT(eL;-3XRs(i1Q(Ekg84@Jr!AHUdwD1Cnc_!;t_0Kb4fev=Kj)ISRDCmZ=1MV})4 z8OawsO_aZiM5EwAw&CwlaHIa8XMT6lImx$Ce^fq)(H?#ng?};n8}KmyR(SXs^@rQP z{@m~%#w&OjgOSJjCvTAe_Z@WWgGta^UYsueEwC#hnuY*a{M3Fej`CK zxKkwmuNn?|6SnUc4G*_JyZD^?zo2A%CgP7IG~uRv<%+(52M%rWY}Q2X$F@QY&Zy$SI5 ze*L=RXUflVkT&r*Tj`uP+$_JH{J&iJEEw`ptp65kALtL$XZ<^_%|xG4Q8E^JBKZM1|)U(ell_^2^L`;@=X1o9lm01kQ_5AG3V3{Tb4l zaFf1H2!2M7uO@w>`A1bA%D?FvZ9f|P50~Gk;b#5kQ+~8&?~U0$)BX#WpS@P_TVmiB z9-rNYbFqJ;SeW(y7CxE+zopN4>DSTZ8`Xa!;NKYds=M1he5do^n)~j*>)sDLixw_e zxS+wwtXsdn{rsPJ4zU`Vd*RIMgUc7M4^=lVhyKqh0+U&x$s}^5#ZNqho>esGXwdVSH z>gKyX{Gm1Xz0JA#=4Izv-`#gVc#m0K!*va7uU(W~ymIZTYt}5j?%IWI+3f1la^NfBl;5Jf#-A`=;f$E@)eM*V?<=?z~?uxNps!cYS#6y*%72_r0qH zYu0^W?R{FJ znh%rU{&n}Szi)lp%Bawc^M>V0HD4)x6X^=eQY&~NV`T7&^B})vT;^(Govxj9?W59h z?5R#Si*G4&5r18z>*B|Qm-EB_NILl>`IB^n+cRMCgwoL0;mL14LAtAX4$^I!W2sGS zY&aoE`JW)&_57A};HX+NHh)STmc>~ABlyc_34fBkgp5_!m+)r|*Cp_vyc>9y+%iHq z*T?v?)bY?o)IoXg8cCNp&Arg4-)`yhd96P|y5;=R^~&2NF?EJ?o4Q8EA4lHvxt+fe ztd&lu0sg-LwrY%Y&PPX52JKkqr_1`Fs%I^AY9yJiOUnERe.conf` + * key=value,'#'/';' 注释,未知键忽略(前向兼容)。键:exec / args / env / enabled / + * restart(always|on-fail|never) / backoff_ms / max_restart。**无 trigger**——sysui 只做 + * 常驻系统进程的监护,`enabled=1` 即拉起/监管,拔卡无关(系统进程不认卡)。 + * 说明:TF 卡 `MeowOS/sysui.d/` 作为扩展点本阶段不读,代码只读板载(最小化)。 + * + * 控制通道:AF_UNIX 单行文本(默认 /tmp/meow_sysui.sock)。 + * start|stop|restart / status [name|all] / reload / pid + * 守护默认后台常驻(无子命令即启动);`-f/--foreground` 供 init/调试。 + * + * 状态文件:/tmp/meow_sysui.status;pidfile:/tmp/meow_sysui.pid。 + * 日志:stderr(-f)+ 默认追加 /tmp/meow_sysui.log + syslog(LOG_DAEMON) 尝试。 + * + * 护固件设计(防真机开不了机): + * - 任一子进程崩溃只处理该条目(填表/重启),主循环绝不因此退出;不级联、不碰邻居。 + * - 失败兜底(有限次):某进程起不来 → restart_count++,超 max_restart → FAILED 停止 + * 自动重启,仅记日志;绝不形成崩溃-重启热循环拖垮系统。 + * - 空配置可跑:无 sysui.d/ 或全 enabled=0 → 空转存活(只写日志/status),不报错。 + * - 最小依赖:无 dlopen/无渲染/无卡热点扫描;仅 POSIX + Linux 基础接口(同 svcd)。 + * + * 编译(mipsel 静态,见 build_sysui.sh): + * ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -Wall -Wextra -static meow_sysui.c -o meow_sysui + * 宿主编译(集成测试):cc -Os -Wall -Wextra meow_sysui.c -o meow_sysui_host + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ---- 默认路径 / 常量 ---- */ +#define SYSUI_DIR_DEFAULT "/usr/data/meow/sysui.d" /* 板载监管配置目录(权威) */ +#define SYSUI_SOCK_DEFAULT "/tmp/meow_sysui.sock" +#define SYSUI_STATUS_DEFAULT "/tmp/meow_sysui.status" +#define SYSUI_PIDFILE_DEFAULT "/tmp/meow_sysui.pid" +#define SYSUI_LOG_DEFAULT "/tmp/meow_sysui.log" + +#define SYSUI_MAX_SUP 32 +#define SYSUI_NAME_MAX 64 +#define SYSUI_EXEC_MAX 256 +#define SYSUI_LINE_MAX 1024 +#define SYSUI_ARG_MAX 128 +#define SYSUI_MAX_ARGS 24 +#define SYSUI_ENV_MAX 24 +#define SYSUI_ENV_MAXLEN 128 +#define SYSUI_RESTART_STR_MAX 16 + +#define SYSUI_TICK_MAX_MS 1000 /* poll 上限(监护探测 ~1s) */ +#define SYSUI_STOP_GRACE_MS 2500 /* SIGTERM 后 SIGKILL 兜底 */ +#define SYSUI_STABLE_MS 30000 /* 连续运行此毫秒 → 清零 restart_count */ +#define SYSUI_SOCK_PATH_MAX 108 +#define SYSUI_SIGCHLD_CHAR 'C' +#define SYSUI_SIGTERM_CHAR 'T' + +/* ---- ANSI 终端配色(status 回显用;仅客户端 stdout 为 TTY 时保留,非 TTY 去色) ---- */ +#define AN_RESET "\033[0m" +#define AN_BOLD "\033[1m" +#define AN_DIM "\033[2m" +#define AN_RED "\033[31m" +#define AN_GREEN "\033[32m" +#define AN_YELLOW "\033[33m" +#define AN_CYAN "\033[36m" +#define AN_GREY "\033[90m" + +/* ---- 被监管进程状态机 ---- */ +typedef enum { + SU_DISABLED = 0, /* enabled=0,永不自动拉起 */ + SU_STOPPED, /* 未运行(被停/等待退避) */ + SU_RUNNING, /* 子进程存活 */ + SU_STOPPING, /* 已发 SIGTERM,等退出(超时 SIGKILL 兜底) */ + SU_FAILED /* 起不来/反复崩且超 max_restart,停止自动重启 */ +} su_state_t; + +static const char *g_state_str[] = { "disabled", "stopped", "running", + "stopping", "failed" }; + +typedef struct { + char name[SYSUI_NAME_MAX]; + char exec[SYSUI_EXEC_MAX]; + char argbuf[SYSUI_MAX_ARGS][SYSUI_ARG_MAX]; /* args 拷贝 */ + char *argv[SYSUI_MAX_ARGS + 1]; /* exec + args,末尾 NULL */ + int argc; + char envbuf[SYSUI_ENV_MAX][SYSUI_ENV_MAXLEN]; /* "K=V" */ + int envc; + int enabled; + char restart[SYSUI_RESTART_STR_MAX]; /* always|on-fail|never */ + long backoff_ms; + int max_restart; /* <0=无限 */ + + /* 运行状态 */ + pid_t pid; + su_state_t state; + int restart_count; + int last_exit_status; /* waitpid status */ + int has_exit; /* 1=曾退出 */ + unsigned long long last_exit_wall; /* 上次退出墙钟秒 */ + unsigned long long restart_at_ms; /* 退避到期;0=无 */ + unsigned long long stop_since_ms; /* SIGTERM 发出时刻 */ + unsigned long long run_since_ms; /* 本轮启动时刻 */ + int stop_requested; + int restart_pending; /* 停后需重启(restart 命令) */ + int user_stopped; /* 用户 stop,勿自动拉起 */ + int in_config; /* 本次扫描仍存在 */ +} sup_t; + +static sup_t g_sups[SYSUI_MAX_SUP]; +static int g_nsup = 0; + +static char g_dir[256] = SYSUI_DIR_DEFAULT; +static char g_sock[SYSUI_SOCK_PATH_MAX] = SYSUI_SOCK_DEFAULT; +static char g_status[256] = SYSUI_STATUS_DEFAULT; +static char g_pidfile[256] = SYSUI_PIDFILE_DEFAULT; +static char g_log[256] = SYSUI_LOG_DEFAULT; + +static int g_shutdown = 0; +static int g_fg = 0; +static int g_sigpipe[2] = {-1, -1}; +static int g_listen_fd = -1; +static int g_status_fd = -1; + +static void handle_connection(int fd); + +static unsigned long long now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (unsigned long long)ts.tv_sec * 1000 + (unsigned long long)ts.tv_nsec / 1000000; +} + +/* log:stderr(-f)+ 默认追加文件 + syslog 尝试 */ +static void su_log(const char *fmt, ...) +{ + char buf[512], line[560]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + int l = snprintf(line, sizeof line, "%llu [meow_sysui] %s\n", now_ms(), buf); + fprintf(stderr, "%s", line); + fflush(stderr); + if (g_log[0]) { + int fd = open(g_log, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd >= 0) { ssize_t w = write(fd, line, (size_t)l); (void)w; close(fd); } + } + syslog(LOG_DAEMON | LOG_INFO, "[meow_sysui] %s", buf); +} + +static int safe_strcpy(char *dst, size_t cap, const char *src) +{ + if (!dst || cap == 0) return -1; + int n = snprintf(dst, cap, "%s", src ? src : ""); + return (n >= 0 && (size_t)n < cap) ? 0 : -1; +} + +static char *trim(char *s) +{ + char *end; + while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') s++; + if (*s == '\0') return s; + end = s + strlen(s) - 1; + while (end > s && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0'; + return s; +} + +static void set_cloexec(int fd) +{ + if (fd < 0) return; + int fl = fcntl(fd, F_GETFD, 0); + if (fl >= 0) fcntl(fd, F_SETFD, fl | FD_CLOEXEC); +} + +static int read_file(const char *path, char *buf, size_t sz) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + ssize_t n = read(fd, buf, sz - 1); + close(fd); + if (n < 0) return -1; + buf[n] = '\0'; + return (long)n; +} + +static int write_file(const char *path, const char *data, size_t len) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return -1; + ssize_t n = write(fd, data, len); + close(fd); + return (n == (ssize_t)len) ? 0 : -1; +} + +/* ---- 配置解析(复用 svcd 的 tokenizer / apply_field 思路) ---- */ +static int split_args(char *in, char tok[][SYSUI_ARG_MAX], int maxtok) +{ + int n = 0; + char *p = in; + while (*p && n < maxtok) { + while (*p == ' ' || *p == '\t') p++; + if (!*p) break; + char *out = tok[n]; + int oi = 0; + char quote = 0; + while (*p) { + char c = *p; + if (quote) { + if (c == quote) { quote = 0; p++; continue; } + if (c == '\\' && p[1]) { p++; c = *p; } + } else { + if (c == ' ' || c == '\t') break; + if (c == '"' || c == '\'') { quote = c; p++; continue; } + if (c == '\\' && p[1]) { p++; c = *p; } + } + if (oi < SYSUI_ARG_MAX - 1) out[oi++] = c; + p++; + } + out[oi] = '\0'; + if (oi > 0) n++; + } + return n; +} + +static void apply_field(sup_t *s, const char *k, const char *v) +{ + if (strcmp(k, "exec") == 0) { safe_strcpy(s->exec, sizeof s->exec, v); } + else if (strcmp(k, "args") == 0) { s->argc = split_args((char *)v, s->argbuf, SYSUI_MAX_ARGS); } + else if (strcmp(k, "env") == 0) { + if (s->envc < SYSUI_ENV_MAX) + safe_strcpy(s->envbuf[s->envc++], sizeof s->envbuf[0], v); + } + else if (strcmp(k, "enabled") == 0) { s->enabled = (atoi(v) != 0); } + else if (strcmp(k, "restart") == 0) { safe_strcpy(s->restart, sizeof s->restart, v); } + else if (strcmp(k, "backoff_ms") == 0) { s->backoff_ms = atol(v); } + else if (strcmp(k, "max_restart") == 0){ s->max_restart = atoi(v); } + else if (strcmp(k, "name") == 0) { /* 名字以文件名为准,name 仅备忘 */ } +} + +static int load_conf(const char *path, const char *name, sup_t *s) +{ + char buf[SYSUI_LINE_MAX]; + FILE *f = fopen(path, "r"); + if (!f) return -1; + + memset(s, 0, sizeof *s); + safe_strcpy(s->name, sizeof s->name, name); + s->enabled = 0; + s->state = SU_STOPPED; + safe_strcpy(s->restart, sizeof s->restart, "on-fail"); + s->backoff_ms = 2000; + s->max_restart = 5; + + while (fgets(buf, sizeof buf, f)) { + char *line = trim(buf); + if (!*line || *line == '#' || *line == ';') continue; + char *eq = strchr(line, '='); + if (!eq) continue; + *eq = '\0'; + char *k = trim(line); + char *v = trim(eq + 1); + for (char *q = v; *q; q++) { + if ((*q == '#' || *q == ';') && (q == v || *(q - 1) != '\\')) { *q = '\0'; break; } + } + v = trim(v); + if (k[0]) apply_field(s, k, v); + } + fclose(f); + + if (!s->exec[0]) { su_log("supervised %s: no exec, skip", name); return -1; } + if (s->backoff_ms < 0) s->backoff_ms = 0; + if (s->max_restart < 0) s->max_restart = -1; + if (strcmp(s->restart, "always") && strcmp(s->restart, "on-fail") && + strcmp(s->restart, "never")) + safe_strcpy(s->restart, sizeof s->restart, "on-fail"); + + s->argv[0] = s->exec; + for (int i = 0; i < s->argc; i++) s->argv[i + 1] = s->argbuf[i]; + s->argv[s->argc + 1] = NULL; + return 0; +} + +/* ---- 监管表工具 ---- */ +static int find_sup(const char *name) +{ + for (int i = 0; i < g_nsup; i++) + if (strcmp(g_sups[i].name, name) == 0) return i; + return -1; +} + +static void remove_sup_idx(int idx) +{ + if (idx < 0 || idx >= g_nsup) return; + if (g_nsup - idx - 1 > 0) + memmove(&g_sups[idx], &g_sups[idx + 1], + (size_t)(g_nsup - idx - 1) * sizeof(sup_t)); + g_nsup--; +} + +static void build_status_file(void) +{ + char buf[8192]; + int n = snprintf(buf, sizeof buf, + "# meow_sysui status\nsupervised=%d\n", g_nsup); + for (int i = 0; i < g_nsup && n < (int)sizeof buf - 1; i++) { + sup_t *s = &g_sups[i]; + int k = snprintf(buf + n, sizeof buf - (size_t)n, + "sup=%s state=%s pid=%d restart=%d exit=%d backoff_ms=%ld enabled=%d\n", + s->name, g_state_str[s->state], (int)s->pid, s->restart_count, + s->last_exit_status, s->backoff_ms, s->enabled); + if (k <= 0) break; + n += k; + } + if (g_status_fd >= 0) { + if (lseek(g_status_fd, 0, SEEK_SET) == 0 && ftruncate(g_status_fd, 0) == 0) { + ssize_t w = write(g_status_fd, buf, (size_t)n); + (void)w; + } + } else { + (void)write_file(g_status, buf, (size_t)n); + } +} + +/* ---- 子进程管理 ---- */ +static int spawn_sup(sup_t *s) +{ + if (s->pid > 0) return 0; + + pid_t pid = fork(); + if (pid < 0) { + su_log("supervised %s: fork failed: %s", s->name, strerror(errno)); + s->state = SU_FAILED; + return -1; + } + if (pid == 0) { + /* 子进程:独立进程组,防父进程信号误伤。 */ + (void)setpgid(0, 0); + for (int i = 0; i < s->envc; i++) { + char *eq = strchr(s->envbuf[i], '='); + if (!eq) continue; + *eq = '\0'; + (void)setenv(s->envbuf[i], eq + 1, 1); + } + execv(s->exec, s->argv); + fprintf(stderr, "[meow_sysui] supervised %s execv %s: %s\n", + s->name, s->exec, strerror(errno)); + _exit(127); + } + s->pid = pid; + s->state = SU_RUNNING; + s->run_since_ms = now_ms(); + s->stop_requested = 0; + s->restart_pending = 0; + s->restart_at_ms = 0; + su_log("supervised %s start: pid=%d exec=%s", s->name, (int)pid, s->exec); + return 0; +} + +static void stop_sup(sup_t *s, int restart_after, int user) +{ + if (user) s->user_stopped = 1; + if (s->pid <= 0) { + if (s->state == SU_RUNNING || s->state == SU_STOPPING) s->state = SU_STOPPED; + if (restart_after) { s->restart_count = 0; s->user_stopped = 0; spawn_sup(s); } + return; + } + if (s->state == SU_STOPPING) { if (restart_after) s->restart_pending = 1; return; } + s->stop_requested = 1; + s->restart_pending = restart_after; + s->state = SU_STOPPING; + s->stop_since_ms = now_ms(); + (void)kill(s->pid, SIGTERM); + su_log("supervised %s: SIGTERM (pid=%d)", s->name, (int)s->pid); +} + +static void apply_exit(sup_t *s, int status) +{ + int failed = !(WIFEXITED(status) && WEXITSTATUS(status) == 0); + s->pid = 0; + s->last_exit_status = status; + s->has_exit = 1; + s->last_exit_wall = (unsigned long long)time(NULL); + + if (s->restart_pending) { + s->restart_pending = 0; + s->stop_requested = 0; + s->restart_count = 0; + s->state = SU_STOPPED; + su_log("supervised %s: restart -> respawn", s->name); + spawn_sup(s); + return; + } + if (s->stop_requested) { + s->stop_requested = 0; + s->state = SU_STOPPED; + su_log("supervised %s: stopped (exit=%s)", s->name, failed ? "fail" : "ok"); + return; + } + + /* 非受控退出 → 崩溃处理(只处理本条目,无级联) */ + int want = 0; + if (strcmp(s->restart, "always") == 0) want = 1; + else if (strcmp(s->restart, "on-fail") == 0) want = failed; + /* never → want=0 */ + + if (want) { + s->restart_count++; + if (s->max_restart >= 0 && s->restart_count > s->max_restart) { + s->state = SU_FAILED; + su_log("supervised %s: exceeded max_restart(%d) -> FAILED, stop auto-restart (exit=%s)", + s->name, s->max_restart, failed ? "fail" : "ok"); + } else { + long wait = s->backoff_ms; + s->restart_at_ms = now_ms() + (unsigned long long)wait; + s->state = SU_STOPPED; + su_log("supervised %s: restart in %ldms (attempt %d, exit=%s)", + s->name, wait, s->restart_count, failed ? "fail" : "ok"); + } + } else { + s->state = failed ? SU_FAILED : SU_STOPPED; + su_log("supervised %s: exit (policy=%s, %s) -> %s", + s->name, s->restart, failed ? "fail" : "ok", + failed ? "FAILED" : "STOPPED"); + } +} + +static void sig_child(int sig) +{ + (void)sig; + char c = SYSUI_SIGCHLD_CHAR; + ssize_t w = write(g_sigpipe[1], &c, 1); + (void)w; +} + +static void sig_term(int sig) +{ + (void)sig; + g_shutdown = 1; + char c = SYSUI_SIGTERM_CHAR; + ssize_t w = write(g_sigpipe[1], &c, 1); + (void)w; +} + +static void reap_children(void) +{ + char buf[64]; + while (read(g_sigpipe[0], buf, sizeof buf) > 0) { /* drain */ } + + int status; + pid_t pid; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + int idx = -1; + for (int i = 0; i < g_nsup; i++) + if (g_sups[i].pid == pid) { idx = i; break; } + if (idx < 0) { su_log("reap unknown pid=%d", (int)pid); continue; } + apply_exit(&g_sups[idx], status); + } + build_status_file(); +} + +/* --- 配置扫描 / 调和 --- */ +static int scan_conf_names(const char *dir, char out[][SYSUI_NAME_MAX], int max) +{ + DIR *d = opendir(dir); + if (!d) return 0; + int n = 0; + struct dirent *e; + while ((e = readdir(d)) != NULL && n < max) { + if (e->d_name[0] == '.') continue; + size_t l = strlen(e->d_name); + if (l < 6 || strcmp(e->d_name + l - 5, ".conf") != 0) continue; + size_t nl = l - 5; + if (nl >= SYSUI_NAME_MAX) nl = SYSUI_NAME_MAX - 1; + memcpy(out[n], e->d_name, nl); + out[n][nl] = '\0'; + n++; + } + closedir(d); + return n; +} + +static void scan_and_reconcile(void) +{ + char names[SYSUI_MAX_SUP][SYSUI_NAME_MAX]; + int n = scan_conf_names(g_dir, names, SYSUI_MAX_SUP); + + for (int i = 0; i < g_nsup; i++) g_sups[i].in_config = 0; + + for (int i = 0; i < n; i++) { + char path[512]; + if (snprintf(path, sizeof path, "%s/%s.conf", g_dir, names[i]) >= (int)sizeof path) continue; + int idx = find_sup(names[i]); + if (idx < 0) { + if (g_nsup >= SYSUI_MAX_SUP) { su_log("supervise table full, drop %s", names[i]); continue; } + idx = g_nsup++; + } + sup_t tmp; + if (load_conf(path, names[i], &tmp) != 0) { continue; } + sup_t *s = &g_sups[idx]; + + int config_changed = (strcmp(s->exec, tmp.exec) != 0) + || (s->argc != tmp.argc) || (s->envc != tmp.envc) + || (s->enabled != tmp.enabled) + || (strcmp(s->restart, tmp.restart) != 0) + || (s->backoff_ms != tmp.backoff_ms) || (s->max_restart != tmp.max_restart); + if (!config_changed) { + for (int a = 0; a < tmp.argc; a++) + if (strcmp(s->argv[a + 1], tmp.argv[a + 1]) != 0) { config_changed = 1; break; } + for (int e2 = 0; e2 < tmp.envc; e2++) + if (strcmp(s->envbuf[e2], tmp.envbuf[e2]) != 0) { config_changed = 1; break; } + } + + int keep_count = s->restart_count; + int keep_exit = s->last_exit_status; + int keep_has = s->has_exit; + unsigned long long keep_wall = s->last_exit_wall; + pid_t keep_pid = s->pid; + int keep_stop = s->stop_requested; + int keep_rp = s->restart_pending; + int keep_user = s->user_stopped; + int keep_state = s->state; + *s = tmp; + s->restart_count = keep_count; + s->last_exit_status = keep_exit; + s->has_exit = keep_has; + s->last_exit_wall = keep_wall; + s->pid = keep_pid; + s->stop_requested = keep_stop; + s->restart_pending = keep_rp; + s->user_stopped = keep_user; + s->in_config = 1; + s->restart_at_ms = 0; + + if (keep_pid > 0) { + if (config_changed) { + su_log("supervised %s: config changed, restart", s->name); + stop_sup(s, 1, 0); + } else { + s->state = (keep_state == SU_STOPPING) ? SU_STOPPING : SU_RUNNING; + } + } else { + s->state = SU_STOPPED; + } + } + + /* 已移除配置的条目,倒序删(先停运行中的) */ + for (int i = g_nsup - 1; i >= 0; i--) { + if (g_sups[i].in_config) continue; + if (g_sups[i].pid > 0) stop_sup(&g_sups[i], 0, 0); + su_log("supervised %s: removed (no config)", g_sups[i].name); + remove_sup_idx(i); + } + + /* 禁用:运行中 → 停;未运行 → DISABLED */ + for (int i = 0; i < g_nsup; i++) { + sup_t *s = &g_sups[i]; + if (!s->in_config) continue; + if (!s->enabled) { + if (s->pid > 0) { su_log("supervised %s: disabled -> stop", s->name); stop_sup(s, 0, 0); } + else s->state = SU_DISABLED; + } + } + + /* 曾 FAILED 且现启用 → 重置,交 tick 重试 */ + for (int i = 0; i < g_nsup; i++) { + sup_t *s = &g_sups[i]; + if (s->in_config && s->enabled && s->state == SU_FAILED) { + s->restart_count = 0; + s->state = SU_STOPPED; + s->user_stopped = 0; + } + } + build_status_file(); +} + +static void tick_evaluate(void) +{ + for (int i = 0; i < g_nsup; i++) { + sup_t *s = &g_sups[i]; + if (s->state == SU_STOPPING) { + if (now_ms() - s->stop_since_ms > SYSUI_STOP_GRACE_MS) { + if (s->pid > 0) { kill(s->pid, SIGKILL); s->pid = 0; } + s->state = SU_STOPPED; + su_log("supervised %s: SIGKILL grace timeout", s->name); + } + continue; + } + if (s->pid > 0) { + /* kill(pid,0) 存活探测:子进程已死但尚未收尸(或异常)→ 视同死亡 */ + if (!s->stop_requested && !s->restart_pending && kill(s->pid, 0) != 0 && errno == ESRCH) { + su_log("supervised %s: probe pid=%d dead (ESRCH)", s->name, (int)s->pid); + apply_exit(s, 1 << 8); /* 视为非零退出(fail)走重建策略 */ + } else if (s->state == SU_RUNNING && + now_ms() - s->run_since_ms > SYSUI_STABLE_MS) { + s->restart_count = 0; /* 稳定运行 → 清零计数 */ + } + continue; + } + if (s->state == SU_FAILED || s->state == SU_DISABLED) continue; + + if (s->restart_at_ms > 0) { + if (now_ms() >= s->restart_at_ms) { + s->restart_at_ms = 0; + su_log("supervised %s: backoff done, respawn", s->name); + spawn_sup(s); + } + } else if (s->enabled && !s->user_stopped) { + spawn_sup(s); + } + } +} + +/* ---- 控制通道 ---- */ +static void send_resp(int fd, const char *s) +{ + ssize_t w = write(fd, s, strlen(s)); + (void)w; +} + +static int cmd_start(const char *name) +{ + if (strcmp(name, "all") == 0) { + for (int i = 0; i < g_nsup; i++) { + g_sups[i].user_stopped = 0; + g_sups[i].restart_count = 0; + g_sups[i].state = SU_STOPPED; + spawn_sup(&g_sups[i]); + } + return 0; + } + int idx = find_sup(name); + if (idx < 0) return -1; + sup_t *s = &g_sups[idx]; + s->user_stopped = 0; + if (s->pid <= 0) { s->restart_count = 0; s->state = SU_STOPPED; spawn_sup(s); } + return 0; +} + +static int cmd_stop(const char *name) +{ + if (strcmp(name, "all") == 0) { + for (int i = 0; i < g_nsup; i++) stop_sup(&g_sups[i], 0, 1); + return 0; + } + int idx = find_sup(name); + if (idx < 0) return -1; + stop_sup(&g_sups[idx], 0, 1); + return 0; +} + +static int cmd_restart(const char *name) +{ + if (strcmp(name, "all") == 0) { + for (int i = 0; i < g_nsup; i++) stop_sup(&g_sups[i], 1, 0); + return 0; + } + int idx = find_sup(name); + if (idx < 0) return -1; + sup_t *s = &g_sups[idx]; + s->user_stopped = 0; + if (s->pid <= 0) { s->restart_count = 0; s->state = SU_STOPPED; spawn_sup(s); } + else stop_sup(s, 1, 0); + return 0; +} + +/* ---- status 回显(详细 + ANSI 配色;非 TTY 由客户端去色) ---- */ +static void put_field(char *b, size_t cap, size_t *o, const char *color, const char *plain, int width) +{ + size_t pl = strlen(plain); + int w; + if (color && color[0]) + w = snprintf(b + *o, cap - *o, "%s%s" AN_RESET, color, plain); + else + w = snprintf(b + *o, cap - *o, "%s", plain); + if (w > 0) *o += (size_t)w; + if (*o >= cap) *o = cap - 1; + if (width > 0) { + int pad = width - (int)pl; + while (pad-- > 0 && *o < cap - 1) b[(*o)++] = ' '; + } + b[*o] = '\0'; +} + +static void put_str(char *b, size_t cap, size_t *o, const char *s) +{ + int w = snprintf(b + *o, cap - *o, "%s", s); + if (w > 0) *o += (size_t)w; + if (*o >= cap) *o = cap - 1; + b[*o] = '\0'; +} + +static const char *state_color(int st) +{ + switch (st) { + case SU_RUNNING: return AN_GREEN; + case SU_FAILED: return AN_RED; + case SU_STOPPED: return AN_YELLOW; + case SU_STOPPING: return AN_YELLOW; + case SU_DISABLED: return AN_GREY; + default: return ""; + } +} + +static void fmt_exit_str(const sup_t *s, char *out, size_t cap) +{ + if (!s->has_exit) { snprintf(out, cap, "-"); return; } + int st = s->last_exit_status; + if (WIFEXITED(st)) snprintf(out, cap, "%d", WEXITSTATUS(st)); + else if (WIFSIGNALED(st)) snprintf(out, cap, "sig%d", WTERMSIG(st)); + else snprintf(out, cap, "-"); +} + +static void cmd_status(int fd, const char *name) +{ + int idx = -1; + if (name && strcmp(name, "all") != 0) { + idx = find_sup(name); + if (idx < 0) { send_resp(fd, "ERR no-such-supervised\n"); return; } + } + int shown = (idx >= 0) ? 1 : g_nsup; + + int nw = 4; + for (int i = 0; i < g_nsup; i++) { + if (idx >= 0 && i != idx) continue; + int l = (int)strlen(g_sups[i].name); + if (l > nw) nw = l; + } + if (nw > 40) nw = 40; + + const int sw = 8, pw = 7, rw = 7, bw = 9, ew = 6; + size_t cap = (size_t)(shown + 3) * 512 + 1024; + char *b = malloc(cap); + if (!b) { send_resp(fd, "ERR out-of-memory\n"); return; } + size_t o = 0; + + put_field(b, cap, &o, AN_BOLD AN_CYAN, "meow_sysui status", -1); + put_str(b, cap, &o, "\n"); + char hdr[64]; + snprintf(hdr, sizeof hdr, "supervised=%d\n", g_nsup); + put_str(b, cap, &o, hdr); + + put_field(b, cap, &o, AN_DIM, "NAME", nw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "STATE", sw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "PID", pw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "RESTART", rw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "BACKOFF", bw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "EXIT", ew); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "ENABLED", 8); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_DIM, "EXEC", -1); + put_str(b, cap, &o, "\n"); + + int total = nw + 1 + sw + 1 + pw + 1 + rw + 1 + bw + 1 + ew + 1 + 8 + 1 + 4; + put_str(b, cap, &o, AN_DIM); + for (int x = 0; x < total && o < cap - 1; x++) b[o++] = '-'; + put_str(b, cap, &o, AN_RESET "\n"); + + for (int i = 0; i < g_nsup; i++) { + if (idx >= 0 && i != idx) continue; + sup_t *s = &g_sups[i]; + char pidstr[16], rstr[16], bstr[16], estr[16]; + snprintf(pidstr, sizeof pidstr, "%d", (int)s->pid); + if (s->max_restart < 0) snprintf(rstr, sizeof rstr, "%d/inf", s->restart_count); + else snprintf(rstr, sizeof rstr, "%d/%d", s->restart_count, s->max_restart); + snprintf(bstr, sizeof bstr, "%ldms", s->backoff_ms); + fmt_exit_str(s, estr, sizeof estr); + + put_field(b, cap, &o, AN_BOLD AN_CYAN, s->name, nw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, state_color(s->state), g_state_str[s->state], sw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_YELLOW, pidstr, pw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_YELLOW, rstr, rw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, AN_YELLOW, bstr, bw); put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", estr, ew); put_str(b, cap, &o, " "); + put_field(b, cap, &o, (s->enabled ? AN_GREEN : AN_GREY), (s->enabled ? "yes" : "no"), 8); + put_str(b, cap, &o, " "); + put_field(b, cap, &o, "", s->exec, -1); + put_str(b, cap, &o, "\n"); + } + + size_t sent = 0; + while (sent < o) { + ssize_t w = write(fd, b + sent, o - sent); + if (w <= 0) break; + sent += (size_t)w; + } + free(b); +} + +static void handle_connection(int fd) +{ + char buf[SYSUI_LINE_MAX]; + ssize_t n = read(fd, buf, sizeof buf - 1); + if (n <= 0) { send_resp(fd, "ERR empty\n"); return; } + buf[n] = '\0'; + char *line = trim(buf); + if (!line[0]) { send_resp(fd, "ERR empty\n"); return; } + char *sp = strchr(line, ' '); + if (sp) *sp++ = '\0'; + + if (strcmp(line, "pid") == 0) { + char r[64]; snprintf(r, sizeof r, "%d\n", (int)getpid()); send_resp(fd, r); + } else if (strcmp(line, "status") == 0) { + cmd_status(fd, sp); + } else if (strcmp(line, "reload") == 0) { + scan_and_reconcile(); + send_resp(fd, "OK\n"); + } else if (sp && strcmp(line, "start") == 0) { + send_resp(fd, cmd_start(sp) == 0 ? "OK\n" : "ERR no-such-supervised\n"); + } else if (sp && strcmp(line, "stop") == 0) { + send_resp(fd, cmd_stop(sp) == 0 ? "OK\n" : "ERR no-such-supervised\n"); + } else if (sp && strcmp(line, "restart") == 0) { + send_resp(fd, cmd_restart(sp) == 0 ? "OK\n" : "ERR no-such-supervised\n"); + } else { + send_resp(fd, "ERR unknown-command\n"); + } +} + +/* ---- Daemon 生命周期 ---- */ +static int setup_socket(void) +{ + unlink(g_sock); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { su_log("socket: %s", strerror(errno)); return -1; } + set_cloexec(fd); + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + safe_strcpy(a.sun_path, sizeof a.sun_path, g_sock); + if (bind(fd, (struct sockaddr *)&a, sizeof a) != 0) { + su_log("bind %s: %s", g_sock, strerror(errno)); + close(fd); return -1; + } + if (listen(fd, 4) != 0) { su_log("listen: %s", strerror(errno)); close(fd); return -1; } + g_listen_fd = fd; + return 0; +} + +static int setup_signals(void) +{ + if (pipe(g_sigpipe) != 0) { su_log("pipe: %s", strerror(errno)); return -1; } + int rf = fcntl(g_sigpipe[0], F_GETFL, 0); + if (rf >= 0) fcntl(g_sigpipe[0], F_SETFL, rf | O_NONBLOCK); + int wf = fcntl(g_sigpipe[1], F_GETFL, 0); + if (wf >= 0) fcntl(g_sigpipe[1], F_SETFL, wf | O_NONBLOCK); + set_cloexec(g_sigpipe[0]); + set_cloexec(g_sigpipe[1]); + + struct sigaction sa; + memset(&sa, 0, sizeof sa); + sa.sa_handler = sig_child; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGCHLD, &sa, NULL); + + struct sigaction st; + memset(&st, 0, sizeof st); + st.sa_handler = sig_term; + sigemptyset(&st.sa_mask); + st.sa_flags = 0; + sigaction(SIGTERM, &st, NULL); + sigaction(SIGINT, &st, NULL); + + signal(SIGPIPE, SIG_IGN); + return 0; +} + +static void daemonize(void) +{ + pid_t pid = fork(); + if (pid < 0) { su_log("fork: %s", strerror(errno)); } + if (pid > 0) _exit(0); + if (setsid() < 0) { /* ignore */ } + pid = fork(); + if (pid < 0) { su_log("fork2: %s", strerror(errno)); } + if (pid > 0) _exit(0); + if (chdir("/") != 0) { /* ignore */ } + int nul = open("/dev/null", O_RDWR); + if (nul >= 0) { dup2(nul, 0); dup2(nul, 1); if (nul > 2) close(nul); } + /* 保留 stderr=2(g_log 仍会写文件;stderr 落到 /dev/null 由调用方决定) */ +} + +static void write_pidfile(void) +{ + char buf[32]; + int l = snprintf(buf, sizeof buf, "%d\n", (int)getpid()); + (void)write_file(g_pidfile, buf, (size_t)l); +} + +static void shutdown_all(void) +{ + for (int i = 0; i < g_nsup; i++) { + if (g_sups[i].pid > 0) { + kill(g_sups[i].pid, SIGTERM); + unsigned long long wait_until = now_ms() + 1500; + while (g_sups[i].pid > 0 && now_ms() < wait_until) { + int st; pid_t p = waitpid(g_sups[i].pid, &st, WNOHANG); + if (p == g_sups[i].pid) { g_sups[i].pid = 0; } + else usleep(50000); + } + if (g_sups[i].pid > 0) { kill(g_sups[i].pid, SIGKILL); g_sups[i].pid = 0; } + } + } + build_status_file(); + if (g_listen_fd >= 0) close(g_listen_fd); + if (g_status_fd >= 0) close(g_status_fd); + unlink(g_sock); + unlink(g_pidfile); +} + +static int start_daemon(void) +{ + if (setup_signals() != 0) return 1; + /* 子进程回收者:被监管进程若派生孙进程,在直接子进程退出后由 sysui 收尸,强化「孤儿/僵死隔离」。 */ + if (prctl(PR_SET_CHILD_SUBREAPER, 1) != 0) + su_log("PR_SET_CHILD_SUBREAPER failed: %s (continue)", strerror(errno)); + if (setup_socket() != 0) return 1; + write_pidfile(); + + g_status_fd = open(g_status, O_RDWR | O_CREAT, 0644); + if (g_status_fd < 0) su_log("status file open fail: %s", strerror(errno)); + else set_cloexec(g_status_fd); + + scan_and_reconcile(); + + su_log("meow_sysui up: dir=%s sock=%s pid=%d supervised=%d", + g_dir, g_sock, (int)getpid(), g_nsup); + + struct pollfd pfds[2]; + nfds_t nfds = 2; + + unsigned long long last_tick = 0; + while (!g_shutdown) { + unsigned long long soon = 0; + for (int i = 0; i < g_nsup; i++) { + if (g_sups[i].restart_at_ms > 0) { + unsigned long long d = g_sups[i].restart_at_ms > now_ms() + ? g_sups[i].restart_at_ms - now_ms() : 0; + if (soon == 0 || d < soon) soon = d; + } + } + int timeout = (soon > 0 && soon < (unsigned long long)SYSUI_TICK_MAX_MS) + ? (int)soon : SYSUI_TICK_MAX_MS; + if (timeout < 20) timeout = 20; + + pfds[0].fd = g_listen_fd; pfds[0].events = POLLIN; pfds[0].revents = 0; + pfds[1].fd = g_sigpipe[0]; pfds[1].events = POLLIN; pfds[1].revents = 0; + + int r = poll(pfds, nfds, timeout); + if (r < 0) { + if (errno == EINTR) continue; + su_log("poll: %s", strerror(errno)); + break; + } + if (g_shutdown) break; + + if (pfds[0].revents & POLLIN) { + int cfd = accept(g_listen_fd, NULL, NULL); + if (cfd >= 0) { + set_cloexec(cfd); + handle_connection(cfd); + close(cfd); + } + } + if (pfds[1].revents & POLLIN) reap_children(); + + if (now_ms() - last_tick >= (unsigned long long)SYSUI_TICK_MAX_MS) { + last_tick = now_ms(); + /* 仅做监护评估(tick_evaluate),不在此处定期 scan:scan_and_reconcile 会把 + * FAILED(超 max_restart) 重置回 STOPPED,若每 tick 调会导致「无限次重试」, + * 破坏「有限次兜底」。配置扫描只在 启动 + reload 命令 执行(同 svcd)。 */ + } + tick_evaluate(); + build_status_file(); + } + + su_log("meow_sysui shutdown"); + shutdown_all(); + return 0; +} + +/* ---- CLI ---- */ +static char *strip_ansi(char *s) +{ + char *r = s, *w = s; + while (*r) { + if (*r == '\033' && r[1] == '[') { + r += 2; + while (*r && !(*r >= '@' && *r <= '~')) r++; + if (*r) r++; + } else { + *w++ = *r++; + } + } + *w = '\0'; + return s; +} + +static int run_client(const char *cmdline) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { fprintf(stderr, "[meow_sysui] socket: %s\n", strerror(errno)); return 2; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + safe_strcpy(a.sun_path, sizeof a.sun_path, g_sock); + if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { + fprintf(stderr, "[meow_sysui] daemon not running (%s): %s\n", g_sock, strerror(errno)); + close(fd); + char *buf = malloc(8192); + if (buf && read_file(g_status, buf, 8192) > 0) { fputs(buf, stdout); free(buf); } + else if (buf) { free(buf); printf("supervised=0\n"); } + return 1; + } + ssize_t w = write(fd, cmdline, strlen(cmdline)); + (void)w; + shutdown(fd, SHUT_WR); + + size_t cap = 65536, len = 0; + char *rbuf = malloc(cap); + if (!rbuf) { close(fd); return 2; } + ssize_t n; + while ((n = read(fd, rbuf + len, cap - len - 1)) > 0) { + len += (size_t)n; + if (len >= cap - 1) break; + } + close(fd); + rbuf[len] = '\0'; + if (!isatty(STDOUT_FILENO)) strip_ansi(rbuf); + fputs(rbuf, stdout); + free(rbuf); + return 0; +} + +static void usage(const char *argv0) +{ + fprintf(stderr, + "usage: %s [options] [command]\n" + " commands: start | stop | restart \n" + " | status [name|all] | reload | pid\n" + " no command -> start daemon (default; -f/--foreground keeps fg for init)\n" + " options:\n" + " -f, --foreground stay in foreground\n" + " --dir supervised config dir (default /usr/data/meow/sysui.d)\n" + " --sock control socket (default /tmp/meow_sysui.sock)\n" + " --status status file (default /tmp/meow_sysui.status)\n" + " --pidfile pid file (default /tmp/meow_sysui.pid)\n" + " --log also append log to (default /tmp/meow_sysui.log)\n", + argv0); +} + +int main(int argc, char **argv) +{ + const char *cmd_tok[16]; + int cmd_n = 0; + for (int i = 1; i < argc; i++) { + const char *a = argv[i]; + if (strcmp(a, "-f") == 0 || strcmp(a, "--foreground") == 0) g_fg = 1; + else if (strcmp(a, "--dir") == 0 && i + 1 < argc) safe_strcpy(g_dir, sizeof g_dir, argv[++i]); + else if (strcmp(a, "--sock") == 0 && i + 1 < argc) safe_strcpy(g_sock, sizeof g_sock, argv[++i]); + else if (strcmp(a, "--status") == 0 && i + 1 < argc) safe_strcpy(g_status, sizeof g_status, argv[++i]); + else if (strcmp(a, "--pidfile") == 0 && i + 1 < argc) safe_strcpy(g_pidfile, sizeof g_pidfile, argv[++i]); + else if (strcmp(a, "--log") == 0 && i + 1 < argc) safe_strcpy(g_log, sizeof g_log, argv[++i]); + else if (a[0] == '-') { usage(argv[0]); return 2; } + else if (cmd_n < 16) cmd_tok[cmd_n++] = a; + } + + setvbuf(stderr, NULL, _IONBF, 0); + + if (cmd_n > 0) { + char buf[SYSUI_LINE_MAX]; + buf[0] = '\0'; + size_t off = 0; + for (int i = 0; i < cmd_n; i++) { + int k = snprintf(buf + off, sizeof buf - off, "%s%s", i ? " " : "", cmd_tok[i]); + if (k < 0 || (size_t)k >= sizeof buf - off) break; + off += (size_t)k; + } + return run_client(buf); + } + + if (!g_fg) daemonize(); + return start_daemon(); +} diff --git a/patch_v2/meow_sysui/meow_sysui.md b/patch_v2/meow_sysui/meow_sysui.md new file mode 100644 index 0000000..4281dff --- /dev/null +++ b/patch_v2/meow_sysui/meow_sysui.md @@ -0,0 +1,130 @@ +# meow_sysui — MeowOS 系统进程总监 + +系统进程**总监**:监管系统进程(launcher / meow_svcd / meow_mdns / 未来媒体服务 / MeowPanel), +**一个崩 → 只拉起它,绝不级联、绝不拖垮自身**。自身**最小依赖、不渲染、不承载重逻辑** → 最稳。 +由开机启动链 `etc/init.d/S70meow_sysui` 拉起(早于 S89meow_svcd),居顶层监管。 + +权威锚点:`FRAMEWORK_DESIGN §10.1`(进程角色/Supervisor 树)、`docs/MEOWOS_SPEC_REF §4`、 +`devdocs/plan/SYS_BASELINE.md`(本线方案)。 + +二进制入口:`patch_v2/meow_sysui/meow_sysui`(mipsel 静态,~113KB,<200KB 固件阈值)。 +宿主编译:`patch_v2/meow_sysui/meow_sysui_host`。 + +--- + +## 1. 定位与监管模型 + +- **只监护自己 fork+exec 出来的进程**:`waitpid` 需为父进程,这是「一个崩只拉起它」的可靠前提; + sysui **不做**「adopt 外部 pid」式监护(那只 probe `kill(pid,0)`、无法真正 waitpid/重启)。 +- 要监管什么由配置决定;**默认配置只开启 `meow_svcd`**(符合定案「sysui 总监 → svcd 被监管 → + 服务为 svcd 子进程」)。`launcher` 当前由 S90meow_bootmenu 的 `exec` 拥有,配置中登记但 + `enabled=0`(避免双重拉起;真正移交需 bootmenu 让位,见 §6)。 +- `meow_mdns` 是 svcd 的服务子进程(svcd 负责 spawn/restart),sysui 本阶段**不直接监管**它 + (避免与 svcd 双重管理);`§10.1` 的「mdns 崩→svcd 拉起(或 sysui 兜底)」——兜底可后续 + 在 `sysui.d/` 加一条 meow_mdns 条目实现(默认关)。 + +## 2. 配置:`/usr/data/meow/sysui.d/.conf`(板载权威,目录式) + +复用 services.d 语义:一个被监管进程一个 `.conf`,`key=value`,`#`/`;` 注释,未知键忽略(前向兼容)。 + +| 字段 | 必填 | 说明 | 默认 | +|---|---|---|---| +| `exec` | ✅ | 被监管二进制绝对路径 | 无(缺省 → 跳过该条目) | +| `args` | 否 | 空格切分,支持单/双引号与 `\` 转义 | 无 | +| `env` | 否 | `K=V`,可多条;exec 前 `setenv` | 无 | +| `enabled` | 否 | `1`=监护该进程;`0`=只登记不拉起 | 0 | +| `restart` | 否 | `always`/`on-fail`/`never` | `on-fail` | +| `backoff_ms` | 否 | 崩溃后重启退避毫秒(固定) | 2000 | +| `max_restart` | 否 | 连续重启上限,超限 → `failed` 停止自动重启;`-1`=无限 | 5 | + +> **无 `trigger`**:sysui 只管**常驻系统进程**(launcher/svcd/mdns),`enabled=1` 即拉起/常驻监管, +> 拔卡无关(系统进程不认卡)。卡相关服务归 svcd 的 `services.d`。 +> TF 卡 `MeowOS/sysui.d/` 作为扩展点本阶段不读(代码只读板载,最小化)。 + +示例(`svcd.conf`,由 S70 首启预置): + +```ini +# 系统进程总监 → svcd 被监管 → 服务为 svcd 子进程 +exec = /usr/bin/meow_svcd +args = -f +enabled = 1 +restart = always +backoff_ms = 3000 +max_restart = 5 +``` + +## 3. 状态机 + +``` +DISABLED enabled=0,永不自动拉起 +STOPPED 未运行(被停 / 等待退避) +RUNNING 子进程存活 +STOPPING 已发 SIGTERM,等退出(SYSUI_STOP_GRACE_MS=2500 后 SIGKILL 兜底) +FAILED 反复崩/起不来且超 max_restart,停止自动重启(有限次兜底) +``` + +转换(主循环事件驱动 + 周期 tick,同 svcd): + +- **子进程死亡**(`waitpid` 匹配):无 `stop_requested`/`restart_pending` → 按该条目 `restart` + 策略决定重建(先进 `backoff_ms` 退避)或落 `FAILED`/`STOPPED`;稳定运行 `SYSUI_STABLE_MS=30s` + 清零 `restart_count`。**只处理该条目,无级联。** +- **`kill(pid,0)` 存活探测**:`tick` 里对 `RUNNING` 条目做 `kill(pid,0)`;若 `ESRCH`(子进程已死 + 未收尸/异常)→ 视同死亡走重建策略(对应「kill(pid,0)/waitpid 探测」定案)。 +- **`stop` 命令**:`STOPPING → STOPPED`(置 `user_stopped`,抑制自动拉起)。 +- **`restart` 命令**:收尸后原地 respawn(清零计数)。 +- **配置变更**:`reload` 命令重读 `sysui.d/`;新增→监护,移除→停,配置变更→重启该条。 + +## 4. 崩溃隔离 / 护固件(防开不了机) + +- **独立进程**:每个被监管进程 `fork()+execv()` + `setpgid(0,0)`,sysui 只经 `waitpid`/`SIGCHLD` + 感知;无中间层内存共享、无 dlopen。 +- **一个崩不全部崩**:`reap_children()` 只命中死掉的那条;重启只作用于该条;主循环绝不在子进程 + 死亡时 `exit`。 +- **有限次兜底**:某进程起不来/反复崩 → `restart_count++`,超 `max_restart` → `FAILED` **停止自动重启**, + 仅记日志(`sysui: exceeded max_restart -> FAILED`);绝不崩溃-重启热循环拖垮系统。 + 重新尝试需 `reload`/配置变更(`scan_and_reconcile` 仅在启动与 reload 执行——**不在每 tick 调用**, + 否则会每秒重置 FAILED、破坏「有限次」语义)。 +- **自身不死**:主循环只在 `SIGTERM/SIGINT` 退出;`SIGPIPE` 置 `SIG_IGN`;poll `EINTR` 续跑。 +- **空配置可跑**:无 `sysui.d/` 或全 `enabled=0` → 空转存活(只写日志/status),不报错、不阻塞后续 S84/S89/S90。 +- **子进程回收者**:`PR_SET_CHILD_SUBREAPER`——被监管进程若派生孙进程,在直接子进程退出后由 sysui + 收尸而非 reparent 到 init,强化「孤儿/僵死隔离」(失败仅日志,不硬失败)。 + +## 5. CLI / 控制协议 / 日志 + +守护默认后台常驻(无子命令即启动);`-f/--foreground` 供 init/调试。控制走 AF_UNIX 单行文本 +(默认 `/tmp/meow_sysui.sock`): + +``` +meow_sysui status [name|all] # 列出各被监管进程 + 全局(daemon 未运行则打印状态文件) +meow_sysui reload # 重读 sysui.d/ +meow_sysui start +meow_sysui stop +meow_sysui restart +meow_sysui pid +``` + +- 状态文件 `/tmp/meow_sysui.status`;pidfile `/tmp/meow_sysui.pid`。 +- status 回显:每条目 服务名/状态/pid/重启次数+max_restart/backoff_ms/最近退出码/是否启用/exec; + TTY 带 ANSI 配色(表头 dim、服务名加粗青、RUNNING=绿、FAILED=红、STOPPED·STOPPING=黄、DISABLED=灰), + 非 TTY 自动去色。 +- 日志:stderr(`-f`)+ 默认追加 `/tmp/meow_sysui.log` + `syslog(LOG_DAEMON)` 尝试。 + +## 6. 迁移说明 / 边界(诚实标注) + +- **launcher 监管**:现网 launcher 由 `S90meow_bootmenu` 的 `exec` 拉起(bootmenu 拥有其生命周期)。 + sysui **本阶段不拉起 launcher**(`sysui.d/launcher.conf` 默认 `enabled=0` + 注释); + launcher 真正归 sysui 需 bootmenu 让位(后续迁移)。sysui 代码完全支持加一条 launcher 条目即可。 +- **mdns 监管**:由 svcd 负责;sysui 兜底条目默认关。 + +## 7. 编译 + +```bash +./build_sysui.sh # mipsel 静态 → meow_sysui(0 warning) +./build_sysui.sh host # 宿主编译 → meow_sysui_host(集成测试) +``` + +## 后续(不在本阶段) + +- launcher 正式移交 sysui 监管(依赖 bootmenu 让位)。 +- `MeowOS/sysui.d/` TF 双源扩展(复用 svcd 板载优先语义)。 +- 与 meow_watchdog / 崩溃捕获的职责对齐(见 WORK_STATE SDK 缺失清单 #5)。