/* SPDX-License-Identifier: ISC */ #include "init.h" /* service configurations */ static service_list_t cfg; /* service run time data, sorted by target and topological order */ static svc_run_data_t *rt_data = NULL; static size_t rt_count = 0; /* maps a target to range in rt_data */ static size_t queue_start[TGT_MAX]; static size_t queue_count[TGT_MAX]; /* current state */ static size_t queue_idx = 0; static int target = TGT_BOOT; static size_t singleshot = 0; /*****************************************************************************/ static const char *status_str[] = { [STATE_OFF] = NULL, [STATE_QUEUED] = NULL, [STATE_RUNNING] ="\033[22;33m UP \033[0m", [STATE_COMPLETED] = "\033[22;32mDONE\033[0m", [STATE_FAILED] ="\033[22;31mFAIL\033[0m", }; static void print_status(const char *msg, int type) { printf("[%s] %s\n", status_str[type], msg); fflush(stdout); } static void respawn(svc_run_data_t *rt) { if (rt->svc->rspwn_limit > 0) { rt->rspwn_count += 1; if (rt->rspwn_count >= rt->svc->rspwn_limit) goto fail; } rt->pid = runsvc(rt->svc); if (rt->pid == -1) goto fail; rt->state = STATE_RUNNING; return; fail: rt->state = STATE_FAILED; print_status(rt->svc->desc, rt->state); return; } static void check_target_completion(void) { if (singleshot > 0 || queue_idx < queue_count[target]) return; switch (target) { case TGT_BOOT: break; case TGT_SHUTDOWN: for (;;) reboot(RB_POWER_OFF); break; case TGT_REBOOT: for (;;) reboot(RB_AUTOBOOT); break; } } void supervisor_handle_exited(pid_t pid, int status) { svc_run_data_t *rt; service_t *svc; size_t i; for (i = 0; i < rt_count; ++i) { if (rt_data[i].pid == pid) break; } if (i >= rt_count) return; rt = rt_data + i; svc = rt->svc; rt->status = status; rt->pid = -1; if (svc->type == SVC_RESPAWN) { if (target != TGT_REBOOT && target != TGT_SHUTDOWN) respawn(rt); } else { if (rt->status == EXIT_SUCCESS) { rt->state = STATE_COMPLETED; } else { rt->state = STATE_FAILED; } print_status(svc->desc, rt->state); if (svc->type == SVC_ONCE) singleshot -= 1; check_target_completion(); } } void supervisor_set_target(int next) { if (target == TGT_REBOOT || target == TGT_SHUTDOWN || next == target) return; if (queue_idx < queue_count[target]) { if (next != TGT_REBOOT && next != TGT_SHUTDOWN) return; } target = next; queue_idx = 0; } void supervisor_init(void) { int status = STATE_FAILED; service_t *it; size_t i, j; if (svcscan(SVCDIR, &cfg)) goto out; /* allocate run time data */ rt_count = 0; for (i = 0; i < TGT_MAX; ++i) { for (it = cfg.targets[i]; it != NULL; it = it->next) ++rt_count; } rt_data = calloc(rt_count, sizeof(rt_data[0])); if (rt_data == NULL) { rt_count = 0; goto out; } /* map runtime data to services */ j = 0; for (i = 0; i < TGT_MAX; ++i) { queue_start[i] = j; for (it = cfg.targets[i]; it != NULL; it = it->next) { rt_data[j].svc = it; rt_data[j].state = STATE_OFF; rt_data[j].pid = -1; ++j; } queue_count[i] = j - queue_start[i]; } /* initialize state */ status = STATE_COMPLETED; for (i = 0; i < queue_count[target]; ++i) rt_data[queue_start[target] + i].state = STATE_QUEUED; out: print_status("reading configuration from " SVCDIR, status); } static pid_t wait_for_process(int *status) { pid_t pid = wait(status); if (pid == -1 || !WIFEXITED(*status)) { *status = EXIT_FAILURE; } else { *status = WEXITSTATUS(*status); } return pid; } void supervisor_process_queues(void) { svc_run_data_t *rt; service_t *svc; size_t count; int status; pid_t pid; count = queue_count[target]; if (queue_idx >= count) { pid = wait_for_process(&status); supervisor_handle_exited(pid, status); return; } rt = rt_data + queue_start[target] + queue_idx++; svc = rt->svc; if (svc->flags & SVC_FLAG_HAS_EXEC) { rt->pid = runsvc(rt->svc); if (rt->pid == -1) { rt->state = STATE_FAILED; print_status(svc->desc, rt->state); } else { rt->state = STATE_RUNNING; print_status(svc->desc, rt->state); switch (svc->type) { case SVC_WAIT: for (;;) { pid = wait_for_process(&status); if (pid == rt->pid) break; supervisor_handle_exited(pid, status); } rt->state = status == EXIT_SUCCESS ? STATE_COMPLETED : STATE_FAILED; print_status(svc->desc, rt->state); break; case SVC_ONCE: singleshot += 1; break; } } } else { rt->status = EXIT_SUCCESS; rt->state = STATE_COMPLETED; print_status(svc->desc, rt->state); } check_target_completion(); }