Quick VM restore support

This commit is contained in:
Rafal Wojtczuk 2010-06-02 15:50:22 +02:00 committed by Joanna Rutkowska
parent 172cc9a0e9
commit 793b7b2596
11 changed files with 608 additions and 24 deletions

View File

@ -8,7 +8,7 @@
#
/dev/mapper/dmroot / ext4 defaults,noatime 1 1
/dev/mapper/dmswap swap swap defaults 0 0
/dev/xvdb /rw ext4 defaults 0 0
/dev/xvdb /rw ext4 noauto,defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0

View File

@ -14,6 +14,20 @@ start()
echo "ERROR: /usr/bin/xenstore-read not found!"
exit 1
fi
if xenstore-read qubes_save_request ; then
dmesg -c >/dev/null
# echo 1 >/proc/sys/vm/drop_caches
# free | grep buffers/cache |
# (read a b c d ; xenstore-write device/qubes_used_mem $c)
free | grep Mem: |
(read a b c d ; xenstore-write device/qubes_used_mem $c)
echo "Waiting for restore"
while ! dmesg -c | grep "using vcpu" ; do usleep 10 ; done
while ! xenstore-read qubes_vm_type 2>/dev/null ; do
usleep 10
done
echo Back to life.
fi
name=$(/usr/bin/xenstore-read name)
hostname $name
@ -29,30 +43,36 @@ start()
echo "nameserver $secondary_dns" >> /etc/resolv.conf
fi
if ! [ -d /rw/home ] ; then
echo
echo "--> Virgin boot of the VM: Linking /home to /rw/home"
mv /home /home.orig
mkdir -p /rw/config
mkdir -p /rw/home
ln -s /rw/home/ /home
cp -a /home.orig/user /home
touch /rw/config/rc.local
rm -fr /home.orig
touch /var/lib/qubes/first_boot_completed
fi
if [ -e /dev/xvdb ] ; then
mount /rw
if ! [ -L /home ] ; then
mv /home /home.orig
ln -s /rw/home /home
fi
if ! [ -L /usr/local ] ; then
mv /usr/local /usr/local.orig
ln -s /rw/usrlocal /usr/local
fi
if ! [ -d /rw/home ] ; then
echo
echo "--> Virgin boot of the VM: Linking /home to /rw/home"
mv /home /home.orig
mkdir -p /rw/config
mkdir -p /rw/home
ln -s /rw/home/ /home
cp -a /home.orig/user /home
touch /rw/config/rc.local
rm -fr /home.orig
touch /var/lib/qubes/first_boot_completed
fi
if ! [ -L /home ] ; then
mv /home /home.orig
ln -s /rw/home /home
fi
if ! [ -L /usr/local ] ; then
mv /usr/local /usr/local.orig
ln -s /rw/usrlocal /usr/local
fi
#make it last, we want all above to work without /rw mounted
if ! [ -d /rw/usrlocal ] ; then
cp -a /usr/local.orig /rw/usrlocal
if ! [ -d /rw/usrlocal ] ; then
cp -a /usr/local.orig /rw/usrlocal
fi
else
ln -sf /home_volatile /home
fi
[ -x /rw/config/rc.local ] && /rw/config/rc.local

2
appvm/qubes_timestamp Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
exec xenstore-write device/qubes_timestamp $(date +%s.%N)

View File

@ -30,6 +30,8 @@ start()
chgrp qubes /var/run/xenstored/*
chmod 660 /var/run/xenstored/*
xm sched-credit -d 0 -w 65535
printf "\x00\x00\x00\x00" > /var/run/qubes/dispVM_seq
xm mem-set 0 700
cp /var/lib/qubes/qubes.xml /var/lib/qubes/backup/qubes-$(date +%F-%T).xml
touch /var/lock/subsys/qubes_core
success

9
dom0/restore/Makefile Normal file
View File

@ -0,0 +1,9 @@
CC=gcc
all: qubes_restore xenstore-watch
qubes_restore: qubes_restore.o
$(CC) -o qubes_restore qubes_restore.o -lxenstore
xenstore-watch: xenstore-watch.o
$(CC) -o xenstore-watch xenstore-watch.o -lxenstore
clean:
rm -f *.o *~ qubes_restore xenstore-watch

54
dom0/restore/block.qubes Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
hd_arr[10]=a
hd_arr[11]=b
hd_arr[12]=c
hd_arr[13]=d
hd_arr[14]=e
hd_arr[15]=f
hexdigit()
{
if [ $1 -lt 10 ] ; then
RET=$1
else
RET=${hd_arr[$1]}
fi
}
hexnumber()
{
hexdigit $(($1/16))
ret2=$RET
hexdigit $(($1%16))
HEXNUMBER="$ret2"$RET
}
process()
{
if ! [ "x""$1" = "xfile" ] ; then
exec /etc/xen/scripts/block "$@"
fi
while true ; do
dev=$(losetup -f --show $2)
if [ -n "$dev" ] ; then break ; fi
done
hexnumber ${dev:9:70}
xenstore-write "$XENBUS_PATH/node" "$dev" \
"$XENBUS_PATH/physical-device" "7:"$HEXNUMBER \
"$XENBUS_PATH/hotplug-status" connected
}
#exec 2>>/tmp/block.$$
#set -x
export PATH="/sbin:/bin:/usr/bin:/usr/sbin:$PATH"
XENBUS_PATH="${XENBUS_PATH:?}"
if ! [ "$1" = "add" ] || ! [ -f /var/run/qubes/fast_block_attach ] ; then
exec /etc/xen/scripts/block "$@"
fi
vars=$(xenstore-read "$XENBUS_PATH/type" "$XENBUS_PATH/params")
process $vars
exit 0

View File

@ -0,0 +1,42 @@
#!/bin/sh
if ! [ $# = 2 ] ; then
echo usage: $0 domainname savefile_to_be_created
exit 1
fi
VMDIR=/var/lib/qubes/appvms/$1
if ! [ -d $VMDIR ] ; then
echo $VMDIR does not exist ?
exit 1
fi
if ! qvm-start $1 --no-guid ; then
exit 1
fi
ID=none
for i in $(xenstore-list /local/domain) ; do
name=$(xenstore-read /local/domain/$i/name)
if [ "x"$name = "x"$1 ] ; then
ID=$i
fi
done
set -x
if [ $ID = none ] ; then
echo cannot get domain id
exit 1
fi
echo domainid=$ID
xenstore-write /local/domain/$ID/qubes_save_request 1
xenstore-watch /local/domain/$ID/device/qubes_used_mem
xenstore-read /local/domain/$ID/qubes_gateway | \
cut -d . -f 2 | tr -d "\n" > $VMDIR/netvm_id.txt
xm block-detach $1 /dev/xvdb
MEM=$(xenstore-read /local/domain/$ID/device/qubes_used_mem)
echo MEM=$MEM
xm mem-set $1 $(($MEM/1000))
sleep 1
touch $2
xm save $1 $2
cd $VMDIR
tar -Scvf saved_cows.tar root-cow.img swap-cow.img

View File

@ -0,0 +1,406 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <xs.h>
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[] =
"<?xml version='1.0'?>\n"
"<methodCall>\n"
"<methodName>xend.domain.restore</methodName>\n"
"<params>\n"
"<param>\n"
"<value><string>%s</string></value>\n"
"</param>\n"
"<param>\n"
"<value><boolean>0</boolean></value>\n"
"</param>\n" "</params>\n" "</methodCall>\n";
char xmlrpc_body_setmem[] =
"<?xml version='1.0'?>\n<methodCall>\n<methodName>xend.domain.setMemoryTarget</methodName>\n<params>\n<param>\n<value><string>%d</string></value>\n</param>\n<param>\n<value><int>%d</int></value>\n</param>\n</params>\n</methodCall>\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, "<fault>"))
bad_resp(resp);
if (!strstr(resp, "domid"))
bad_resp(resp);
domid = strstr(resp, "<int>");
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)
{
char dstr[40];
snprintf(dstr, sizeof(dstr), "%d", domid);
if (argc == 2)
execl("/usr/bin/qubes_guid", "guid", "-d", dstr, "-c",
"red", "-i", "red", NULL);
else
execl("/usr/bin/qubes_guid", "guid", "-d", dstr, "-c",
"red", "-i", "red", "-e", argv[2], NULL);
perror("execl");
}
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);
}
#define NAME_PATTERN "/root-cow.img"
char *fix_savefile_and_get_vmname(int fd, int dispid)
{
static char buf[4096];
char *name;
char *slash;
char val[256];
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);
snprintf(val, sizeof(val), "disp%02d", dispid);
fix_savefile(fd, buf, "(name ", val);
snprintf(val, sizeof(val), "00:16:3e:7c:8b:%02x", dispid);
fix_savefile(fd, buf, "(mac ", val);
lseek(fd, 0, SEEK_SET);
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 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);
}
}
void setup_xenstore(int domid, char *name)
{
char val[256];
char netvm_id_path[256];
int fd, n;
char netvm_id[256];
struct xs_handle *xs = xs_daemon_open();
if (!xs) {
perror("xs_daemon_open");
exit(1);
}
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;
snprintf(val, sizeof(val), "10.%s.%d.%d", netvm_id,
domid / 254 + 200, (domid % 254) + 1);
write_xs_single(xs, domid, "qubes_ip", val);
write_xs_single(xs, domid, "qubes_netmask", "255.255.0.0");
snprintf(val, sizeof(val), "10.%s.0.1", netvm_id);
write_xs_single(xs, domid, "qubes_gateway", val);
snprintf(val, sizeof(val), "10.%s.255.254", netvm_id);
write_xs_single(xs, domid, "qubes_secondary_dns", val);
write_xs_single(xs, domid, "qubes_vm_type", "AppVM");
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)
{
FILE *f = fopen("/var/run/qubes/dispVM_xid", "w");
if (!f) {
perror("fopen dispVM_xid");
exit(1);
}
fprintf(f, "%d", domid);
fclose(f);
}
int main(int argc, char **argv)
{
int fd, domid, dispid;
char *resp;
char *name;
if (argc != 2 && argc != 3) {
fprintf(stderr, "usage: %s savefile\n", argv[0]);
exit(1);
}
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 = fix_savefile_and_get_vmname(fd, dispid);
// 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);
fprintf(stderr,
"time=%s, created domid=%d, executing set_mem 400\n",
gettime(), domid);
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());
setup_xenstore(domid, name);
fprintf(stderr, "time=%s, starting qubes_guid\n", gettime());
rm_fast_flag();
start_guid(domid, argc, argv);
return 0;
}

View File

@ -0,0 +1,28 @@
#include <sys/types.h>
#include <xs.h>
#include <stdio.h>
#include <stdlib.h>
main(int argc, char **argv)
{
struct xs_handle *xs;
unsigned int count;
char **vec;
char dummy;
if (argc != 2) {
fprintf(stderr, "usage: %s xenstore_path\n", argv[0]);
exit(1);
}
xs = xs_daemon_open();
if (!xs) {
perror("xs_daemon_open");
exit(1);
}
if (!xs_watch(xs, argv[1], &dummy)) {
perror("xs_watch");
exit(1);
}
vec = xs_read_watch(xs, &count);
free(vec);
vec = xs_read_watch(xs, &count);
free(vec);
}

View File

@ -60,7 +60,7 @@ mkdir -p $RPM_BUILD_ROOT/etc/init.d
cp qubes_core $RPM_BUILD_ROOT/etc/init.d/
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes
mkdir -p $RPM_BUILD_ROOT/usr/bin
cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/bin
cp qubes_timestamp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/bin
mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir}
cp qvm-copy.desktop $RPM_BUILD_ROOT/%{kde_service_dir}
mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d
@ -77,6 +77,9 @@ cp ../common/qubes_serial_login $RPM_BUILD_ROOT/sbin
mkdir -p $RPM_BUILD_ROOT/etc
cp ../common/serial.conf $RPM_BUILD_ROOT/var/lib/qubes/
mkdir -p $RPM_BUILD_ROOT/home_volatile/user
chown 500:500 $RPM_BUILD_ROOT/home_volatile/user
%triggerin -- initscripts
cp /var/lib/qubes/serial.conf /etc/init/serial.conf
@ -186,3 +189,6 @@ rm -rf $RPM_BUILD_ROOT
%dir /mnt/removable
/etc/yum.repos.d/qubes.repo
/sbin/qubes_serial_login
/usr/bin/qubes_timestamp
%dir /home_volatile
%attr(700,user,user) /home_volatile/user

View File

@ -46,6 +46,7 @@ The Qubes core files for installation on Dom0.
%build
python -m compileall qvm-core
python -O -m compileall qvm-core
make -C restore
%install
@ -57,6 +58,12 @@ mkdir -p $RPM_BUILD_ROOT/usr/bin/
cp qvm-tools/qvm-* $RPM_BUILD_ROOT/usr/bin
cp clipboard_notifier/qclipd $RPM_BUILD_ROOT/usr/bin
cp pendrive_swapper/qfilexchgd $RPM_BUILD_ROOT/usr/bin
cp restore/xenstore-watch $RPM_BUILD_ROOT/usr/bin
cp restore/qubes_restore $RPM_BUILD_ROOT/usr/bin
cp restore/qubes_prepare_saved_domain.sh $RPM_BUILD_ROOT/usr/bin
mkdir -p $RPM_BUILD_ROOT/etc/xen/scripts
cp restore/block.qubes $RPM_BUILD_ROOT/etc/xen/scripts
mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp qvm-core/qubes.py $RPM_BUILD_ROOT%{python_sitearch}/qubes
@ -99,6 +106,9 @@ mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
cp pm-utils/01qubes-sync-vms-clock $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
cp pm-utils/02qubes-pause-vms $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
%triggerin -- xen-runtime
sed -i 's/\/block /\/block.qubes /' /etc/udev/rules.d/xen-backend.rules
%post
if [ -e /etc/yum.repos.d/qubes-r1-dom0.repo ]; then
@ -167,6 +177,7 @@ if [ "$1" = 0 ] ; then
chgrp root /etc/xen
chmod 700 /etc/xen
groupdel qubes
sed -i 's/\/block.qubes /\/block /' /etc/udev/rules.d/xen-backend.rules
fi
%files
@ -202,3 +213,7 @@ fi
/etc/sysconfig/iptables
/usr/lib64/pm-utils/sleep.d/01qubes-sync-vms-clock
/usr/lib64/pm-utils/sleep.d/02qubes-pause-vms
/usr/bin/xenstore-watch
/usr/bin/qubes_restore
/usr/bin/qubes_prepare_saved_domain.sh
/etc/xen/scripts/block.qubes