431 lines
8.9 KiB
C
431 lines
8.9 KiB
C
/**
|
|
* Copyright (C) 2018 Intel Corporation
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <pthread.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include "acrnctl.h"
|
|
#include "acrn_mngr.h"
|
|
#include "mevent.h"
|
|
#include "pm.h"
|
|
|
|
const char *state_str[] = {
|
|
[VM_STATE_UNKNOWN] = "unknown",
|
|
[VM_CREATED] = "stopped",
|
|
[VM_STARTED] = "started",
|
|
[VM_SUSPENDED] = "suspended",
|
|
[VM_UNTRACKED] = "untracked",
|
|
};
|
|
|
|
/* List head of all vm */
|
|
static pthread_mutex_t vmmngr_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
struct vmmngr_list_struct vmmngr_head = { NULL };
|
|
static unsigned long update_count = 0;
|
|
|
|
struct vmmngr_struct *vmmngr_find(const char *name)
|
|
{
|
|
struct vmmngr_struct *s;
|
|
|
|
LIST_FOREACH(s, &vmmngr_head, list)
|
|
if (!strcmp(name, s->name))
|
|
return s;
|
|
return NULL;
|
|
}
|
|
|
|
static int send_msg(const char *vmname, struct mngr_msg *req, struct mngr_msg *ack);
|
|
|
|
static int query_state(const char *name)
|
|
{
|
|
struct mngr_msg req;
|
|
struct mngr_msg ack;
|
|
int ret;
|
|
|
|
req.magic = MNGR_MSG_MAGIC;
|
|
req.msgid = DM_QUERY;
|
|
req.timestamp = time(NULL);
|
|
|
|
ret = send_msg(name, &req, &ack);
|
|
if (ret) {
|
|
printf("%s: Error to quary %s state, err: %d\n", __func__, name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ack.data.state < 0) {
|
|
fprintf(stderr, "%s ack.data.state:%d\n", __FUNCTION__, ack.data.state);
|
|
}
|
|
|
|
return ack.data.state;
|
|
}
|
|
|
|
/*
|
|
* get vmname and pid from /run/acrn/mngr/[vmname].monitor.[pid].socket
|
|
*/
|
|
static inline int _get_vmname_pid(const char *src, char *p_vmname,
|
|
int max_len_vmname, int *pid)
|
|
{
|
|
char *p = NULL;
|
|
long val64;
|
|
|
|
p = strchr(src, '.');
|
|
/* p - src: length of the substring "vmname" in the sting "src" */
|
|
if (!p || p - src == 0 || p - src >= max_len_vmname)
|
|
return -1;
|
|
else
|
|
strncpy(p_vmname, src, p - src);
|
|
|
|
/* move the pointer to the "pid" in the string "src" */
|
|
if (strncmp(".monitor.", p, strlen(".monitor.")))
|
|
return -1;
|
|
else
|
|
p = p + strlen(".monitor.");
|
|
|
|
val64 = strtol(p, NULL, 10);
|
|
if ((errno == ERANGE && (val64 == LONG_MAX || val64 == LONG_MIN))
|
|
|| (errno != 0 && val64 == 0))
|
|
return -1;
|
|
|
|
*pid = (int)val64;
|
|
|
|
p = strchr(p, '.');
|
|
if (!p || strncmp(".socket", p, strlen(".socket")))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* find all the running DM process, which has */
|
|
/* /run/acrn/mngr/[vmname].monitor.[pid].socket */
|
|
static void _scan_alive_vm(void)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
struct vmmngr_struct *vm;
|
|
char name[PATH_LEN];
|
|
int pid;
|
|
int ret;
|
|
|
|
ret = check_dir(ACRN_DM_SOCK_PATH, CHK_ONLY);
|
|
if (ret) {
|
|
printf("%s: Failed to check directory %s, err: %d\n\
|
|
Make sure the acrnd daemon is running ('systemctl status acrnd').\n",
|
|
__func__, ACRN_DM_SOCK_PATH, ret);
|
|
return;
|
|
}
|
|
|
|
dir = opendir(ACRN_DM_SOCK_PATH);
|
|
if (!dir) {
|
|
printf("%s: Failed to open directory %s\n", __func__, ACRN_DM_SOCK_PATH);
|
|
return;
|
|
}
|
|
|
|
while ((entry = readdir(dir))) {
|
|
memset(name, 0, sizeof(name));
|
|
ret = _get_vmname_pid(entry->d_name, name, sizeof(name), &pid);
|
|
if (ret < 0)
|
|
continue;
|
|
|
|
if (name[sizeof(name) - 1]) {
|
|
/* truncate name and go a head */
|
|
name[sizeof(name) - 1] = 0;
|
|
printf("%s: Truncate name as %s\n", __func__, name);
|
|
}
|
|
|
|
vm = vmmngr_find(name);
|
|
|
|
if (!vm) {
|
|
vm = calloc(1, sizeof(*vm));
|
|
if (!vm) {
|
|
printf("%s: Failed to alloc mem for %s\n", __func__, name);
|
|
continue;
|
|
}
|
|
memcpy(vm->name, name, sizeof(vm->name) - 1);
|
|
LIST_INSERT_HEAD(&vmmngr_head, vm, list);
|
|
}
|
|
|
|
ret = query_state(name);
|
|
|
|
if (ret < 0)
|
|
/* unsupport query */
|
|
vm->state_tmp = VM_STARTED;
|
|
else
|
|
switch (ret) {
|
|
case VM_SUSPEND_NONE:
|
|
vm->state_tmp = VM_STARTED;
|
|
break;
|
|
case VM_SUSPEND_SUSPEND:
|
|
vm->state_tmp = VM_SUSPENDED;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Warnning: unknow vm state:0x%lx\n",
|
|
vm->state);
|
|
vm->state_tmp = VM_STATE_UNKNOWN;
|
|
}
|
|
vm->update = update_count;
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
/*
|
|
* get vmname and suffix from src,
|
|
* which has [vmname].[suffix]
|
|
*/
|
|
static inline int _get_vmname_suffix(const char *src,
|
|
char *name, int max_len_name, char *suffix, int max_len_suffix)
|
|
{
|
|
char *p = NULL;
|
|
|
|
p = strchr(src, '.');
|
|
/* p - src: length of the substring vmname in the string src*/
|
|
if (!p || p - src == 0)
|
|
return -1;
|
|
|
|
strncpy(name, src, p - src);
|
|
if (p - src >= max_len_name) {
|
|
/* truncate name and go a head */
|
|
name[max_len_name - 1] = '\0';
|
|
printf("%s: Truncate name as %s\n", __func__, name);
|
|
}
|
|
|
|
strncpy(suffix, p + 1, max_len_suffix);
|
|
if (strncmp(suffix, "sh", strlen("sh")))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _scan_added_vm(void)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
struct vmmngr_struct *vm;
|
|
char name[PATH_LEN];
|
|
char suffix[PATH_LEN];
|
|
int ret;
|
|
|
|
ret = check_dir(ACRN_CONF_PATH, CHK_ONLY);
|
|
if (ret) {
|
|
printf("%s: Failed to check directory %s, err: %d\n", __func__, ACRN_CONF_PATH, ret);
|
|
return;
|
|
}
|
|
|
|
ret = check_dir(ACRN_CONF_PATH_ADD, CHK_ONLY);
|
|
if (ret) {
|
|
printf("%s: Failed to check directory %s, err: %d\n", __func__, ACRN_CONF_PATH_ADD, ret);
|
|
return;
|
|
}
|
|
|
|
dir = opendir(ACRN_CONF_PATH_ADD);
|
|
if (!dir) {
|
|
printf("%s: Failed to open directory %s\n", __func__, ACRN_CONF_PATH_ADD);
|
|
return;
|
|
}
|
|
|
|
while ((entry = readdir(dir))) {
|
|
ret = strnlen(entry->d_name, sizeof(entry->d_name));
|
|
if (ret >= sizeof(name)) {
|
|
continue;
|
|
}
|
|
|
|
memset(name, 0, sizeof(name));
|
|
memset(suffix, 0, sizeof(suffix));
|
|
ret = _get_vmname_suffix(entry->d_name,
|
|
name, sizeof(name), suffix, sizeof(suffix));
|
|
if (ret < 0)
|
|
continue;
|
|
|
|
vm = vmmngr_find(name);
|
|
|
|
if (!vm) {
|
|
vm = calloc(1, sizeof(*vm));
|
|
if (!vm) {
|
|
printf("%s: Failed to alloc mem for %s\n", __func__, name);
|
|
continue;
|
|
}
|
|
memcpy(vm->name, name, sizeof(vm->name) - 1);
|
|
LIST_INSERT_HEAD(&vmmngr_head, vm, list);
|
|
}
|
|
|
|
vm->state_tmp = VM_CREATED;
|
|
vm->update = update_count;
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
static void _remove_dead_vm(void)
|
|
{
|
|
struct vmmngr_struct *vm, *tvm;
|
|
|
|
list_foreach_safe(vm, &vmmngr_head, list, tvm) {
|
|
if (vm->update == update_count) {
|
|
vm->state = vm->state_tmp;
|
|
continue;
|
|
}
|
|
LIST_REMOVE(vm, list);
|
|
printf("%s: Removed dead %s\n", __func__, vm->name);
|
|
free(vm);
|
|
}
|
|
};
|
|
|
|
void vmmngr_update(void)
|
|
{
|
|
pthread_mutex_lock(&vmmngr_mutex);
|
|
update_count++;
|
|
_scan_added_vm();
|
|
_scan_alive_vm();
|
|
_remove_dead_vm();
|
|
pthread_mutex_unlock(&vmmngr_mutex);
|
|
}
|
|
|
|
/* helper functions */
|
|
int shell_cmd(const char *cmd, char *outbuf, int len)
|
|
{
|
|
FILE *ptr;
|
|
char cmd_buf[256];
|
|
int ret;
|
|
|
|
if (!outbuf)
|
|
return system(cmd);
|
|
|
|
memset(cmd_buf, 0, sizeof(cmd_buf));
|
|
memset(outbuf, 0, len);
|
|
if (snprintf(cmd_buf, sizeof(cmd_buf), "%s 2>&1", cmd) >= sizeof(cmd_buf)) {
|
|
printf("ERROR: shell command is truncated\n");
|
|
return -1;
|
|
}
|
|
ptr = popen(cmd_buf, "re");
|
|
if (!ptr)
|
|
return -1;
|
|
|
|
ret = fread(outbuf, 1, len, ptr);
|
|
pclose(ptr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int send_msg(const char *vmname, struct mngr_msg *req,
|
|
struct mngr_msg *ack)
|
|
{
|
|
int fd, ret;
|
|
|
|
if (!vmname) {
|
|
printf("No vmname provided\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fd = mngr_open_un(vmname, MNGR_CLIENT);
|
|
if (fd < 0) {
|
|
printf("Unable to open vm %s socket. It may have been shutdown\n", vmname);
|
|
return -1;
|
|
}
|
|
|
|
ret = mngr_send_msg(fd, req, ack, 1);
|
|
if (ret < 0) {
|
|
printf("Unable to send msg to vm %s socket. It may have been shutdown\n", vmname);
|
|
mngr_close(fd);
|
|
return ret;
|
|
}
|
|
|
|
mngr_close(fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int list_vm()
|
|
{
|
|
struct vmmngr_struct *s;
|
|
int find = 0;
|
|
|
|
LIST_FOREACH(s, &vmmngr_head, list) {
|
|
printf("%s\t\t%s\n", s->name, state_str[s->state]);
|
|
find++;
|
|
}
|
|
|
|
if (!find)
|
|
printf("There are no VMs\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int start_vm(const char *vmname)
|
|
{
|
|
char cmd[PATH_LEN + sizeof(ACRN_CONF_PATH_ADD) * 2 + MAX_VM_NAME_LEN * 2];
|
|
|
|
if (snprintf(cmd, sizeof(cmd), "bash %s/%s.sh $(cat %s/%s.args)",
|
|
ACRN_CONF_PATH_ADD, vmname, ACRN_CONF_PATH_ADD, vmname) >= sizeof(cmd)) {
|
|
printf("ERROR: command is truncated\n");
|
|
return -1;
|
|
}
|
|
|
|
return system(cmd);
|
|
}
|
|
|
|
int stop_vm(const char *vmname, int force)
|
|
{
|
|
struct mngr_msg req;
|
|
struct mngr_msg ack;
|
|
|
|
req.magic = MNGR_MSG_MAGIC;
|
|
req.msgid = DM_STOP;
|
|
req.timestamp = time(NULL);
|
|
req.data.acrnd_stop.force = force;
|
|
|
|
send_msg(vmname, &req, &ack);
|
|
if (ack.data.err) {
|
|
printf("Error happens when try to stop vm. errno(%d)\n",
|
|
ack.data.err);
|
|
}
|
|
|
|
return ack.data.err;
|
|
}
|
|
|
|
int resume_vm(const char *vmname, unsigned reason)
|
|
{
|
|
struct mngr_msg req;
|
|
struct mngr_msg ack;
|
|
|
|
req.magic = MNGR_MSG_MAGIC;
|
|
req.msgid = DM_RESUME;
|
|
req.timestamp = time(NULL);
|
|
|
|
req.data.reason = reason;
|
|
|
|
send_msg(vmname, &req, &ack);
|
|
|
|
if (ack.data.err) {
|
|
printf("Unable to resume vm. errno(%d)\n", ack.data.err);
|
|
}
|
|
|
|
return ack.data.err;
|
|
}
|
|
|
|
int blkrescan_vm(const char *vmname, char *devargs)
|
|
{
|
|
struct mngr_msg req;
|
|
struct mngr_msg ack;
|
|
|
|
req.magic = MNGR_MSG_MAGIC;
|
|
req.msgid = DM_BLKRESCAN;
|
|
req.timestamp = time(NULL);
|
|
strncpy(req.data.devargs, devargs, PARAM_LEN - 1);
|
|
req.data.devargs[PARAM_LEN - 1] = '\0';
|
|
|
|
send_msg(vmname, &req, &ack);
|
|
|
|
if (ack.data.err) {
|
|
printf("Unable to rescan virtio-blk device in vm. errno(%d)\n", ack.data.err);
|
|
}
|
|
|
|
return ack.data.err;
|
|
}
|