/* * Copyright (C)2018-2022 Intel Corporation. * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mevent.h" #include "acrnctl.h" #include "acrn_mngr.h" #include "ioc.h" #define ACRND_NAME "acrnd" #define SERVICE_VM_LCS_SOCK "service-vm-lcs" #define HW_IOC_PATH "/dev/cbc-early-signals" #define VMS_STOP_TIMEOUT 20U /* Time to wait VMs to stop */ #define SOCK_TIMEOUT 2U /* acrnd worker timer */ struct work_arg { char name[MAX_VM_NAME_LEN]; }; struct acrnd_work { void (*func) (struct work_arg *arg); time_t expire; /* when execute this work */ struct work_arg arg; LIST_ENTRY(acrnd_work) list; }; static LIST_HEAD(acrnd_work_list, acrnd_work) work_head; static pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t acrnd_stop_mutex = PTHREAD_MUTEX_INITIALIZER; static unsigned int acrnd_stop_timeout; static unsigned char platform_has_hw_ioc; static int sigterm = 0; /* Exit acrnd when recevied SIGTERM and stop all vms */ static int logfile = 1; #ifdef MNGR_DEBUG static int autostart_delay = 0; #endif /* acrnd_add_work(), add a worker function. * @func, the worker function, will be called with work_mutex hold. * @sec, when add a @func(), after @sec seconds @func() will be called. * @arg, a cpoy of @arg will be pass to @func. */ int acrnd_add_work(void (*func) (struct work_arg *arg), struct work_arg *arg, unsigned sec) { struct acrnd_work *work; time_t current; if (!func) { printf("%s: No worker function configured\n", __func__); return -1; } work = calloc(1, sizeof(*work)); if (!work) { perror("Alloc work struct fail:"); return -1; } current = time(NULL); if (current == (time_t) - 1) { perror("Get current time by timer() fail:"); free(work); return -1; } work->func = func; work->expire = sec + current; if (arg) memcpy(&work->arg, arg, sizeof(*arg)); pthread_mutex_lock(&work_mutex); LIST_INSERT_HEAD(&work_head, work, list); pthread_mutex_unlock(&work_mutex); printf("%s added, work->expire:%lx\n", __FUNCTION__, work->expire); return 0; } /* check if the works in work_head is expired, if expired then run * work func() once and delete it */ static void try_do_works(void) { time_t current; struct acrnd_work *work, *twork; current = time(NULL); if (current == (time_t) - 1) { perror("Get current time by timer() fail:"); return; } pthread_mutex_lock(&work_mutex); list_foreach_safe(work, &work_head, list, twork) { if (current >= work->expire) { printf("Run expired work(%lu), current time(%lu)\n", work->expire, current); work->func(&work->arg); LIST_REMOVE(work, list); free(work); } } pthread_mutex_unlock(&work_mutex); } static void acrnd_run_vm(char *name); unsigned get_sos_wakeup_reason(void); /* Timer callback to run/resume VM. * Will be called with work_mutex hold */ void acrnd_vm_timer_func(struct work_arg *arg) { struct vmmngr_struct *vm; pid_t pid; if (!arg) { printf("%s: No work argument configured\n", __func__); return; } vmmngr_update(); vm = vmmngr_find(arg->name); if (!vm) { printf("%s: Can't find %s\n", __func__, arg->name); return; } switch (vm->state) { case VM_CREATED: pid = fork(); if (!pid) acrnd_run_vm(vm->name); break; case VM_SUSPENDED: resume_vm(arg->name, CBC_WK_RSN_RTC); break; default: printf("%s: Unknown vm state %ld\n", __func__, vm->state); } } static pthread_mutex_t timer_file_mutex = PTHREAD_MUTEX_INITIALIZER; /* load/store_timer_list to file to keep timers if Service VM poweroff */ static int load_timer_list(void) { FILE *fp; struct work_arg arg = {}; time_t expire, current; char l[256]; int i, ret = 0; pthread_mutex_lock(&timer_file_mutex); fp = fopen(ACRN_CONF_TIMER_LIST, "r"); if (!fp) { perror("Open timer list file"); ret = -1; goto open_file_err; } while (!feof(fp)) { memset(l, 0, 256); /* Ignore null line */ if (!fgets(l, 255, fp)) { continue; } /* get vmname from the string "l", which has "[vmname]\t[expire]" */ for (i = 0; i < sizeof(arg.name); i++) { if (l[i] == '\t') { arg.name[i] = '\0'; break; } arg.name[i] = l[i]; } /* can't found vmname in the string "l" or vmname is truncated */ if (i == 0 || i == sizeof(arg.name)) { fprintf(stderr, "Invalid vmname %s from timer list file\n", arg.name); continue; } /* get expire from the string "l" */ expire = strtoul(&l[i + 1], NULL, 10); if (expire == 0 || errno == ERANGE) { perror("Invalid expire from timer list file"); continue; } current = time(NULL); if (expire > current) expire -= current; else expire = 1; ret = acrnd_add_work(acrnd_vm_timer_func, &arg, expire); if (ret != 0) { fprintf(stderr, "Failed to add vm timer, errno %d", ret); } else { printf("vm %s will be activated at %ld seconds later\n", arg.name, expire); } } fclose(fp); open_file_err: pthread_mutex_unlock(&timer_file_mutex); return ret; } static void acrnd_run_vm(char *name) { /*If do not use logfile, then output to stdout, so that it can be redirected to journal by systemd */ if (logfile) { stdin = freopen("/dev/null", "r+", stdin); stdout = freopen("/dev/null", "r+", stdout); stderr = freopen("/dev/null", "r+", stderr); fflush(stdin); fflush(stdout); fflush(stderr); } start_vm(name); printf("%s exited!\n", name); exit(0); } static int active_all_vms(void) { struct vmmngr_struct *vm; int ret = 0; pid_t pid; unsigned reason = 0; vmmngr_update(); LIST_FOREACH(vm, &vmmngr_head, list) { switch (vm->state) { case VM_CREATED: pid = fork(); if (!pid) acrnd_run_vm(vm->name); break; case VM_SUSPENDED: if (platform_has_hw_ioc) { reason = get_sos_wakeup_reason(); } ret += resume_vm(vm->name, reason); break; default: printf("%s: Unkown vm state %ld\n", __func__, vm->state); } } return ret ? -1 : 0; } static void stop_all_vms(void) { struct vmmngr_struct *vm; int err; vmmngr_update(); LIST_FOREACH(vm, &vmmngr_head, list) { err = stop_vm(vm->name, 0); if (err != 0) { fprintf(stderr, "Fail to send stop cmd to vm %s\n", vm->name); } else { printf("Send stop cmd to vm %s successfully\n", vm->name); } } } static int wakeup_suspended_vms(unsigned wakeup_reason) { struct vmmngr_struct *vm; int ret = 0; vmmngr_update(); LIST_FOREACH(vm, &vmmngr_head, list) { if (vm->state == VM_SUSPENDED) ret += resume_vm(vm->name, wakeup_reason); } return ret ? -1 : 0; } static int acrnd_fd = -1; unsigned get_sos_wakeup_reason(void) { int client_fd, ret = 0; struct mngr_msg req; struct mngr_msg ack; client_fd = mngr_open_un(SERVICE_VM_LCS_SOCK, MNGR_CLIENT); if (client_fd <= 0) { fprintf(stderr, "Failed to open the socket(%s) to query the " "reason for the wake-up", SERVICE_VM_LCS_SOCK); goto EXIT; } req.magic = MNGR_MSG_MAGIC; req.msgid = WAKEUP_REASON; req.timestamp = time(NULL); if (mngr_send_msg(client_fd, &req, &ack, SOCK_TIMEOUT) <= 0) fprintf(stderr, "Failed to get wakeup_reason from Service VM, err(%d)\n", ret); else ret = ack.data.reason; mngr_close(client_fd); EXIT: return ret; } static void handle_timer_req(struct mngr_msg *msg, int client_fd, void *param) { struct mngr_msg ack; struct vmmngr_struct *vm; struct work_arg arg = {}; ack.msgid = msg->msgid; ack.timestamp = msg->timestamp; ack.data.err = -1; vmmngr_update(); vm = vmmngr_find(msg->data.acrnd_timer.name); if (!vm) { printf("%s: Can't find %s\n", __func__, msg->data.acrnd_timer.name); goto reply_ack; } strncpy(arg.name, msg->data.acrnd_timer.name, sizeof(arg.name) - 1); if (sizeof(arg.name) - 1 < strnlen(msg->data.acrnd_timer.name, MAX_VM_NAME_LEN)) { perror("timer name was truncated\n"); goto reply_ack; } if (acrnd_add_work(acrnd_vm_timer_func, &arg, msg->data.acrnd_timer.t)) { printf("%s: Failed to add acrnd_vm_timer_func\n", __func__); goto reply_ack; } ack.data.err = 0; reply_ack: if (client_fd > 0) mngr_send_msg(client_fd, &ack, NULL, 0); } static int set_sos_timer(time_t due_time) { int client_fd, ret; int retry = 1; struct mngr_msg req; struct mngr_msg ack; client_fd = mngr_open_un(SERVICE_VM_LCS_SOCK, MNGR_CLIENT); if (client_fd <= 0) { perror("Failed to open sock for to req wkup_reason"); ret = client_fd; goto EXIT; } req.magic = MNGR_MSG_MAGIC; req.msgid = RTC_TIMER; req.timestamp = time(NULL); req.data.rtc_timer.t = due_time; RETRY: ret = mngr_send_msg(client_fd, &req, &ack, SOCK_TIMEOUT); while (ret <= 0 && retry < 5) { printf("Fail to set sos wakeup timer(err:%d), retry %d...\n", ret, retry++); goto RETRY; } mngr_close(client_fd); EXIT: return ret; } static int store_timer_list(void) { FILE *fp; struct acrnd_work *w, *twork; time_t sys_wakeup = 0; int ret = 0; pthread_mutex_lock(&timer_file_mutex); fp = fopen(ACRN_CONF_TIMER_LIST, "w+"); if (!fp) { perror("Open timer list file"); ret = -1; goto open_file_err; } pthread_mutex_lock(&work_mutex); list_foreach_safe(w, &work_head, list, twork) { if (w->func != acrnd_vm_timer_func) continue; if (!sys_wakeup) sys_wakeup = w->expire; if (w->expire < sys_wakeup) sys_wakeup = w->expire; fprintf(fp, "%s\t%lu\n", w->arg.name, w->expire); /* remove work from list after it's saved onto fs */ LIST_REMOVE(w, list); free(w); } pthread_mutex_unlock(&work_mutex); /* If any timer is stored * system must be awake at sys_wakeup */ if (sys_wakeup) { set_sos_timer(sys_wakeup); } else { unlink(ACRN_CONF_TIMER_LIST); } fclose(fp); open_file_err: pthread_mutex_unlock(&timer_file_mutex); return ret; } static int check_vms_status(unsigned int status) { struct vmmngr_struct *s; LIST_FOREACH(s, &vmmngr_head, list) if (s->state != status) return -1; return 0; } static int wait_for_stop(unsigned int timeout) { unsigned long t = timeout; /*Let ospm stopping User VMs */ /* list and update the vm status */ do { vmmngr_update(); printf("Waiting %lu seconds for all vms enter S3/S5 state\n", t); if (check_vms_status(VM_CREATED) == 0) { printf("All vms have entered S5 state successfully\n"); return SHUTDOWN; } if (check_vms_status(VM_SUSPENDED) == 0) { printf("All vms have entered S3 state successfully\n"); return SUSPEND; } sleep(1); } while (t--); return -1; } static void* notify_stop_state(void *arg) { int lcs_fd; int rc; struct mngr_msg req; req.magic = MNGR_MSG_MAGIC; rc = wait_for_stop(acrnd_stop_timeout); if (rc < 0) { fprintf(stderr, "Timeout(%u sec) to wait all vms enter S3/S5\n", acrnd_stop_timeout); req.msgid = SUSPEND; req.data.err = -1; } else { req.msgid = rc; req.data.err = 0; } store_timer_list(); lcs_fd = mngr_open_un(SERVICE_VM_LCS_SOCK, MNGR_CLIENT); if (lcs_fd < 0) { fprintf(stderr, "cannot open sos-lcs.socket\n"); goto exit; } mngr_send_msg(lcs_fd, &req, NULL, 0); mngr_close(lcs_fd); exit: pthread_mutex_unlock(&acrnd_stop_mutex); return NULL; } static void _handle_acrnd_stop(unsigned int timeout) { int rc; pthread_t tid; pthread_attr_t attr; /* * Only one acrnd stop thread at a time * if failed to lock the acrnd_stop_mutex, then return directly * if creating thread success, then unlock in the thread exit * if failed to create thread, then unlock immediately. */ if (pthread_mutex_trylock(&acrnd_stop_mutex) == 0) { acrnd_stop_timeout = timeout; /* * Due to acrnd only has one main thread, and acrnd stop flow * probably blocks main thread, so a detached thread is created * to avoid this. */ rc = pthread_attr_init(&attr); if (rc < 0) goto fail_init; rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (rc < 0) goto fail; rc = pthread_create(&tid, &attr, notify_stop_state, NULL); if (rc < 0) goto fail; pthread_attr_destroy(&attr); } return; fail: pthread_attr_destroy(&attr); fail_init: pthread_mutex_unlock(&acrnd_stop_mutex); fprintf(stderr, "Failed to invoke handle_acrnd_stop \n"); } static void handle_acrnd_stop(struct mngr_msg *msg, int client_fd, void *param) { struct mngr_msg ack; ack.msgid = msg->msgid; ack.timestamp = msg->timestamp; ack.data.err = 0; if (client_fd > 0) mngr_send_msg(client_fd, &ack, NULL, 0); _handle_acrnd_stop(msg->data.acrnd_stop.timeout); } void handle_acrnd_resume(struct mngr_msg *msg, int client_fd, void *param) { struct mngr_msg ack; struct stat st; int wakeup_reason = 0; ack.msgid = msg->msgid; ack.timestamp = msg->timestamp; ack.data.err = -1; /* acrnd get wakeup_reason from sos lcs */ if (platform_has_hw_ioc) { wakeup_reason = get_sos_wakeup_reason(); } if (wakeup_reason & CBC_WK_RSN_RTC) { printf("Resumed User VM, by RTC timer, reason(%x)!\n", wakeup_reason); /* wakeup by RTC timer */ if (!stat(ACRN_CONF_TIMER_LIST, &st) && S_ISREG(st.st_mode)) { ack.data.err = load_timer_list(); if (ack.data.err == 0) { printf("Resumed User VM by RTC timer, try do works!\n"); /* load timers successfully */ try_do_works(); goto reply_ack; } } perror("Error to load timers, wakeup all VMs"); ack.data.err = wakeup_suspended_vms(wakeup_reason); } else { printf("Resumed User VM, by ignition button\n"); ack.data.err = wakeup_suspended_vms(wakeup_reason); } reply_ack: unlink(ACRN_CONF_TIMER_LIST); if (client_fd > 0) mngr_send_msg(client_fd, &ack, NULL, 0); } static void handle_on_exit(void) { printf("Exiting from acrnd\n"); store_timer_list(); if (acrnd_fd > 0) { mngr_close(acrnd_fd); acrnd_fd = -1; } } int init_vm(void) { unsigned int wakeup_reason = 0; int ret; #ifdef MNGR_DEBUG printf("VM auto-start in %ds. \"systemctl stop acrnd\" to interrupt the auto-start.\n", autostart_delay); sleep(autostart_delay); #endif /* init all User VMs, according wakeup_reason */ if (platform_has_hw_ioc) { wakeup_reason = get_sos_wakeup_reason(); } if (wakeup_reason & CBC_WK_RSN_RTC) { printf("Loading timer list to set vms wakeup time\n"); ret = load_timer_list(); if (ret == 0) { printf("Successfully activate vms from timer list\n"); return 0; } else { printf("Error happens when load timer list, errno %d\n", ret); } } /* TODO: auto start User VMs */ printf("Activating all vms\n"); return active_all_vms(); } static void sigterm_handler(int signo) { printf("Received signal %d, Will stop all the vms within %d sec\n", signo, VMS_STOP_TIMEOUT); sigterm = 1; } static const char optString[] = "td:h"; static void display_usage(void) { printf("acrnd - Daemon for ACRN VM Management\n" #ifdef MNGR_DEBUG "[Usage] acrnd [-t] [-d delay] [-h]\n\n" #else "[Usage] acrnd [-t] [-h]\n\n" #endif "[Options]\n" "\t-t: print messages to stdout\n" #ifdef MNGR_DEBUG "\t-d: delay the autostarting of VMs, <0-60> in second\n" #endif "\t-h: print this message\n" "\n"); } static int parse_opt(int argc, char *argv[]) { int opt, ret = 0; #ifdef MNGR_DEBUG long delay = 0; #endif while ((opt = getopt(argc, argv, optString)) != -1) { switch (opt) { case 't': logfile = 0; break; #ifdef MNGR_DEBUG case 'd': delay = strtol(optarg, NULL, 10); if ((errno == ERANGE && (delay == LONG_MAX || delay == LONG_MIN)) || (errno != 0 && delay == 0) || (delay < 0 || delay > 60)) { printf("'-d' invalid parameter: %s\n", optarg); return -EINVAL; } autostart_delay = (int)delay; ret = 0; break; #endif case 'h': display_usage(); ret = -EINVAL; break; default: printf("Ignore unknown option: %c\n", opt); ret = -EINVAL; } } return ret; } int main(int argc, char *argv[]) { int ret; if (getuid() != 0) { printf("Please run acrnd with root privileges. Exiting.\n"); return -1; } if (parse_opt(argc, argv)) return -1; if (!access(HW_IOC_PATH, F_OK)) { platform_has_hw_ioc = 1; } else { platform_has_hw_ioc = 0; } ret = check_dir(ACRN_DM_BASE_PATH, CHK_CREAT); if (ret) { fprintf(stderr, "%s %d\r\n", __FUNCTION__, __LINE__); return -1; } ret = check_dir(ACRN_DM_SOCK_PATH, CHK_CREAT); if (ret) { fprintf(stderr, "%s %d\r\n", __FUNCTION__, __LINE__); return -1; } /* create listening thread */ acrnd_fd = mngr_open_un(ACRND_NAME, MNGR_SERVER); if (acrnd_fd < 0) { printf("%s: Failed to open %s, err: %s\n", __func__, ACRND_NAME, strerror(errno)); return -1; } if (init_vm()) { printf("%s: Failed to init_vm\n", __func__); return -1; } unlink(ACRN_CONF_TIMER_LIST); atexit(handle_on_exit); if (signal(SIGTERM, sigterm_handler) == SIG_ERR) { fprintf(stderr, "Can not catch signal SIGTERM(%d), err: %s\n", SIGTERM, strerror(errno)); } mngr_add_handler(acrnd_fd, ACRND_TIMER, handle_timer_req, NULL); mngr_add_handler(acrnd_fd, ACRND_STOP, handle_acrnd_stop, NULL); mngr_add_handler(acrnd_fd, ACRND_RESUME, handle_acrnd_resume, NULL); /* Last thing, run our timer works */ while (!sigterm) { try_do_works(); sleep(1); } /* * Try to stop all the vms when receiving SIGTERM within VMS_STOP_TIMEOUT sec * gracefully. acrnd will exit after waiting maximal VMS_STOP_TIMEOUT sec. * System will kill all other vms which can not be stopped within VMS_STOP_TIMEOUT sec. */ stop_all_vms(); wait_for_stop(VMS_STOP_TIMEOUT); return 0; }