| /* |
| * VM utilities |
| * |
| * Copyright (c) 2017 Fabrice Bellard |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <time.h> |
| |
| #include "cutils.h" |
| #include "iomem.h" |
| #include "virtio.h" |
| #include "machine.h" |
| #include "fs_utils.h" |
| #ifdef CONFIG_FS_NET |
| #include "fs_wget.h" |
| #endif |
| |
| void __attribute__((format(printf, 1, 2))) vm_error(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| #ifdef EMSCRIPTEN |
| vprintf(fmt, ap); |
| #else |
| vfprintf(stderr, fmt, ap); |
| #endif |
| va_end(ap); |
| } |
| |
| int vm_get_int(JSONValue obj, const char *name, int *pval) |
| { |
| JSONValue val; |
| val = json_object_get(obj, name); |
| if (json_is_undefined(val)) { |
| vm_error("expecting '%s' property\n", name); |
| return -1; |
| } |
| if (val.type != JSON_INT) { |
| vm_error("%s: integer expected\n", name); |
| return -1; |
| } |
| *pval = val.u.int32; |
| return 0; |
| } |
| |
| int vm_get_int_opt(JSONValue obj, const char *name, int *pval, int def_val) |
| { |
| JSONValue val; |
| val = json_object_get(obj, name); |
| if (json_is_undefined(val)) { |
| *pval = def_val; |
| return 0; |
| } |
| if (val.type != JSON_INT) { |
| vm_error("%s: integer expected\n", name); |
| return -1; |
| } |
| *pval = val.u.int32; |
| return 0; |
| } |
| |
| static int vm_get_str2(JSONValue obj, const char *name, const char **pstr, |
| BOOL is_opt) |
| { |
| JSONValue val; |
| val = json_object_get(obj, name); |
| if (json_is_undefined(val)) { |
| if (is_opt) { |
| *pstr = NULL; |
| return 0; |
| } else { |
| vm_error("expecting '%s' property\n", name); |
| return -1; |
| } |
| } |
| if (val.type != JSON_STR) { |
| vm_error("%s: string expected\n", name); |
| return -1; |
| } |
| *pstr = val.u.str->data; |
| return 0; |
| } |
| |
| static int vm_get_str(JSONValue obj, const char *name, const char **pstr) |
| { |
| return vm_get_str2(obj, name, pstr, FALSE); |
| } |
| |
| static int vm_get_str_opt(JSONValue obj, const char *name, const char **pstr) |
| { |
| return vm_get_str2(obj, name, pstr, TRUE); |
| } |
| |
| static char *strdup_null(const char *str) |
| { |
| if (!str) |
| return NULL; |
| else |
| return strdup(str); |
| } |
| |
| /* currently only for "TZ" */ |
| static char *cmdline_subst(const char *cmdline) |
| { |
| DynBuf dbuf; |
| const char *p; |
| char var_name[32], *q, buf[32]; |
| |
| dbuf_init(&dbuf); |
| p = cmdline; |
| while (*p != '\0') { |
| if (p[0] == '$' && p[1] == '{') { |
| p += 2; |
| q = var_name; |
| while (*p != '\0' && *p != '}') { |
| if ((q - var_name) < sizeof(var_name) - 1) |
| *q++ = *p; |
| p++; |
| } |
| *q = '\0'; |
| if (*p == '}') |
| p++; |
| if (!strcmp(var_name, "TZ")) { |
| time_t ti; |
| struct tm tm; |
| int n, sg; |
| /* get the offset to UTC */ |
| time(&ti); |
| localtime_r(&ti, &tm); |
| n = tm.tm_gmtoff / 60; |
| sg = '-'; |
| if (n < 0) { |
| sg = '+'; |
| n = -n; |
| } |
| snprintf(buf, sizeof(buf), "UTC%c%02d:%02d", |
| sg, n / 60, n % 60); |
| dbuf_putstr(&dbuf, buf); |
| } |
| } else { |
| dbuf_putc(&dbuf, *p++); |
| } |
| } |
| dbuf_putc(&dbuf, 0); |
| return (char *)dbuf.buf; |
| } |
| |
| static BOOL find_name(const char *name, const char *name_list) |
| { |
| size_t len; |
| const char *p, *r; |
| |
| p = name_list; |
| for(;;) { |
| r = strchr(p, ','); |
| if (!r) { |
| if (!strcmp(name, p)) |
| return TRUE; |
| break; |
| } else { |
| len = r - p; |
| if (len == strlen(name) && !memcmp(name, p, len)) |
| return TRUE; |
| p = r + 1; |
| } |
| } |
| return FALSE; |
| } |
| |
| static const VirtMachineClass *virt_machine_list[] = { |
| #if defined(EMSCRIPTEN) |
| /* only a single machine in the EMSCRIPTEN target */ |
| #ifndef CONFIG_X86EMU |
| &riscv_machine_class, |
| #endif |
| #else |
| &riscv_machine_class, |
| #endif /* !EMSCRIPTEN */ |
| #ifdef CONFIG_X86EMU |
| &pc_machine_class, |
| #endif |
| NULL, |
| }; |
| |
| static const VirtMachineClass *virt_machine_find_class(const char *machine_name) |
| { |
| const VirtMachineClass *vmc, **pvmc; |
| |
| for(pvmc = virt_machine_list; *pvmc != NULL; pvmc++) { |
| vmc = *pvmc; |
| if (find_name(machine_name, vmc->machine_names)) |
| return vmc; |
| } |
| return NULL; |
| } |
| |
| static int virt_machine_parse_config(VirtMachineParams *p, |
| char *config_file_str, int len) |
| { |
| int version, val; |
| const char *tag_name, *str; |
| char buf1[256]; |
| JSONValue cfg, obj, el; |
| |
| cfg = json_parse_value_len(config_file_str, len); |
| if (json_is_error(cfg)) { |
| vm_error("error: %s\n", json_get_error(cfg)); |
| json_free(cfg); |
| return -1; |
| } |
| |
| if (vm_get_int(cfg, "version", &version) < 0) |
| goto tag_fail; |
| if (version != VM_CONFIG_VERSION) { |
| if (version > VM_CONFIG_VERSION) { |
| vm_error("The emulator is too old to run this VM: please upgrade\n"); |
| return -1; |
| } else { |
| vm_error("The VM configuration file is too old for this emulator version: please upgrade the VM configuration file\n"); |
| return -1; |
| } |
| } |
| |
| if (vm_get_str(cfg, "machine", &str) < 0) |
| goto tag_fail; |
| p->machine_name = strdup(str); |
| p->vmc = virt_machine_find_class(p->machine_name); |
| if (!p->vmc) { |
| vm_error("Unknown machine name: %s\n", p->machine_name); |
| goto tag_fail; |
| } |
| p->vmc->virt_machine_set_defaults(p); |
| |
| tag_name = "memory_size"; |
| if (vm_get_int(cfg, tag_name, &val) < 0) |
| goto tag_fail; |
| p->ram_size = (uint64_t)val << 20; |
| |
| tag_name = "bios"; |
| if (vm_get_str_opt(cfg, tag_name, &str) < 0) |
| goto tag_fail; |
| if (str) { |
| p->files[VM_FILE_BIOS].filename = strdup(str); |
| } |
| |
| tag_name = "kernel"; |
| if (vm_get_str_opt(cfg, tag_name, &str) < 0) |
| goto tag_fail; |
| if (str) { |
| p->files[VM_FILE_KERNEL].filename = strdup(str); |
| } |
| |
| if (vm_get_str_opt(cfg, "cmdline", &str) < 0) |
| goto tag_fail; |
| if (str) { |
| p->cmdline = cmdline_subst(str); |
| } |
| |
| for(;;) { |
| snprintf(buf1, sizeof(buf1), "drive%d", p->drive_count); |
| obj = json_object_get(cfg, buf1); |
| if (json_is_undefined(obj)) |
| break; |
| if (p->drive_count >= MAX_DRIVE_DEVICE) { |
| vm_error("Too many drives\n"); |
| return -1; |
| } |
| if (vm_get_str(obj, "file", &str) < 0) |
| goto tag_fail; |
| p->tab_drive[p->drive_count].filename = strdup(str); |
| if (vm_get_str_opt(obj, "device", &str) < 0) |
| goto tag_fail; |
| p->tab_drive[p->drive_count].device = strdup_null(str); |
| p->drive_count++; |
| } |
| |
| for(;;) { |
| snprintf(buf1, sizeof(buf1), "fs%d", p->fs_count); |
| obj = json_object_get(cfg, buf1); |
| if (json_is_undefined(obj)) |
| break; |
| if (p->fs_count >= MAX_DRIVE_DEVICE) { |
| vm_error("Too many filesystems\n"); |
| return -1; |
| } |
| if (vm_get_str(obj, "file", &str) < 0) |
| goto tag_fail; |
| p->tab_fs[p->fs_count].filename = strdup(str); |
| if (vm_get_str_opt(obj, "tag", &str) < 0) |
| goto tag_fail; |
| if (!str) { |
| if (p->fs_count == 0) |
| strcpy(buf1, "/dev/root"); |
| else |
| snprintf(buf1, sizeof(buf1), "/dev/root%d", p->fs_count); |
| str = buf1; |
| } |
| p->tab_fs[p->fs_count].tag = strdup(str); |
| p->fs_count++; |
| } |
| |
| for(;;) { |
| snprintf(buf1, sizeof(buf1), "eth%d", p->eth_count); |
| obj = json_object_get(cfg, buf1); |
| if (json_is_undefined(obj)) |
| break; |
| if (p->eth_count >= MAX_ETH_DEVICE) { |
| vm_error("Too many ethernet interfaces\n"); |
| return -1; |
| } |
| if (vm_get_str(obj, "driver", &str) < 0) |
| goto tag_fail; |
| p->tab_eth[p->eth_count].driver = strdup(str); |
| if (!strcmp(str, "tap")) { |
| if (vm_get_str(obj, "ifname", &str) < 0) |
| goto tag_fail; |
| p->tab_eth[p->eth_count].ifname = strdup(str); |
| } |
| p->eth_count++; |
| } |
| |
| p->display_device = NULL; |
| obj = json_object_get(cfg, "display0"); |
| if (!json_is_undefined(obj)) { |
| if (vm_get_str(obj, "device", &str) < 0) |
| goto tag_fail; |
| p->display_device = strdup(str); |
| if (vm_get_int(obj, "width", &p->width) < 0) |
| goto tag_fail; |
| if (vm_get_int(obj, "height", &p->height) < 0) |
| goto tag_fail; |
| if (vm_get_str_opt(obj, "vga_bios", &str) < 0) |
| goto tag_fail; |
| if (str) { |
| p->files[VM_FILE_VGA_BIOS].filename = strdup(str); |
| } |
| } |
| |
| if (vm_get_str_opt(cfg, "input_device", &str) < 0) |
| goto tag_fail; |
| p->input_device = strdup_null(str); |
| |
| if (vm_get_str_opt(cfg, "accel", &str) < 0) |
| goto tag_fail; |
| if (str) { |
| if (!strcmp(str, "none")) { |
| p->accel_enable = FALSE; |
| } else if (!strcmp(str, "auto")) { |
| p->accel_enable = TRUE; |
| } else { |
| vm_error("unsupported 'accel' config: %s\n", str); |
| return -1; |
| } |
| } |
| |
| tag_name = "rtc_local_time"; |
| el = json_object_get(cfg, tag_name); |
| if (!json_is_undefined(el)) { |
| if (el.type != JSON_BOOL) { |
| vm_error("%s: boolean expected\n", tag_name); |
| goto tag_fail; |
| } |
| p->rtc_local_time = el.u.b; |
| } |
| |
| json_free(cfg); |
| return 0; |
| tag_fail: |
| json_free(cfg); |
| return -1; |
| } |
| |
| typedef void FSLoadFileCB(void *opaque, uint8_t *buf, int buf_len); |
| |
| typedef struct { |
| VirtMachineParams *vm_params; |
| void (*start_cb)(void *opaque); |
| void *opaque; |
| |
| FSLoadFileCB *file_load_cb; |
| void *file_load_opaque; |
| int file_index; |
| } VMConfigLoadState; |
| |
| static void config_file_loaded(void *opaque, uint8_t *buf, int buf_len); |
| static void config_additional_file_load(VMConfigLoadState *s); |
| static void config_additional_file_load_cb(void *opaque, |
| uint8_t *buf, int buf_len); |
| |
| /* XXX: win32, URL */ |
| char *get_file_path(const char *base_filename, const char *filename) |
| { |
| int len, len1; |
| char *fname, *p; |
| |
| if (!base_filename) |
| goto done; |
| if (strchr(filename, ':')) |
| goto done; /* full URL */ |
| if (filename[0] == '/') |
| goto done; |
| p = strrchr(base_filename, '/'); |
| if (!p) { |
| done: |
| return strdup(filename); |
| } |
| len = p + 1 - base_filename; |
| len1 = strlen(filename); |
| fname = malloc(len + len1 + 1); |
| memcpy(fname, base_filename, len); |
| memcpy(fname + len, filename, len1 + 1); |
| return fname; |
| } |
| |
| |
| #ifdef EMSCRIPTEN |
| static int load_file(uint8_t **pbuf, const char *filename) |
| { |
| abort(); |
| } |
| #else |
| /* return -1 if error. */ |
| static int load_file(uint8_t **pbuf, const char *filename) |
| { |
| FILE *f; |
| int size; |
| uint8_t *buf; |
| |
| f = fopen(filename, "rb"); |
| if (!f) { |
| perror(filename); |
| exit(1); |
| } |
| fseek(f, 0, SEEK_END); |
| size = ftell(f); |
| fseek(f, 0, SEEK_SET); |
| buf = malloc(size); |
| if (fread(buf, 1, size, f) != size) { |
| fprintf(stderr, "%s: read error\n", filename); |
| exit(1); |
| } |
| fclose(f); |
| *pbuf = buf; |
| return size; |
| } |
| #endif |
| |
| #ifdef CONFIG_FS_NET |
| static void config_load_file_cb(void *opaque, int err, void *data, size_t size) |
| { |
| VMConfigLoadState *s = opaque; |
| |
| // printf("err=%d data=%p size=%ld\n", err, data, size); |
| if (err < 0) { |
| vm_error("Error %d while loading file\n", -err); |
| exit(1); |
| } |
| s->file_load_cb(s->file_load_opaque, data, size); |
| } |
| #endif |
| |
| static void config_load_file(VMConfigLoadState *s, const char *filename, |
| FSLoadFileCB *cb, void *opaque) |
| { |
| // printf("loading %s\n", filename); |
| #ifdef CONFIG_FS_NET |
| if (is_url(filename)) { |
| s->file_load_cb = cb; |
| s->file_load_opaque = opaque; |
| fs_wget(filename, NULL, NULL, s, config_load_file_cb, TRUE); |
| } else |
| #endif |
| { |
| uint8_t *buf; |
| int size; |
| size = load_file(&buf, filename); |
| cb(opaque, buf, size); |
| free(buf); |
| } |
| } |
| |
| void virt_machine_load_config_file(VirtMachineParams *p, |
| const char *filename, |
| void (*start_cb)(void *opaque), |
| void *opaque) |
| { |
| VMConfigLoadState *s; |
| |
| s = mallocz(sizeof(*s)); |
| s->vm_params = p; |
| s->start_cb = start_cb; |
| s->opaque = opaque; |
| p->cfg_filename = strdup(filename); |
| |
| config_load_file(s, filename, config_file_loaded, s); |
| } |
| |
| static void config_file_loaded(void *opaque, uint8_t *buf, int buf_len) |
| { |
| VMConfigLoadState *s = opaque; |
| VirtMachineParams *p = s->vm_params; |
| |
| if (virt_machine_parse_config(p, (char *)buf, buf_len) < 0) |
| exit(1); |
| |
| /* load the additional files */ |
| s->file_index = 0; |
| config_additional_file_load(s); |
| } |
| |
| static void config_additional_file_load(VMConfigLoadState *s) |
| { |
| VirtMachineParams *p = s->vm_params; |
| while (s->file_index < VM_FILE_COUNT && |
| p->files[s->file_index].filename == NULL) { |
| s->file_index++; |
| } |
| if (s->file_index == VM_FILE_COUNT) { |
| if (s->start_cb) |
| s->start_cb(s->opaque); |
| free(s); |
| } else { |
| char *fname; |
| |
| fname = get_file_path(p->cfg_filename, |
| p->files[s->file_index].filename); |
| config_load_file(s, fname, |
| config_additional_file_load_cb, s); |
| free(fname); |
| } |
| } |
| |
| static void config_additional_file_load_cb(void *opaque, |
| uint8_t *buf, int buf_len) |
| { |
| VMConfigLoadState *s = opaque; |
| VirtMachineParams *p = s->vm_params; |
| |
| p->files[s->file_index].buf = malloc(buf_len); |
| memcpy(p->files[s->file_index].buf, buf, buf_len); |
| p->files[s->file_index].len = buf_len; |
| |
| /* load the next files */ |
| s->file_index++; |
| config_additional_file_load(s); |
| } |
| |
| void vm_add_cmdline(VirtMachineParams *p, const char *cmdline) |
| { |
| char *new_cmdline, *old_cmdline; |
| if (cmdline[0] == '!') { |
| new_cmdline = strdup(cmdline + 1); |
| } else { |
| old_cmdline = p->cmdline; |
| if (!old_cmdline) |
| old_cmdline = ""; |
| new_cmdline = malloc(strlen(old_cmdline) + 1 + strlen(cmdline) + 1); |
| strcpy(new_cmdline, old_cmdline); |
| strcat(new_cmdline, " "); |
| strcat(new_cmdline, cmdline); |
| } |
| free(p->cmdline); |
| p->cmdline = new_cmdline; |
| } |
| |
| void virt_machine_free_config(VirtMachineParams *p) |
| { |
| int i; |
| |
| free(p->machine_name); |
| free(p->cmdline); |
| for(i = 0; i < VM_FILE_COUNT; i++) { |
| free(p->files[i].filename); |
| free(p->files[i].buf); |
| } |
| for(i = 0; i < p->drive_count; i++) { |
| free(p->tab_drive[i].filename); |
| free(p->tab_drive[i].device); |
| } |
| for(i = 0; i < p->fs_count; i++) { |
| free(p->tab_fs[i].filename); |
| free(p->tab_fs[i].tag); |
| } |
| for(i = 0; i < p->eth_count; i++) { |
| free(p->tab_eth[i].driver); |
| free(p->tab_eth[i].ifname); |
| } |
| free(p->input_device); |
| free(p->display_device); |
| free(p->cfg_filename); |
| } |
| |
| VirtMachine *virt_machine_init(const VirtMachineParams *p) |
| { |
| const VirtMachineClass *vmc = p->vmc; |
| return vmc->virt_machine_init(p); |
| } |
| |
| void virt_machine_set_defaults(VirtMachineParams *p) |
| { |
| memset(p, 0, sizeof(*p)); |
| } |
| |
| void virt_machine_end(VirtMachine *s) |
| { |
| s->vmc->virt_machine_end(s); |
| } |