/* 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; static int target = -1; static size_t singleshot = 0; static bool waiting = false; /*****************************************************************************/ enum { STATUS_OK = 0, STATUS_FAIL, STATUS_WAIT, STATUS_STARTED, }; static const char *status_str[] = { [STATUS_OK] = "\033[22;32m OK \033[0m", [STATUS_FAIL] ="\033[22;31mFAIL\033[0m", [STATUS_WAIT] = "\033[22;33m .. \033[0m", [STATUS_STARTED] ="\033[22;32m UP \033[0m", }; static void print_status(const char *msg, int type, bool update) { if (update) fputc('\r', stdout); printf("[%s] %s", status_str[type], msg); if (type != STATUS_WAIT) fputc('\n', stdout); fflush(stdout); } static svc_run_data_t *run_time_data_from_pid(pid_t pid) { size_t i; for (i = 0; i < rt_count; ++i) { if (rt_data[i].pid == pid) return rt_data + i; } return NULL; } 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: print_status(rt->svc->desc, STATUS_FAIL, false); rt->state = STATE_FAILED; return; } void supervisor_handle_exited(pid_t pid, int status) { svc_run_data_t *rt = run_time_data_from_pid(pid); service_t *svc; if (rt == NULL) return; 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; print_status(svc->desc, STATUS_OK, svc->type == SVC_WAIT); } else { rt->state = STATE_FAILED; print_status(svc->desc, STATUS_FAIL, svc->type == SVC_WAIT); } waiting = false; if (svc->type == SVC_ONCE) singleshot -= 1; if (singleshot == 0 && queue_idx >= queue_count[target] && !waiting) target_completed(target); } } 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 = STATUS_FAIL; 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) { status = STATUS_FAIL; 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 */ singleshot = 0; queue_idx = 0; waiting = false; target = TGT_BOOT; status = STATUS_OK; 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, false); } bool supervisor_process_queues(void) { sigset_t mask, old_mask; svc_run_data_t *rt; service_t *svc; size_t count; bool ret = false; sigfillset(&mask); sigprocmask(SIG_SETMASK, &mask, &old_mask); if (waiting) goto out_unblock; count = queue_count[target]; if (queue_idx >= count) goto out_unblock; rt = rt_data + queue_start[target] + queue_idx++; svc = rt->svc; ret = true; if (svc->flags & SVC_FLAG_HAS_EXEC) { rt->pid = runsvc(rt->svc); if (rt->pid == -1) { rt->state = STATE_FAILED; print_status(rt->svc->desc, STATUS_FAIL, false); } else { rt->state = STATE_RUNNING; switch (svc->type) { case SVC_WAIT: print_status(svc->desc, STATUS_WAIT, false); waiting = true; break; case SVC_RESPAWN: print_status(svc->desc, STATUS_STARTED, false); break; case SVC_ONCE: singleshot += 1; break; } } } else { print_status(svc->desc, STATUS_OK, false); rt->status = EXIT_SUCCESS; rt->state = STATE_COMPLETED; } if (singleshot == 0 && !waiting && queue_idx >= count) target_completed(target); out_unblock: sigprocmask(SIG_SETMASK, &old_mask, NULL); return ret; }