/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */ /* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */ /* Copyright (C) 2014 OSCAR lab, 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 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 General Public License for more details. You should have received a copy of the GNU 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 #define malloc_method(size) malloc_method_file(size) #include LIST_HEAD(created_sessions); struct cpsession { IDTYPE session; struct shim_handle * cpfile; struct list_head registered_threads; struct list_head list; PAL_HANDLE finish_event; }; struct cpthread { struct shim_thread * thread; struct list_head list; }; static struct cpsession * current_cpsession = NULL; int create_checkpoint (const char * cpdir, IDTYPE * session) { struct cpsession * cpsession = malloc(sizeof(struct cpsession)); if (!cpsession) return -ENOMEM; int ret = 0; INIT_LIST_HEAD(&cpsession->registered_threads); INIT_LIST_HEAD(&cpsession->list); cpsession->finish_event = DkNotificationEventCreate(0); 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; open_handle(cpsession->cpfile); master_lock(); if (*session) { struct cpsession * cps; list_for_each_entry(cps, &created_sessions, list) if (cps->session == *session) { ret = 0; goto err_locked; } } else { struct cpsession * cps; retry: getrand(session, sizeof(IDTYPE)); list_for_each_entry(cps, &created_sessions, list) if (cps->session == *session) goto retry; } list_add_tail(&cpsession->list, &created_sessions); if (!current_cpsession) current_cpsession = cpsession; master_unlock(); return 0; err_locked: master_unlock(); err: if (cpsession->cpfile) close_handle(cpsession->cpfile); DkObjectClose(cpsession->finish_event); free(cpsession); return ret; } static int finish_checkpoint (void); static int check_thread (struct shim_thread * thread, void * arg, bool * unlocked) { struct list_head * registered = (struct list_head *) arg; struct cpthread * cpt; if (!thread->in_vm || !thread->is_alive) return 0; list_for_each_entry(cpt, registered, list) if (cpt->thread == thread) return 0; return 1; } int join_checkpoint (struct shim_thread * cur, ucontext_t * context) { struct cpthread cpt; int ret = 0; bool do_checkpoint = false; master_lock(); if (!current_cpsession) { master_unlock(); return -EINVAL; } INIT_LIST_HEAD(&cpt.list); cpt.thread = cur; list_add_tail(&cpt.list, ¤t_cpsession->registered_threads); /* find out if there is any thread that is not registered yet */ ret = walk_thread_list(&check_thread, ¤t_cpsession->registered_threads, false); if (ret == -ESRCH) do_checkpoint = true; PAL_HANDLE finish_event = current_cpsession->finish_event; master_unlock(); if (!do_checkpoint) { debug("waiting for checkpointing\n"); DkObjectsWaitAny(1, &finish_event, NO_TIMEOUT); return 0; } debug("ready for checkpointing\n"); ret = finish_checkpoint(); if (ret < 0) debug("failed creating checkpoint: %e\n", -ret); else debug("finish checkpointing, time to wake up all threads\n"); DkEventSet(finish_event); return ret; } void * malloc_method_file (size_t size) { struct shim_handle * cpfile; master_lock(); if (!current_cpsession || !current_cpsession->cpfile) { master_unlock(); return NULL; } cpfile = current_cpsession->cpfile; get_handle(cpfile); master_unlock(); struct shim_mount * fs = cpfile->fs; if (!fs || !fs->fs_ops || !fs->fs_ops->truncate || !fs->fs_ops->mmap) return NULL; if (fs->fs_ops->truncate(cpfile, size) < 0) return NULL; void * addr = NULL; void * mem = fs->fs_ops->mmap(cpfile, &addr, ALIGN_UP(size), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, 0) < 0 ? NULL : addr; put_handle(cpfile); return mem; } static int finish_checkpoint (void) { struct shim_cp_store cpstore; again: INIT_CP_STORE(&cpstore); BEGIN_MIGRATION_DEF(checkpoint) { store->use_gipc = false; DEFINE_MIGRATE(process, &cur_process, sizeof(struct shim_process), false); DEFINE_MIGRATE(all_mounts, NULL, 0, false); DEFINE_MIGRATE(all_vmas, NULL, 0, true); DEFINE_MIGRATE(all_running_threads, NULL, 0, true); DEFINE_MIGRATE(brk, NULL, 0, false); DEFINE_MIGRATE(loaded_libraries, NULL, 0, false); DEFINE_MIGRATE(gdb_map, NULL, 0, false); DEFINE_MIGRATE(migratable, NULL, 0, false); } END_MIGRATION_DEF int ret = START_MIGRATE(&cpstore, checkpoint, sizeof(struct cp_header)); if (ret < 0) return ret; struct shim_cp_entry * cpent = cpstore.cpdata; for ( ; cpent->cp_type != CP_NULL ; cpent++) if (cpent->cp_type == CP_PALHDL && cpent->cp_un.cp_val) { PAL_HANDLE * pal_hdl = cpstore.cpdata + cpent->cp_un.cp_val; assert(*pal_hdl); *pal_hdl = NULL; } struct cp_header * hdr = (struct cp_header *) cpstore.cpaddr; hdr->cpaddr = cpstore.cpaddr; hdr->cpsize = cpstore.cpsize; hdr->cpoffset = cpstore.cpdata - cpstore.cpaddr; DkStreamUnmap(cpstore.cpaddr, cpstore.cpsize); master_lock(); assert(current_cpsession); struct shim_handle * cpfile = current_cpsession->cpfile; bool do_again = false; current_cpsession->cpfile = NULL; if (current_cpsession->list.next != &created_sessions) { current_cpsession = list_entry(current_cpsession->list.next, struct cpsession, list); do_again = true; } else { current_cpsession = NULL; } master_unlock(); close_handle(cpfile); if (do_again) goto again; 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, CHECKPOINT_REQUESTED, SIGINT); ret = join_checkpoint(tcb->tp, &signal.context); if (ret < 0) { shim_do_rmdir(filename); return ret; } return 0; }