#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include char xmlrpc_header[] = "POST /RPC2 HTTP/1.0\r\n" "Host: \r\n" "User-Agent: xmlrpclib.py/1.0.1 (by www.pythonware.com)\r\n" "Content-Type: text/xml\r\n" "Content-Length: %d\r\n" "\r\n"; char xmlrpc_body_restore[] = "\n" "\n" "xend.domain.restore\n" "\n" "\n" "%s\n" "\n" "\n" "0\n" "\n" "\n" "\n"; char xmlrpc_body_setmem[] = "\n\nxend.domain.setMemoryTarget\n\n\n%d\n\n\n%d\n\n\n\n"; void send_raw(int fd, char *body) { char *header; asprintf(&header, xmlrpc_header, strlen(body)); if (write(fd, header, strlen(header)) != strlen(header)) { perror("write xend"); exit(1); } if (write(fd, body, strlen(body)) != strlen(body)) { perror("write xend"); exit(1); } shutdown(fd, SHUT_WR); } void send_req_restore(int fd, char *name) { char *body; asprintf(&body, xmlrpc_body_restore, name); send_raw(fd, body); } void send_req_setmem(int fd, int domid, int mem) { char *body; asprintf(&body, xmlrpc_body_setmem, domid, mem); send_raw(fd, body); } char *recv_resp(int fd) { #define RESPSIZE 65536 static char buf[RESPSIZE]; int total = 0; int n; for (;;) { n = read(fd, buf + total, RESPSIZE - total); if (n == 0) { buf[total] = 0; close(fd); return buf; } if (n < 0) { perror("xend read"); exit(1); } total += n; } } void bad_resp(char *resp) { fprintf(stderr, "Error; Xend response:\n%s\n", resp); exit(1); } int parse_resp(char *resp) { char *domid; if (strstr(resp, "")) bad_resp(resp); if (!strstr(resp, "domid")) bad_resp(resp); domid = strstr(resp, ""); if (!domid) bad_resp(resp); return strtoul(domid + 5, NULL, 0); } char *gettime() { static char retbuf[60]; struct timeval tv; gettimeofday(&tv, NULL); snprintf(retbuf, sizeof(retbuf), "%lld.%lld", (long long) tv.tv_sec, (long long) tv.tv_usec); return retbuf; } int actually_do_unlink = 1; #define FAST_FLAG_PATH "/var/run/qubes/fast_block_attach" void set_fast_flag() { int fd = open(FAST_FLAG_PATH, O_CREAT | O_RDONLY, 0600); if (fd < 0) { perror("set_fast_flag"); exit(1); } close(fd); } void rm_fast_flag() { if (actually_do_unlink) unlink(FAST_FLAG_PATH); } #define BUFSIZE (512*1024) void do_read(int fd) { static char buf[BUFSIZE]; int n; while ((n = read(fd, buf, BUFSIZE))) { if (n < 0) { perror("read savefile"); exit(1); } } } void preload_cache(int fd) { signal(SIGCHLD, SIG_IGN); switch (fork()) { case -1: perror("fork"); exit(1); case 0: actually_do_unlink = 0; do_read(fd); fprintf(stderr, "time=%s, fs cache preload complete\n", gettime()); exit(0); default: close(fd); } } int xend_connect() { struct sockaddr_un server; int s; s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) { perror("socket af_unix"); exit(1); } server.sun_family = AF_UNIX; strcpy(server.sun_path, "/var/run/xend/xmlrpc.sock"); if (connect (s, (struct sockaddr *) &server, strlen(server.sun_path) + sizeof(server.sun_family))) { perror("connext xend"); exit(1); } return s; } void start_guid(int domid, int argc, char **argv) { int i; char dstr[40]; char *guid_args[argc + 2]; snprintf(dstr, sizeof(dstr), "%d", domid); guid_args[0] = "qubes_guid"; guid_args[1] = "-d"; guid_args[2] = dstr; for (i = 2; i < argc; i++) guid_args[i + 1] = argv[i]; guid_args[argc + 1] = NULL; execv("/usr/bin/qubes_guid", guid_args); perror("execv"); } // modify the savefile. fd = fd to the open savefile, // buf - already read 1st page of the savefile // pattern - pattern to search for // val - string to replace pattern with void fix_savefile(int fd, char *buf, char *pattern, char *val) { int i, len = strlen(val), origlen; char *bracket; char *loc = strstr(buf + 20, pattern) + strlen(pattern); if (!loc) return; bracket = index(loc, ')'); if (!bracket) return; origlen = (long) bracket - (long) loc; if (origlen < len) { fprintf(stderr, "too long string %s\n", val); exit(1); } for (i = 0; i < origlen - len; i++) loc[i] = ' '; memcpy(loc + i, val, strlen(val)); lseek(fd, (long) loc - (long) buf, SEEK_SET); write(fd, loc, origlen); } char *dispname_by_dispid(int dispid) { static char retbuf[16]; snprintf(retbuf, sizeof(retbuf), "disp%d", dispid); return retbuf; } char *build_dvm_ip(int netvm, int id) { static char buf[256]; snprintf(buf, sizeof(buf), "10.%d.%d.%d", netvm, id / 254 + 200, (id % 254) + 1); return buf; } #define NAME_PATTERN "/root-cow.img" // replaces the unique portions of the savefile with per-dvm values // returns the name of VM the savefile was taken for // by looking for /.../vmname/root-cow.img // normally, it should be "templatename-dvm" char *get_vmname_from_savefile(int fd) { static char buf[4096]; char *name; char *slash; lseek(fd, 0, SEEK_SET); if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { perror("read savefile"); exit(1); } buf[sizeof(buf) - 1] = 0; name = strstr(buf + 20, NAME_PATTERN); if (!name) { fprintf(stderr, "cannot find 'root-cow.img' in savefile\n"); exit(1); } *name = 0; slash = name - 1; while (slash[0] && slash[0] != '/') slash--; if (!*slash) { fprintf(stderr, "cannot find / in savefile\n"); exit(1); } return slash + 1; } void fix_savefile_all(int fd, int dispid, int netvm_id) { char val[256]; char buf[4096]; lseek(fd, 0, SEEK_SET); if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { perror("read savefile"); exit(1); } buf[sizeof(buf) - 1] = 0; snprintf(val, sizeof(val), "064cd14c-95ad-4fc2-a4c9-cf9f522e5b%02x", dispid); fix_savefile(fd, buf, "(uuid ", val); fix_savefile(fd, buf, "(name ", dispname_by_dispid(dispid)); snprintf(val, sizeof(val), "00:16:3e:7c:8b:%02x", dispid); fix_savefile(fd, buf, "(mac ", val); fix_savefile(fd, buf, "(ip ", build_dvm_ip(netvm_id, dispid)); } void unpack_cows(char *name) { char vmdir[4096]; char tarfile[4096]; int status; snprintf(vmdir, sizeof(vmdir), "/var/lib/qubes/appvms/%s", name); snprintf(tarfile, sizeof(tarfile), "/var/lib/qubes/appvms/%s/saved_cows.tar", name); switch (fork()) { case -1: fprintf(stderr, "fork"); exit(1); case 0: execl("/bin/tar", "tar", "-C", vmdir, "-Sxf", tarfile, NULL); perror("execl"); exit(1); default: wait(&status); if (WEXITSTATUS(status)) { fprintf(stderr, "tar exited with status=0x%x\n", status); exit(1); } fprintf(stderr, "time=%s, cows restored\n", gettime()); } } void write_xs_single(struct xs_handle *xs, int domid, char *name, char *val) { char key[256]; snprintf(key, sizeof(key), "/local/domain/%d/%s", domid, name); if (!xs_write(xs, XBT_NULL, key, val, strlen(val))) { fprintf(stderr, "xs_write"); exit(1); } } int get_netvm_id_from_name(char *name) { int fd, n; char netvm_id[256]; char netvm_id_path[256]; snprintf(netvm_id_path, sizeof(netvm_id_path), "/var/lib/qubes/appvms/%s/netvm_id.txt", name); fd = open(netvm_id_path, O_RDONLY); if (fd < 0) { perror("open netvm_id"); exit(1); } n = read(fd, netvm_id, sizeof(netvm_id) - 1); close(fd); netvm_id[n] = 0; return atoi(netvm_id); } void setup_xenstore(int netvm_id, int domid, int dvmid, char *name) { char val[256]; struct xs_handle *xs = xs_daemon_open(); if (!xs) { perror("xs_daemon_open"); exit(1); } write_xs_single(xs, domid, "qubes_ip", build_dvm_ip(netvm_id, dvmid)); write_xs_single(xs, domid, "qubes_netmask", "255.255.0.0"); snprintf(val, sizeof(val), "10.%d.0.1", netvm_id); write_xs_single(xs, domid, "qubes_gateway", val); snprintf(val, sizeof(val), "10.%d.255.254", netvm_id); write_xs_single(xs, domid, "qubes_secondary_dns", val); write_xs_single(xs, domid, "qubes_vm_type", "AppVM"); write_xs_single(xs, domid, "qubes_restore_complete", "True"); xs_daemon_close(xs); } int get_next_disposable_id() { int seq = 0; int fd = open("/var/run/qubes/dispVM_seq", O_RDWR); if (fd < 0) { perror("open dispVM_seq"); exit(1); } read(fd, &seq, sizeof(seq)); seq++; lseek(fd, 0, SEEK_SET); write(fd, &seq, sizeof(seq)); close(fd); return seq; } void write_varrun_domid(int domid, char *dispname, char *orig) { FILE *f = fopen("/var/run/qubes/dispVM_xid", "w"); if (!f) { perror("fopen dispVM_xid"); exit(1); } fprintf(f, "%d\n%s\n%s\n", domid, dispname, orig); fclose(f); } void redirect_stderr() { int fd = open("/var/log/qubes/qubes_restore.log", O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) { syslog(LOG_DAEMON | LOG_ERR, "open qubes_restore.log"); exit(1); } dup2(fd, 2); } int main(int argc, char **argv) { int fd, domid, dispid, netvm_id; char *resp; char *name; if (argc < 2) { fprintf(stderr, "usage: %s savefile [guid args] \n", argv[0]); exit(1); } redirect_stderr(); fprintf(stderr, "time=%s, starting\n", gettime()); set_fast_flag(); atexit(rm_fast_flag); fd = open(argv[1], O_RDWR); if (fd < 0) { perror("open savefile"); exit(1); } dispid = get_next_disposable_id(); name = get_vmname_from_savefile(fd); netvm_id = get_netvm_id_from_name(name); fix_savefile_all(fd, dispid, netvm_id); // printf("name=%s\n", name); unpack_cows(name); // no preloading for now, assume savefile in shm // preload_cache(fd); fd = xend_connect(); send_req_restore(fd, argv[1]); resp = recv_resp(fd); domid = parse_resp(resp); write_varrun_domid(domid, dispname_by_dispid(dispid), name); fprintf(stderr, "time=%s, created domid=%d, creating xenstore entries\n", gettime(), domid); #if 0 fd = xend_connect(); send_req_setmem(fd, domid, 400); resp = recv_resp(fd); // printf("%s\n", resp); fprintf(stderr, "time=%s, creating xenstore entries\n", gettime()); #endif setup_xenstore(netvm_id, domid, dispid, name); fprintf(stderr, "time=%s, starting qubes_guid\n", gettime()); rm_fast_flag(); start_guid(domid, argc, argv); return 0; }