diff --git a/devicemodel/tools/acrn-manager/Makefile b/devicemodel/tools/acrn-manager/Makefile new file mode 100644 index 000000000..fe9f1b062 --- /dev/null +++ b/devicemodel/tools/acrn-manager/Makefile @@ -0,0 +1,5 @@ +all: acrnctl.c + gcc -o acrnctl acrnctl.c -I../../include -Wall -g + +clean: + rm -f acrnctl diff --git a/devicemodel/tools/acrn-manager/README.rst b/devicemodel/tools/acrn-manager/README.rst new file mode 100644 index 000000000..ca4c62e95 --- /dev/null +++ b/devicemodel/tools/acrn-manager/README.rst @@ -0,0 +1,54 @@ +acrnctl +####### + +DESCRIPTION +########### +acrnctl: The acrnctl can help user to create, delete, launch and stop UOSs. +It runs under Service OS, and UOSs should be based on acrn-dm + +USAGE +##### +To see what it can do, just run: + # acrnctl +or + # acrnctl help +you may see: + support: + list + start + stop + del + add + Use acrnctl [cmd] help for details + +There are examples: +(1) add a VM + Each time you can just add one VM. Suppose you have an UOS + launch script, such as launch_UOS.sh + you can run: + # acrnctl add launch_UOS.sh -U 1 + vm1-14:59:30 added + Note that, launch script shoud be able to launch ONE UOS. If + it fail, it is better to print some error logs, to tell user + the reason, so that he knows how to solve it. + The vmname is important, the acrnctl searchs VMs by their + names. so duplicated VM names are not allowed. Beside, if the + launch script changes VM name at launch time, acrnctl will + not recgonize it. +(2) delete VMs + # acrnctl del vm1-14:59:30 +(3) show VMs + # acrnctl list + vm1-14:59:30 untracked + vm-yocto stop + vm-android stop +(4) start VM + you can start a vm with 'stop' status, each time can start + one VM. + # acrnctl start vm-yocto +(5) stop VM + you can stop VMs, if their status is not 'stop' + # acrnctl stop vm-yocto vm1-14:59:30 vm-android +BUILD +##### +# make diff --git a/devicemodel/tools/acrn-manager/acrnctl.c b/devicemodel/tools/acrn-manager/acrnctl.c new file mode 100644 index 000000000..319a32e54 --- /dev/null +++ b/devicemodel/tools/acrn-manager/acrnctl.c @@ -0,0 +1,729 @@ +/* + * ProjectAcrn + * Acrnctl + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Author: Tao Yuhong + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "monitor_msg.h" + +#define ACRNCTL_OPT_ROOT "/opt/acrn/conf" + +/* helper functions */ +static 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); + snprintf(cmd_buf, sizeof(cmd_buf), "%s 2>&1", cmd); + ptr = popen(cmd_buf, "re"); + if (!ptr) + return -1; + + ret = fread(outbuf, 1, len, ptr); + pclose(ptr); + + return ret; +} + +static void process_msg(struct vmm_msg *msg) +{ + if (msg->len < sizeof(*msg)) + return; + + switch(msg->msgid) { + case MSG_STR: + printf("%s\n", msg->payload); + break; + default: + printf("Unknown msgid(%d) received\n", msg->msgid); + } +} + +/* vm states data and helper functions */ + +#define ACRN_DM_SOCK_ROOT "/run/acrn" + +struct vmm_struct { + char name[128]; + unsigned long state; + LIST_ENTRY(vmm_struct) list; +}; + +enum vm_state { + VM_STATE_UNKNOWN = 0, + VM_CREATED, /* VM created / awaiting start (boot) */ + VM_STARTED, /* VM started (booted) */ + VM_PAUSED, /* VM paused */ + VM_UNTRACKED, /* VM not created by acrnctl, or its launch script can change vm name */ +}; + +static const char *state_str[] = { + [VM_STATE_UNKNOWN] = "unknown", + [VM_CREATED] = "stopped", + [VM_STARTED] = "started", + [VM_PAUSED] = "paused", + [VM_UNTRACKED] = "untracked", +}; + +static LIST_HEAD(vmm_list_struct, vmm_struct) vmm_head; + +static struct vmm_struct *vmm_list_add(char *name) +{ + struct vmm_struct *s; + + s = calloc(1, sizeof(struct vmm_struct)); + if (!s) { + perror("alloc vmm_struct"); + return NULL; + } + + strcpy(s->name, name); + LIST_INSERT_HEAD(&vmm_head, s, list); + + return s; +} + +static struct vmm_struct *vmm_find(char *name) +{ + struct vmm_struct *s; + + LIST_FOREACH(s, &vmm_head, list) + if (!strcmp(name, s->name)) + return s; + return NULL; +} + +static void vmm_update(void) +{ + char cmd[128] = { }; + char cmd_out[256] = { }; + char *vmname; + char *pvmname = NULL; + struct vmm_struct *s; + + snprintf(cmd, sizeof(cmd), + "find %s/add/ -name \"*.sh\" | " + "sed \"s/\\/opt\\/acrn\\/conf\\/add\\///g\" | " + "sed \"s/.sh//g\"", ACRNCTL_OPT_ROOT); + shell_cmd(cmd, cmd_out, sizeof(cmd_out)); + + vmname = strtok_r(cmd_out, "\n", &pvmname); + while (vmname) { + s = vmm_list_add(vmname); + if (!s) + continue; + s->state = VM_CREATED; + vmname = strtok_r(NULL, "\n", &pvmname); + } + + pvmname = NULL; + + snprintf(cmd, sizeof(cmd), + "find %s/ -name \"*.socket\" | " + "sed \"s/\\/run\\/acrn\\///g\" | " + "sed \"s/-monitor.socket//g\"", ACRN_DM_SOCK_ROOT); + shell_cmd(cmd, cmd_out, sizeof(cmd_out)); + + vmname = strtok_r(cmd_out, "\n", &pvmname); + while (vmname) { + s = vmm_find(vmname); + if (s) + s->state = VM_STARTED; + else { + s = vmm_list_add(vmname); + if (s) + s->state = VM_UNTRACKED; + } + vmname = strtok_r(NULL, "\n", &pvmname); + } +} + +/* There are acrnctl cmds */ +/* command: list */ +static void acrnctl_list_help(void) +{ + printf("acrnctl list\n" + "\tlist all VMs, shown in %s or %s\n", + ACRNCTL_OPT_ROOT, ACRN_DM_SOCK_ROOT); +} + +static int acrnctl_do_list(int argc, char *argv[]) +{ + struct vmm_struct *s; + int find = 0; + + if (argc == 2) + if (!strcmp("help", argv[1])) { + acrnctl_list_help(); + return 0; + } + + vmm_update(); + LIST_FOREACH(s, &vmm_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; +} + +/* command: add */ +static void acrnctl_add_help(void) +{ + printf("acrnctl add [uos_bash_script]\n" + "\trequires an uos script file\n"); +} + +static int check_name(const char *name) +{ + int i = 0, j = 0; + char illegal[] = "!@#$%^&*, "; + + /* Name should start with a letter */ + if ((name[0] < 'a' || name[0] > 'z') + && (name[0] < 'A' || name[0] > 'Z')) { + printf("name not started with latter!\n"); + return -1; + } + + /* no illegal charactoer */ + while (name[i]) { + j = 0; + while (illegal[j]) { + if (name[i] == illegal[j]) { + printf("vmname[%d] is '%c'!\n", i, name[i]); + return -1; + } + j++; + } + i++; + } + + if (!strcmp(name, "help")) + return -1; + if (!strcmp(name, "nothing")) + return -1; + + return 0; +} + +static const char *acrnctl_bin_path; +static int find_acrn_dm; + +static int write_tmp_file(int fd, int n, char *word[]) +{ + int len, ret, i = 0; + char buf[128]; + + if (!n) + return 0; + + len = strlen(word[0]); + if (len >= strlen("acrn-dm")) { + if (!strcmp(word[0] + len - strlen("acrn-dm"), "acrn-dm")) { + find_acrn_dm++; + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "%s gentmpfile", + acrnctl_bin_path); + ret = write(fd, buf, strlen(buf)); + if (ret < 0) + return -1; + i++; + } + } + + while (i < n) { + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), " %s", word[i]); + i++; + ret = write(fd, buf, strlen(buf)); + if (ret < 0) + return -1; + } + ret = write(fd, "\n", 1); + if (ret < 0) + return -1; + return 0; +} + +#define MAX_FILE_SIZE (4096 * 4) +#define MAX_WORD 64 +#define FILE_NAME_LENGTH 128 + +#define TMP_FILE_SUFFIX ".acrnctl" + +static int acrnctl_do_add(int argc, char *argv[]) +{ + struct vmm_struct *s; + int fd, fd_tmp, ret = 0; + char *buf; + char *word[MAX_WORD], *line; + char *word_p = NULL, *line_p = NULL; + int n_word; + char fname[FILE_NAME_LENGTH + sizeof(TMP_FILE_SUFFIX)]; + char cmd[128]; + char args[128]; + int p, i; + char cmd_out[256]; + char vmname[128]; + + if (argc < 2) { + acrnctl_add_help(); + return -1; + } + + if (!strcmp("help", argv[1])) { + acrnctl_add_help(); + return 0; + } + + if (strlen(argv[1]) >= FILE_NAME_LENGTH) { + printf("file name too long: %s\n", argv[1]); + return -1; + } + + memset(args, 0, sizeof(args)); + p = 0; + for (i = 2; i < argc; i++) { + if (p >= sizeof(args) - 1) { + args[sizeof(args) - 1] = 0; + printf("Too many optional args: %s\n", args); + return -1; + } + p += snprintf(&args[p], sizeof(args) - p, " %s", argv[i]); + } + args[p] = ' '; + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror(argv[1]); + ret = -1; + goto open_read_file; + } + + buf = calloc(1, MAX_FILE_SIZE); + if (!buf) { + perror("calloc for add vm"); + ret = -1; + goto calloc_err; + } + + ret = read(fd, buf, MAX_FILE_SIZE); + if (ret >= MAX_FILE_SIZE) { + printf("%s exceed MAX_FILE_SIZE:%d", argv[1], MAX_FILE_SIZE); + ret = -1; + goto file_exceed; + } + + /* open tmp file for write */ + memset(fname, 0, sizeof(fname)); + snprintf(fname, sizeof(fname), "%s%s", argv[1], TMP_FILE_SUFFIX); + fd_tmp = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0666); + if (fd_tmp < 0) { + perror(fname); + ret = -1; + goto open_tmp_file; + } + + find_acrn_dm = 0; + + line = strtok_r(buf, "\n", &line_p); + while (line) { + word_p = NULL; + n_word = 0; + word[n_word] = strtok_r(line, " ", &word_p); + while (word[n_word]) { + n_word++; + word[n_word] = strtok_r(NULL, " ", &word_p); + } + if (write_tmp_file(fd_tmp, n_word, word)) { + ret = -1; + perror(fname); + goto write_tmpfile; + } + line = strtok_r(NULL, "\n", &line_p); + } + + if (!find_acrn_dm) { + printf + ("Don't see 'acrn-dm' in %s, maybe it is in another script, " + "this is no supported for now\n", argv[1]); + ret = -1; + goto no_acrn_dm; + } + + snprintf(cmd, sizeof(cmd), "mv %s %s.back", argv[1], argv[1]); + system(cmd); + + snprintf(cmd, sizeof(cmd), "mv %s %s", fname, argv[1]); + system(cmd); + + memset(vmname, 0, sizeof(vmname)); + snprintf(cmd, sizeof(cmd), "bash %s%s >./%s.result", argv[1], + args, argv[1]); + ret = shell_cmd(cmd, cmd_out, sizeof(cmd_out)); + if (ret < 0) + goto get_vmname; + + snprintf(cmd, sizeof(cmd), "grep \"acrnctl: \" ./%s.result", argv[1]); + ret = shell_cmd(cmd, cmd_out, sizeof(cmd_out)); + if (ret < 0) + goto get_vmname; + + ret = sscanf(cmd_out, "acrnctl: %s", vmname); + if (ret != 1) { + ret = -1; + snprintf(cmd, sizeof(cmd), "cat ./%s.result", argv[1]); + shell_cmd(cmd, cmd_out, sizeof(cmd_out)); + printf("%s can't reach acrn-dm, " + "please try again when you make sure it can launch an UOS\n" + "result:\n%s\n", argv[1], cmd_out); + goto get_vmname; + } + + ret = check_name(vmname); + if (ret) { + printf("\"%s\" is a bad name, please select another name\n", + vmname); + goto get_vmname; + } + + snprintf(cmd, sizeof(cmd), "mkdir -p %s/add", ACRNCTL_OPT_ROOT); + system(cmd); + + vmm_update(); + s = vmm_find(vmname); + if (s) { + printf("%s(%s) already exist, can't add %s%s\n", + vmname, state_str[s->state], argv[1], args); + ret = -1; + goto vm_exist; + } + + snprintf(cmd, sizeof(cmd), "cp %s.back %s/add/%s.sh", argv[1], + ACRNCTL_OPT_ROOT, vmname); + system(cmd); + + snprintf(cmd, sizeof(cmd), "echo %s >%s/add/%s.args", args, + ACRNCTL_OPT_ROOT, vmname); + system(cmd); + printf("%s added\n", vmname); + + vm_exist: + get_vmname: + snprintf(cmd, sizeof(cmd), "rm -f ./%s.result", argv[1]); + system(cmd); + + snprintf(cmd, sizeof(cmd), "mv %s %s", argv[1], fname); + system(cmd); + + snprintf(cmd, sizeof(cmd), "mv %s.back %s", argv[1], argv[1]); + system(cmd); + + no_acrn_dm: + snprintf(cmd, sizeof(cmd), "rm -f %s", fname); + system(cmd); + write_tmpfile: + close(fd_tmp); + open_tmp_file: + file_exceed: + free(buf); + calloc_err: + close(fd); + open_read_file: + return ret; +} + +/* command: stop */ +static void acrnctl_stop_help(void) +{ + printf("acrnctl stop [vmname0] [vmname1] ...\n" + "\t run \"acrnctl list\" to get running VMs\n"); +} + +static int send_stop_msg(char *vmname) +{ + int fd, ret; + struct sockaddr_un addr; + struct vmm_msg msg; + struct timeval timeout; + fd_set rfd, wfd; + char buf[128]; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + printf("%s %d\n", __FUNCTION__, __LINE__); + ret = -1; + goto sock_err; + } + + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s-monitor.socket", + ACRN_DM_SOCK_ROOT, vmname); + + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + printf("%s %d\n", __FUNCTION__, __LINE__); + goto connect_err; + } + + msg.magic = VMM_MSG_MAGIC; + msg.msgid = REQ_STOP; + msg.len = sizeof(msg); + + timeout.tv_sec = 1; /* wait 1 second for read/write socket */ + timeout.tv_usec = 0; + FD_ZERO(&rfd); + FD_ZERO(&wfd); + FD_SET(fd, &rfd); + FD_SET(fd, &wfd); + + select(fd + 1, NULL, &wfd, NULL, &timeout); + + if (!FD_ISSET(fd, &wfd)) { + printf("%s %d\n", __FUNCTION__, __LINE__); + goto cant_write; + } + + ret = write(fd, &msg, sizeof(msg)); + + /* wait response */ + select(fd + 1, &rfd, NULL, NULL, &timeout); + + if (FD_ISSET(fd, &rfd)) { + memset(buf, 0, sizeof(buf)); + ret = read(fd, buf, sizeof(buf)); + if (ret <= sizeof(buf)) + process_msg((void*)&buf); + } + + cant_write: + connect_err: + close(fd); + sock_err: + return ret; +} + +static int acrnctl_do_stop(int argc, char *argv[]) +{ + struct vmm_struct *s; + int i; + + if (argc < 2) { + acrnctl_stop_help(); + return -1; + } + + if (!strcmp("help", argv[1])) { + acrnctl_stop_help(); + return 0; + } + + vmm_update(); + for (i = 1; i < argc; i++) { + s = vmm_find(argv[i]); + if (!s) { + printf("can't find %s\n", argv[i]); + continue; + } + if (s->state == VM_CREATED) { + printf("%s is already (%s)\n", argv[i], + state_str[s->state]); + continue; + } + send_stop_msg(argv[i]); + } + + return 0; +} + +/* command: delete */ +static void acrnctl_del_help(void) +{ + printf("acrnctl del [vmname0] [vmname1] ...\n" + "\t run \"acrnctl list\" get VM names\n"); +} + +static int acrnctl_do_del(int argc, char *argv[]) +{ + struct vmm_struct *s; + int i; + char cmd[128]; + + if (argc < 2) { + acrnctl_del_help(); + + return -1; + } + + if (!strcmp("help", argv[1])) { + acrnctl_del_help(); + return 0; + } + + vmm_update(); + for (i = 1; i < argc; i++) { + s = vmm_find(argv[i]); + if (!s) { + printf("can't find %s\n", argv[i]); + continue; + } + if (s->state != VM_CREATED) { + printf("can't delete %s(%s)\n", argv[i], + state_str[s->state]); + continue; + } + snprintf(cmd, sizeof(cmd), "rm -f %s/add/%s.sh", + ACRNCTL_OPT_ROOT, argv[i]); + system(cmd); + snprintf(cmd, sizeof(cmd), "rm -f %s/add/%s.args", + ACRNCTL_OPT_ROOT, argv[i]); + system(cmd); + } + + return 0; +} + +/* command: start */ +static void acrnctl_start_help(void) +{ + printf("acrnctl start [vmname]\n" + "\t run \"acrnctl list\" get VM names\n" + "\t each time user can only start one VM\n"); +} + +static int acrnctl_do_start(int argc, char *argv[]) +{ + struct vmm_struct *s; + char cmd[128]; + + if (argc != 2) { + acrnctl_start_help(); + return -1; + } + + if (!strcmp("help", argv[1])) { + acrnctl_start_help(); + return 0; + } + + vmm_update(); + s = vmm_find(argv[1]); + if (!s) { + printf("can't find %s\n", argv[1]); + return -1; + } + + if (s->state != VM_CREATED) { + printf("can't start %s(%s)\n", argv[1], state_str[s->state]); + return -1; + } + + snprintf(cmd, sizeof(cmd), "bash %s/add/%s.sh $(cat %s/add/%s.args)", + ACRNCTL_OPT_ROOT, argv[1], ACRNCTL_OPT_ROOT, argv[1]); + + system(cmd); + + return 0; +} + +#define ACMD(CMD,FUNC) \ +{.cmd = CMD, .func = FUNC,} + +struct acrnctl_cmd { + const char *cmd; + int (*func) (int argc, char *argv[]); +} acmds[] = { + ACMD("list", acrnctl_do_list), + ACMD("start", acrnctl_do_start), + ACMD("stop", acrnctl_do_stop), + ACMD("del", acrnctl_do_del), + ACMD("add", acrnctl_do_add), +}; + +#define NCMD (sizeof(acmds)/sizeof(struct acrnctl_cmd)) + +static void help_info(void) +{ + int i; + + printf("support:\n"); + for (i = 0; i < NCMD; i++) + printf("\t%s\n", acmds[i].cmd); + printf("Use acrnctl [cmd] help for details\n"); +} + +int main(int argc, char *argv[]) +{ + int i; + + if (argc == 1) { + help_info(); + return 0; + } + + acrnctl_bin_path = argv[0]; + + /* first check acrnctl reserved operations */ + if (!strcmp(argv[1], "gentmpfile")) { + printf("\nacrnctl: %s\n", argv[argc - 1]); + return 0; + } + + for (i = 0; i < NCMD; i++) + if (!strcmp(argv[1], acmds[i].cmd)) + return acmds[i].func(argc - 1, &argv[1]); + + /* Reach here means unsupported command */ + printf("Unknown command: %s\n", argv[1]); + help_info(); + + return -1; +}