/* SPDX-License-Identifier: ISC */ #include "init.h" static service_list_t cfg; static int service_id = 1; static int target = -1; static service_t *running = NULL; static service_t *terminated = NULL; static service_t *queue = NULL; static service_t *completed = NULL; static service_t *failed = NULL; static int singleshot = 0; static bool waiting = false; static bool find_service(service_t *list, service_t *svc) { while (list != NULL) { if (strcmp(list->fname, svc->fname) == 0) return true; list = list->next; } return false; } static void remove_not_in_list(service_t **current, service_t *list, int tgt) { service_t *it = *current, *prev = NULL; while (it != NULL) { if (it->target == tgt && !find_service(list, it)) { if (prev == NULL) { delsvc(it); *current = (*current)->next; it = *current; } else { prev->next = it->next; delsvc(it); it = prev->next; } } else { prev = it; it = it->next; } } } static bool have_service(service_t *svc) { return find_service(running, svc) || find_service(terminated, svc) || find_service(queue, svc) || find_service(completed, svc) || find_service(failed, svc); } static int start_service(service_t *svc) { if (svc->id < 1) svc->id = service_id++; svc->pid = runsvc(svc); if (svc->pid == -1) { print_status(svc->desc, STATUS_FAIL, false); svc->next = completed; completed = svc; return -1; } svc->next = running; running = svc; return 0; } static void handle_terminated_service(service_t *svc) { switch (svc->type) { case SVC_RESPAWN: if (target == TGT_REBOOT || target == TGT_SHUTDOWN) break; if (svc->flags & SVC_FLAG_ADMIN_STOPPED) break; if (svc->rspwn_limit > 0) { svc->rspwn_count += 1; if (svc->rspwn_count >= svc->rspwn_limit) { print_status(svc->desc, STATUS_FAIL, false); goto out_failure; } } start_service(svc); return; case SVC_WAIT: waiting = false; print_status(svc->desc, svc->status == EXIT_SUCCESS ? STATUS_OK : STATUS_FAIL, true); if (singleshot == 0 && queue == NULL) target_completed(target); if (svc->status != EXIT_SUCCESS) goto out_failure; break; case SVC_ONCE: singleshot -= 1; print_status(svc->desc, svc->status == EXIT_SUCCESS ? STATUS_OK : STATUS_FAIL, false); if (singleshot == 0 && queue == NULL && !waiting) target_completed(target); if (svc->status != EXIT_SUCCESS) goto out_failure; break; } svc->next = completed; completed = svc; return; out_failure: svc->next = failed; failed = svc; } void supervisor_handle_exited(pid_t pid, int status) { service_t *prev = NULL, *svc = running; while (svc != NULL && svc->pid != pid) { prev = svc; svc = svc->next; } if (svc == NULL) return; if (prev != NULL) { prev->next = svc->next; } else { running = svc->next; } svc->status = status; svc->next = terminated; terminated = svc; } void supervisor_set_target(int next) { service_t *svc; if (target == TGT_REBOOT || target == TGT_SHUTDOWN || next == target) return; if (next == TGT_REBOOT || next == TGT_SHUTDOWN) { while (queue != NULL) { svc = queue; queue = queue->next; delsvc(svc); } } if (queue != NULL) { for (svc = queue; svc->next != NULL; svc = svc->next) ; svc->next = cfg.targets[next]; } else { queue = cfg.targets[next]; } cfg.targets[next] = NULL; target = next; } void supervisor_init(void) { int status = STATUS_OK; if (svcscan(SVCDIR, &cfg)) status = STATUS_FAIL; target = TGT_BOOT; queue = cfg.targets[TGT_BOOT]; cfg.targets[TGT_BOOT] = NULL; print_status("reading configuration from " SVCDIR, status, false); } void supervisor_reload_config(void) { service_list_t newcfg; service_t *svc; int i; if (svcscan(SVCDIR, &newcfg)) return; for (i = 0; i < TGT_MAX; ++i) { if (cfg.targets[i] == NULL) { remove_not_in_list(&queue, newcfg.targets[i], i); remove_not_in_list(&terminated, newcfg.targets[i], i); remove_not_in_list(&completed, newcfg.targets[i], i); remove_not_in_list(&failed, newcfg.targets[i], i); while (newcfg.targets[i] != NULL) { svc = newcfg.targets[i]; newcfg.targets[i] = svc->next; if (have_service(svc)) { delsvc(svc); } else { svc->id = service_id++; svc->status = EXIT_SUCCESS; svc->next = completed; completed = svc; } } } else { svc = cfg.targets[i]; cfg.targets[i] = newcfg.targets[i]; newcfg.targets[i] = svc; } } del_svc_list(&newcfg); } bool supervisor_process_queues(void) { service_t *svc; if (terminated != NULL) { svc = terminated; terminated = terminated->next; handle_terminated_service(svc); return true; } if (waiting || queue == NULL) return false; svc = queue; queue = queue->next; if (!(svc->flags & SVC_FLAG_HAS_EXEC)) { print_status(svc->desc, STATUS_OK, false); svc->status = EXIT_SUCCESS; svc->next = completed; completed = svc; goto out; } if (start_service(svc) != 0) return true; 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; } out: if (singleshot == 0 && queue == NULL && !waiting) target_completed(target); return true; } static int send_svc_list(int fd, const void *dst, size_t addrlen, E_SERVICE_STATE filter, E_SERVICE_STATE state, service_t *list) { if (filter != ESS_NONE && filter != state) return 0; while (list != NULL) { if (init_socket_send_status(fd, dst, addrlen, state, list)) return -1; list = list->next; } return 0; } void supervisor_answer_status_request(int fd, const void *dst, size_t addrlen, E_SERVICE_STATE filter) { if (send_svc_list(fd, dst, addrlen, filter, ESS_RUNNING, running)) return; if (send_svc_list(fd, dst, addrlen, filter, ESS_DONE, completed)) return; if (send_svc_list(fd, dst, addrlen, filter, ESS_FAILED, failed)) return; if (send_svc_list(fd, dst, addrlen, filter, ESS_ENQUEUED, queue)) return; if (send_svc_list(fd, dst, addrlen, filter, ESS_ENQUEUED, terminated)) return; init_socket_send_status(fd, dst, addrlen, ESS_NONE, NULL); } static service_t *remove_by_id(service_t **list, int id) { service_t *svc = *list, *prev = NULL; while (svc != NULL && svc->id != id) { prev = svc; svc = svc->next; } if (svc != NULL) { if (prev == NULL) { *list = svc->next; } else { prev->next = svc->next; } } return svc; } void supervisor_start(int id) { service_t *svc; svc = remove_by_id(&completed, id); if (svc != NULL) goto found; svc = remove_by_id(&failed, id); if (svc != NULL) goto found; return; found: svc->rspwn_count = 0; svc->flags &= ~SVC_FLAG_ADMIN_STOPPED; svc->next = queue; queue = svc; } void supervisor_stop(int id) { service_t *svc; for (svc = running; svc != NULL; svc = svc->next) { if (svc->id == id) break; } if (svc != NULL) { /* TODO: something more sophisticated? */ svc->flags |= SVC_FLAG_ADMIN_STOPPED; kill(svc->pid, SIGTERM); } }