/* Copyright (C) 2014 Stony Brook University
This file is part of Graphene Library OS.
Graphene Library OS is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Graphene Library OS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see . */
/*
* shim_migrate.c
*
* Implementation of system call "checkpoint" and "restore".
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* cp_session objects are on the cp_sessions list, by the list field */
/* cp_threads are organized onto a list, handing off of the
* cp_session->registered_threads list. */
DEFINE_LIST(cp_thread);
struct cp_thread {
struct shim_thread* thread;
LIST_TYPE(cp_thread) list;
};
DEFINE_LIST(cp_session);
DEFINE_LISTP(cp_thread);
struct cp_session {
IDTYPE sid;
struct shim_handle* cpfile;
LISTP_TYPE(cp_thread) registered_threads;
LIST_TYPE(cp_session) list;
PAL_HANDLE finish_event;
struct shim_cp_store cpstore;
};
DEFINE_LISTP(cp_session);
LISTP_TYPE(cp_session) cp_sessions;
int create_checkpoint(const char* cpdir, IDTYPE* sid) {
struct cp_session* cpsession = malloc(sizeof(struct cp_session));
if (!cpsession)
return -ENOMEM;
int ret = 0;
INIT_LISTP(&cpsession->registered_threads);
INIT_LIST_HEAD(cpsession, list);
cpsession->finish_event = DkNotificationEventCreate(PAL_FALSE);
cpsession->cpfile = NULL;
int len = strlen(cpdir);
char* filename = __alloca(len + 10);
memcpy(filename, cpdir, len);
filename[len] = '/';
snprintf(filename + len + 1, 9, "%08x", cur_process.vmid);
cpsession->cpfile = get_new_handle();
if (!cpsession->cpfile) {
ret = -ENOMEM;
goto err;
}
/* the directory might not be created. At least try to create it */
if ((ret = open_namei(NULL, NULL, cpdir, O_CREAT | O_DIRECTORY, 0700, NULL)) < 0 &&
ret != -EEXIST)
goto err;
if ((ret = open_namei(cpsession->cpfile, NULL, filename, O_CREAT | O_EXCL | O_RDWR, 0600,
NULL)) < 0)
goto err;
get_handle(cpsession->cpfile);
MASTER_LOCK();
struct cp_session* s;
if (*sid) {
LISTP_FOR_EACH_ENTRY(s, &cp_sessions, list) {
if (s->sid == *sid) {
ret = 0;
goto err_locked;
}
}
} else {
retry:
ret = DkRandomBitsRead(&cpsession->sid, sizeof(cpsession->sid));
if (ret < 0) {
ret = -convert_pal_errno(-ret);
goto err_locked;
}
LISTP_FOR_EACH_ENTRY(s, &cp_sessions, list) {
if (s->sid == cpsession->sid)
goto retry;
}
*sid = cpsession->sid;
}
LISTP_ADD_TAIL(cpsession, &cp_sessions, list);
MASTER_UNLOCK();
return 0;
err_locked:
MASTER_UNLOCK();
err:
if (cpsession->cpfile)
put_handle(cpsession->cpfile);
DkObjectClose(cpsession->finish_event);
free(cpsession);
return ret;
}
static int finish_checkpoint(struct cp_session* session);
static int check_thread(struct shim_thread* thread, void* arg, bool* unlocked) {
__UNUSED(unlocked); // Retained for API compatibility
LISTP_TYPE(cp_thread)* registered = (LISTP_TYPE(cp_thread)*)arg;
struct cp_thread* t;
if (!thread->in_vm || !thread->is_alive)
return 0;
LISTP_FOR_EACH_ENTRY(t, registered, list) {
if (t->thread == thread)
return 0;
}
return 1;
}
int join_checkpoint(struct shim_thread* thread, IDTYPE sid) {
struct cp_session* s;
struct cp_session* cpsession = NULL;
struct cp_thread cpthread;
int ret = 0;
bool do_checkpoint = false;
MASTER_LOCK();
LISTP_FOR_EACH_ENTRY(s, &cp_sessions, list) {
if (s->sid == sid) {
cpsession = s;
break;
}
}
if (!cpsession) {
MASTER_UNLOCK();
return -EINVAL;
}
INIT_LIST_HEAD(&cpthread, list);
cpthread.thread = thread;
LISTP_ADD_TAIL(&cpthread, &cpsession->registered_threads, list);
/* find out if there is any thread that is not registered yet */
ret = walk_thread_list(&check_thread, &cpsession->registered_threads);
if (ret == -ESRCH)
do_checkpoint = true;
PAL_HANDLE finish_event = cpsession->finish_event;
MASTER_UNLOCK();
if (!do_checkpoint) {
debug("waiting for checkpointing\n");
object_wait_with_retry(finish_event);
return 0;
}
debug("ready for checkpointing\n");
ret = finish_checkpoint(cpsession);
if (ret < 0)
debug("failed creating checkpoint\n");
else
debug("finish checkpointing, time to wake up all threads\n");
DkEventSet(finish_event);
return ret;
}
static void* file_alloc(struct shim_cp_store* store, void* addr, size_t size) {
assert(store->cp_file);
struct shim_mount* fs = store->cp_file->fs;
if (!fs || !fs->fs_ops || !fs->fs_ops->truncate || !fs->fs_ops->mmap)
return NULL;
if (fs->fs_ops->truncate(store->cp_file, size) < 0)
return NULL;
if (fs->fs_ops->mmap(store->cp_file, &addr, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED,
0) < 0)
return NULL;
return addr;
}
static int finish_checkpoint(struct cp_session* cpsession) {
struct shim_cp_store* cpstore = &cpsession->cpstore;
int ret;
cpstore->alloc = file_alloc;
BEGIN_MIGRATION_DEF(checkpoint) {
DEFINE_MIGRATE(process, &cur_process, sizeof(struct shim_process));
DEFINE_MIGRATE(all_mounts, NULL, 0);
DEFINE_MIGRATE(all_vmas, NULL, 0);
DEFINE_MIGRATE(all_running_threads, NULL, 0);
DEFINE_MIGRATE(brk, NULL, 0);
DEFINE_MIGRATE(loaded_libraries, NULL, 0);
#ifdef DEBUG
DEFINE_MIGRATE(gdb_map, NULL, 0);
#endif
DEFINE_MIGRATE(migratable, NULL, 0);
}
END_MIGRATION_DEF(checkpoint)
if ((ret = START_MIGRATE(cpstore, checkpoint)) < 0)
return ret;
struct cp_header* hdr = (struct cp_header*)cpstore->base;
hdr->addr = (void*)cpstore->base;
hdr->size = cpstore->offset;
DkStreamUnmap((void*)cpstore->base, cpstore->bound);
put_handle(cpstore->cp_file);
return 0;
}
int shim_do_checkpoint(const char* filename) {
IDTYPE session = 0;
int ret = 0;
ret = shim_do_mkdir(filename, 0700);
if (ret < 0)
return ret;
shim_tcb_t* tcb = shim_get_tls();
assert(tcb && tcb->tp);
struct shim_signal signal;
__store_context(tcb, NULL, &signal);
ret = create_checkpoint(filename, &session);
if (ret < 0) {
shim_do_rmdir(filename);
return ret;
}
ipc_checkpoint_send(filename, session);
kill_all_threads(tcb->tp, session, SIGCP);
ret = join_checkpoint(tcb->tp, session);
if (ret < 0) {
shim_do_rmdir(filename);
return ret;
}
return 0;
}