meow_svcd status 美化(117) + 按钮按住/点击样式+滑条黑残快照(118)
This commit is contained in:
1 parent
19a001f007
commit
07c556e907
7 files changed
+352
-43
No files matched your search
Binary file not shown.
+202
-21
@@ -27,10 +27,16 @@
|
||||
* - boot:配置在卡 → 随卡存在即启动(拔卡即停)。
|
||||
*
|
||||
* 控制通道:AF_UNIX 单行文本协议(默认 /tmp/meow_svcd.sock):
|
||||
* start|stop|restart <name|all> / status / reload / pid
|
||||
* start|stop|restart <name|all> / status [name|all] / reload / pid
|
||||
* 后台常驻模式默认(无子命令即启动守护进程);`-f/--foreground` 供 supervisor
|
||||
* fork/exec 托管(保证 supervisor 可 waitpid)。
|
||||
*
|
||||
* status 回显(详细 + ANSI 配色):
|
||||
* 每服务列出 状态/pid/重启次数+max_restart/backoff_ms/trigger/来源(板载|TF)/
|
||||
* 最近退出码/退出时间/exec 路径;标题+列名+对齐;关键字段着色(RUNNING=绿、
|
||||
* FAILED=红、STOPPED·STOPPING=黄、DISABLED=灰、服务名=青、数字=黄、表头=dim)。
|
||||
* ANSI 色由守护进程统一产出,客户端在 stdout 非 TTY(重定向/SSH 管道)时去色。
|
||||
*
|
||||
* 状态文件:`/tmp/meow_svcd.status`(可 --status 覆盖),状态变更+周期落盘,
|
||||
* 板载 tmpfs 存活期内可读。
|
||||
*
|
||||
@@ -91,6 +97,16 @@
|
||||
#define SVCD_SIGCHLD_CHAR 'C' /* 自管道字节(拉起 poll) */
|
||||
#define SVCD_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 {
|
||||
SV_DISABLED = 0, /* enabled=0,永不自动启动 */
|
||||
@@ -124,6 +140,8 @@ typedef struct {
|
||||
sv_state_t state;
|
||||
int restart_count;
|
||||
int last_exit_status; /* waitpid status */
|
||||
int has_exit; /* 1=曾退出(last_exit_* 有效) */
|
||||
unsigned long long last_exit_wall; /* 上次退出墙钟秒(0=未知;仅供 status 展示) */
|
||||
unsigned long long restart_at_ms; /* 退避到期时间;0=无到期 */
|
||||
unsigned long long stop_since_ms; /* SIGTERM 发出时刻 */
|
||||
unsigned long long run_since_ms; /* 本轮启动时刻 */
|
||||
@@ -433,6 +451,8 @@ static void apply_exit(svc_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) {
|
||||
/* restart 命令:停后原地重启,无视崩溃策略 */
|
||||
@@ -680,6 +700,8 @@ static void scan_and_reconcile(void)
|
||||
/* 保留运行期字段,其余用 tmp 覆盖 */
|
||||
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;
|
||||
@@ -688,6 +710,8 @@ static void scan_and_reconcile(void)
|
||||
*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;
|
||||
@@ -841,22 +865,153 @@ static int cmd_restart(const char *name)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cmd_status(int fd)
|
||||
/* ---- status 回显(详细 + ANSI 配色;非 TTY 由客户端去色) ---- */
|
||||
|
||||
/* 追加一个字段:可选上色(color 包裹 plain + 复位),再按纯文本宽度补空格对齐。
|
||||
* width<0 表示最后一列(不补齐)。b 为累积缓冲,*o 为偏移。 */
|
||||
static void put_field(char *b, size_t cap, size_t *o, const char *color, const char *plain, int width)
|
||||
{
|
||||
send_resp(fd, "# meow_svcd status\n");
|
||||
char line[384];
|
||||
int k = snprintf(line, sizeof line, "card_present=%d wifi_up=%d services=%d\n",
|
||||
g_card_present, g_wifi_up, g_nsvc);
|
||||
send_resp(fd, line);
|
||||
(void)k;
|
||||
for (int i = 0; i < g_nsvc; i++) {
|
||||
svc_t *s = &g_svcs[i];
|
||||
snprintf(line, sizeof line,
|
||||
"svc=%.63s state=%.16s pid=%d restart=%d exit=%d trigger=%.15s\n",
|
||||
s->name, g_state_str[s->state], (int)s->pid, s->restart_count,
|
||||
s->last_exit_status, s->trigger);
|
||||
send_resp(fd, line);
|
||||
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; /* 截断保护: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';
|
||||
}
|
||||
|
||||
/* 状态 → 关键色:RUNNING=绿 / FAILED=红 / STOPPED·STOPPING=黄 / DISABLED=灰。 */
|
||||
static const char *state_color(int st)
|
||||
{
|
||||
switch (st) {
|
||||
case SV_RUNNING: return AN_GREEN;
|
||||
case SV_FAILED: return AN_RED;
|
||||
case SV_STOPPED: return AN_YELLOW;
|
||||
case SV_STOPPING: return AN_YELLOW;
|
||||
case SV_DISABLED: return AN_GREY;
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
/* 解码 waitpid status 为短串:exit=N / sig=N / "-"(未曾退出)。 */
|
||||
static void fmt_exit_str(const svc_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, "-");
|
||||
}
|
||||
|
||||
/* 上次退出时间 HH:MM:SS;无则 "-"。 */
|
||||
static void fmt_exit_time(const svc_t *s, char *out, size_t cap)
|
||||
{
|
||||
if (!s->has_exit || s->last_exit_wall == 0) { snprintf(out, cap, "-"); return; }
|
||||
time_t t = (time_t)s->last_exit_wall;
|
||||
struct tm tm;
|
||||
if (!localtime_r(&t, &tm)) { snprintf(out, cap, "-"); return; }
|
||||
strftime(out, cap, "%H:%M:%S", &tm);
|
||||
}
|
||||
|
||||
/* 回显状态:`status`(全部+全局)或 `status <name|all>`(单服务)。 */
|
||||
static void cmd_status(int fd, const char *name)
|
||||
{
|
||||
int idx = -1; /* -1=全部 */
|
||||
if (name && strcmp(name, "all") != 0) {
|
||||
idx = find_svc(name);
|
||||
if (idx < 0) { send_resp(fd, "ERR no-such-service\n"); return; }
|
||||
}
|
||||
int shown = (idx >= 0) ? 1 : g_nsvc;
|
||||
|
||||
/* NAME 列宽(按纯文本计) */
|
||||
int nw = 4; /* 至少等于 "NAME" */
|
||||
for (int i = 0; i < g_nsvc; i++) {
|
||||
if (idx >= 0 && i != idx) continue;
|
||||
int l = (int)strlen(g_svcs[i].name);
|
||||
if (l > nw) nw = l;
|
||||
}
|
||||
if (nw > 40) nw = 40; /* 防超宽 */
|
||||
|
||||
const int sw = 8, pw = 7, rw = 7, bw = 8, tw = 11, scw = 5, ew = 6, etw = 9;
|
||||
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;
|
||||
|
||||
/* 标题 + 全局汇总(services= 恒为已知总数) */
|
||||
put_field(b, cap, &o, AN_BOLD AN_CYAN, "meow_svcd status", -1);
|
||||
put_str(b, cap, &o, "\n");
|
||||
char hdr[64];
|
||||
snprintf(hdr, sizeof hdr, "card_present=%d wifi_up=%d services=%d\n",
|
||||
g_card_present, g_wifi_up, g_nsvc);
|
||||
put_str(b, cap, &o, hdr);
|
||||
|
||||
/* 表头(dim) */
|
||||
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, "TRIGGER", tw); put_str(b, cap, &o, " ");
|
||||
put_field(b, cap, &o, AN_DIM, "SRC", scw); 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, "EXIT_TIME", etw); put_str(b, cap, &o, " ");
|
||||
put_field(b, cap, &o, AN_DIM, "EXEC", -1);
|
||||
put_str(b, cap, &o, "\n");
|
||||
|
||||
/* 分隔线(dim) */
|
||||
int total = nw + 1 + sw + 1 + pw + 1 + rw + 1 + bw + 1 + tw + 1 + scw + 1 + ew + 1 + etw + 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_nsvc; i++) {
|
||||
if (idx >= 0 && i != idx) continue;
|
||||
svc_t *s = &g_svcs[i];
|
||||
char pidstr[16], rstr[16], bstr[16], estr[16], etstr[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);
|
||||
fmt_exit_time(s, etstr, sizeof etstr);
|
||||
|
||||
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, "", s->trigger, tw); put_str(b, cap, &o, " ");
|
||||
put_field(b, cap, &o, "", (s->src == 1) ? "board" : "tf", scw); put_str(b, cap, &o, " ");
|
||||
put_field(b, cap, &o, "", estr, ew); put_str(b, cap, &o, " ");
|
||||
put_field(b, cap, &o, "", etstr, etw); put_str(b, cap, &o, " ");
|
||||
put_field(b, cap, &o, "", s->exec, -1);
|
||||
put_str(b, cap, &o, "\n");
|
||||
}
|
||||
|
||||
/* 分块写回(避免单次大 write 阻塞/截断) */
|
||||
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);
|
||||
}
|
||||
|
||||
/* 读一条命令并分发。返回响应内容(静态/栈缓冲由调用方使用)。 */
|
||||
@@ -874,7 +1029,7 @@ static void handle_connection(int fd)
|
||||
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);
|
||||
cmd_status(fd, sp); /* sp=NULL=全部;<name|all>=单服务 */
|
||||
} else if (strcmp(line, "reload") == 0) {
|
||||
scan_and_reconcile();
|
||||
send_resp(fd, "OK\n");
|
||||
@@ -1069,6 +1224,24 @@ static int start_daemon(void)
|
||||
}
|
||||
|
||||
/* ---- CLI ---- */
|
||||
|
||||
/* 就地去除 ANSI 转义序列(\033[...<final>);用于 stdout 非 TTY 时去色。 */
|
||||
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);
|
||||
@@ -1089,13 +1262,21 @@ static int run_client(const char *cmdline)
|
||||
ssize_t w = write(fd, cmdline, strlen(cmdline));
|
||||
(void)w;
|
||||
shutdown(fd, SHUT_WR);
|
||||
char rbuf[8192];
|
||||
|
||||
/* 累积完整响应(可能含 ANSI 色码),再按 stdout 是否 TTY 决定去色:非 TTY/重定向去色。 */
|
||||
size_t cap = 65536, len = 0;
|
||||
char *rbuf = malloc(cap);
|
||||
if (!rbuf) { close(fd); return 2; }
|
||||
ssize_t n;
|
||||
while ((n = read(fd, rbuf, sizeof rbuf - 1)) > 0) {
|
||||
rbuf[n] = '\0';
|
||||
fputs(rbuf, stdout);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1104,7 +1285,7 @@ static void usage(const char *argv0)
|
||||
fprintf(stderr,
|
||||
"usage: %s [options] [command]\n"
|
||||
" commands: start <name|all> | stop <name|all> | restart <name|all>\n"
|
||||
" | status | reload | pid\n"
|
||||
" | status [name|all] | reload | pid\n"
|
||||
" no command -> start daemon (default; -f/--foreground keeps fg for supervisor)\n"
|
||||
" options:\n"
|
||||
" -f, --foreground stay in foreground (supervisor fork/exec 托管)\n"
|
||||
|
||||
@@ -148,7 +148,7 @@ FAILED 崩溃且不再重启(超 max_restart / restart=never)
|
||||
AF_UNIX 单行文本:
|
||||
|
||||
```
|
||||
meow_svcd status # 列出(daemon 未运行则打印状态文件)
|
||||
meow_svcd status [name|all] # 列出各服务+全局(daemon 未运行则打印状态文件)
|
||||
meow_svcd reload # 重读卡配置
|
||||
meow_svcd start <name|all>
|
||||
meow_svcd stop <name|all>
|
||||
@@ -156,19 +156,26 @@ meow_svcd restart <name|all>
|
||||
meow_svcd pid
|
||||
```
|
||||
|
||||
应答:`OK` / `ERR no-such-service` / 多行 `svc=... state=... pid=... restart=... exit=...`。
|
||||
应答:`OK` / `ERR no-such-service` / 多列对齐表格。每服务一行:服务名/状态/pid/
|
||||
重启次数+`max_restart`/`backoff_ms`/`trigger`/来源(`board`|`tf`)/最近退出码/退出时间/`exec` 路径;
|
||||
TTY 时带 ANSI 配色(服务名=加粗青、RUNNING=绿、FAILED=红、STOPPED·STOPPING=黄、DISABLED=灰、
|
||||
数字=黄、表头/分隔线=dim),stdout 非 TTY(重定向/SSH 管道)自动去色。
|
||||
|
||||
### 6.1 `systemctl` 式 CLI 用法(SSH 终端示例)
|
||||
|
||||
守护已由固件 `etc/init.d/S89meow_svcd` 开机自启,SSH 登录设备后**直接敲**命令即可,无需先知道 socket:
|
||||
|
||||
```bash
|
||||
meow_svcd status # 列出所有服务 + 全局状态
|
||||
# # meow_svcd status
|
||||
meow_svcd status # 列出所有服务 + 全局状态(TTY 带色;管道/重定向自动去色)
|
||||
# meow_svcd status
|
||||
# card_present=1 wifi_up=0 services=3
|
||||
# svc=mdnsd state=running pid=1234 restart=0 exit=0 trigger=boot
|
||||
# svc=meow_netd state=stopped pid=0 restart=0 exit=0 trigger=card-mount
|
||||
# svc=ntpc state=stopped pid=0 restart=0 exit=0 trigger=card-mount
|
||||
# NAME STATE PID RESTART BACKOFF TRIGGER SRC EXIT EXIT_TIME EXEC
|
||||
# ------------------------------------------------------------------------------
|
||||
# mdnsd running 1234 0/5 2000ms boot board - - /usr/bin/meow_mdns
|
||||
# meow_netd stopped 0 0/5 1000ms card-mount tf - - /usr/bin/meow_netd
|
||||
# ntpc disabled 0 0/inf 1000ms card-mount tf - - /usr/bin/ntpc
|
||||
|
||||
meow_svcd status mdnsd # 只显示单个服务(其余列同;`status all` 等效于无参)
|
||||
|
||||
meow_svcd restart mdnsd # 重启单个服务(如板载 mDNS 应答器)
|
||||
meow_svcd start meow_netd # 启动单个服务
|
||||
@@ -181,8 +188,8 @@ meow_svcd pid # 打印守护进程 pid
|
||||
守护**未运行**时命令回退打印状态文件 `/tmp/meow_svcd.status`(或 `card_present=0 services=0`)并以退出码 1 返回。
|
||||
- **守护前台**:`-f`/`--foreground` —— 守护自身跑前台,供 supervisor `fork/exec`+`waitpid` 托管;
|
||||
默认(无子命令)则为后台常驻。
|
||||
- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`svc=... state=...` 行
|
||||
含 `pid/restart(重启计数)/exit(上次退出码)/trigger`。
|
||||
- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`status` 返回对齐表格,
|
||||
行内含 `state/pid/restart(重启计数)/exit(上次退出码)/trigger/来源/backoff_ms/exec`。
|
||||
|
||||
## 7. 编译
|
||||
|
||||
|
||||
Reference in new issue
Block a user