criu/criu/image.c
Kir Kolyshkin 6a3280c747 No \n in pr_perror
It adds one for you already.

Signed-off-by: Kir Kolyshkin <kir@openvz.org>
Signed-off-by: Andrei Vagin <avagin@virtuozzo.com>
2017-11-30 01:22:15 +03:00

646 lines
12 KiB
C

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include "crtools.h"
#include "cr_options.h"
#include "imgset.h"
#include "image.h"
#include "pstree.h"
#include "stats.h"
#include "cgroup.h"
#include "lsm.h"
#include "protobuf.h"
#include "xmalloc.h"
#include "images/inventory.pb-c.h"
#include "images/pagemap.pb-c.h"
#include "img-remote.h"
bool ns_per_id = false;
bool img_common_magic = true;
TaskKobjIdsEntry *root_ids;
u32 root_cg_set;
Lsmtype image_lsm;
int check_img_inventory(void)
{
int ret = -1;
struct cr_img *img;
InventoryEntry *he;
img = open_image(CR_FD_INVENTORY, O_RSTR);
if (!img)
return -1;
if (pb_read_one(img, &he, PB_INVENTORY) < 0)
goto out_close;
if (!he->has_fdinfo_per_id || !he->fdinfo_per_id) {
pr_err("Too old image, no longer supported\n");
goto out_close;
}
ns_per_id = he->has_ns_per_id ? he->ns_per_id : false;
if (he->root_ids) {
root_ids = xmalloc(sizeof(*root_ids));
if (!root_ids)
goto out_err;
memcpy(root_ids, he->root_ids, sizeof(*root_ids));
}
if (he->has_root_cg_set) {
if (he->root_cg_set == 0) {
pr_err("Corrupted root cgset\n");
goto out_err;
}
root_cg_set = he->root_cg_set;
}
if (he->has_lsmtype)
image_lsm = he->lsmtype;
else
image_lsm = LSMTYPE__NO_LSM;
switch (he->img_version) {
case CRTOOLS_IMAGES_V1:
/* good old images. OK */
img_common_magic = false;
break;
case CRTOOLS_IMAGES_V1_1:
/* newer images with extra magic in the head */
break;
default:
pr_err("Not supported images version %u\n", he->img_version);
goto out_err;
}
if (!opts.check_only && he->has_check_only)
opts.check_only = he->check_only;
if (opts.check_only)
pr_msg("Checking mode enabled\n");
ret = 0;
out_err:
inventory_entry__free_unpacked(he, NULL);
out_close:
close_image(img);
return ret;
}
int write_img_inventory(InventoryEntry *he)
{
struct cr_img *img;
pr_info("Writing image inventory (version %u)\n", CRTOOLS_IMAGES_V1);
img = open_image(CR_FD_INVENTORY, O_DUMP);
if (!img)
return -1;
if (pb_write_one(img, he, PB_INVENTORY) < 0)
return -1;
xfree(he->root_ids);
close_image(img);
return 0;
}
int prepare_inventory(InventoryEntry *he)
{
struct pid pid;
struct {
struct pstree_item i;
struct dmp_info d;
} crt = { .i.pid = &pid };
pr_info("Perparing image inventory (version %u)\n", CRTOOLS_IMAGES_V1);
he->img_version = CRTOOLS_IMAGES_V1_1;
he->fdinfo_per_id = true;
he->has_fdinfo_per_id = true;
he->ns_per_id = true;
he->has_ns_per_id = true;
he->has_lsmtype = true;
he->lsmtype = host_lsm_type();
if (opts.check_only) {
he->has_check_only = true;
he->check_only = true;
}
crt.i.pid->state = TASK_ALIVE;
crt.i.pid->real = getpid();
if (get_task_ids(&crt.i))
return -1;
he->has_root_cg_set = true;
if (dump_task_cgroup(NULL, &he->root_cg_set, NULL))
return -1;
he->root_ids = crt.i.ids;
return 0;
}
static struct cr_imgset *alloc_cr_imgset(int nr)
{
struct cr_imgset *cr_imgset;
unsigned int i;
cr_imgset = xmalloc(sizeof(*cr_imgset));
if (cr_imgset == NULL)
return NULL;
cr_imgset->_imgs = xmalloc(nr * sizeof(struct cr_img *));
if (cr_imgset->_imgs == NULL) {
xfree(cr_imgset);
return NULL;
}
for (i = 0; i < nr; i++)
cr_imgset->_imgs[i] = NULL;
cr_imgset->fd_nr = nr;
return cr_imgset;
}
static void __close_cr_imgset(struct cr_imgset *cr_imgset)
{
unsigned int i;
if (!cr_imgset)
return;
for (i = 0; i < cr_imgset->fd_nr; i++) {
if (!cr_imgset->_imgs[i])
continue;
close_image(cr_imgset->_imgs[i]);
cr_imgset->_imgs[i] = NULL;
}
}
void close_cr_imgset(struct cr_imgset **cr_imgset)
{
if (!cr_imgset || !*cr_imgset)
return;
__close_cr_imgset(*cr_imgset);
xfree((*cr_imgset)->_imgs);
xfree(*cr_imgset);
*cr_imgset = NULL;
}
struct cr_imgset *cr_imgset_open_range(int pid, int from, int to,
unsigned long flags)
{
struct cr_imgset *imgset;
unsigned int i;
imgset = alloc_cr_imgset(to - from);
if (!imgset)
goto err;
from++;
imgset->fd_off = from;
for (i = from; i < to; i++) {
struct cr_img *img;
img = open_image(i, flags, pid);
if (!img) {
if (!(flags & O_CREAT))
/* caller should check himself */
continue;
goto err;
}
imgset->_imgs[i - from] = img;
}
return imgset;
err:
close_cr_imgset(&imgset);
return NULL;
}
struct cr_imgset *cr_task_imgset_open(int pid, int mode)
{
return cr_imgset_open(pid, TASK, mode);
}
struct cr_imgset *cr_glob_imgset_open(int mode)
{
return cr_imgset_open(-1 /* ignored */, GLOB, mode);
}
static int do_open_image(struct cr_img *img, int dfd, int type, unsigned long flags, char *path);
struct cr_img *open_image_at(int dfd, int type, unsigned long flags, ...)
{
struct cr_img *img;
unsigned long oflags;
char path[PATH_MAX];
va_list args;
bool lazy = false;
if (dfd == -1) {
dfd = get_service_fd(IMG_FD_OFF);
lazy = (flags & O_CREAT);
}
img = xmalloc(sizeof(*img));
if (!img)
return NULL;
oflags = flags | imgset_template[type].oflags;
va_start(args, flags);
vsnprintf(path, PATH_MAX, imgset_template[type].fmt, args);
va_end(args);
if (lazy) {
img->fd = LAZY_IMG_FD;
img->type = type;
img->oflags = oflags;
img->path = xstrdup(path);
return img;
} else
img->fd = EMPTY_IMG_FD;
if (do_open_image(img, dfd, type, oflags, path)) {
close_image(img);
return NULL;
}
return img;
}
static inline u32 head_magic(int oflags)
{
return oflags & O_SERVICE ? IMG_SERVICE_MAGIC : IMG_COMMON_MAGIC;
}
static int img_check_magic(struct cr_img *img, int oflags, int type, char *path)
{
u32 magic;
if (read_img(img, &magic) < 0)
return -1;
if (img_common_magic && (type != CR_FD_INVENTORY)) {
if (magic != head_magic(oflags)) {
pr_err("Head magic doesn't match for %s\n", path);
return -1;
}
if (read_img(img, &magic) < 0)
return -1;
}
if (magic != imgset_template[type].magic) {
pr_err("Magic doesn't match for %s\n", path);
return -1;
}
return 0;
}
static int img_write_magic(struct cr_img *img, int oflags, int type)
{
if (img_common_magic && (type != CR_FD_INVENTORY)) {
u32 cmagic;
cmagic = head_magic(oflags);
if (write_img(img, &cmagic))
return -1;
}
return write_img(img, &imgset_template[type].magic);
}
int do_open_remote_image(int dfd, char *path, int flags)
{
char *snapshot_id = NULL;
int ret, save;
/* When using namespaces, the current dir is changed so we need to
* change to previous working dir and back to correctly open the image
* proxy and cache sockets. */
save = open(".", O_RDONLY);
if (save < 0) {
pr_perror("unable to open current working directory");
return -1;
}
if (fchdir(get_service_fd(IMG_FD_OFF)) < 0) {
pr_perror("fchdir to dfd failed!");
close(save);
return -1;
}
snapshot_id = get_snapshot_id_from_idx(dfd);
if (snapshot_id == NULL)
ret = -1;
else if (flags == O_RDONLY) {
pr_debug("do_open_remote_image RDONLY path=%s snapshot_id=%s\n",
path, snapshot_id);
ret = read_remote_image_connection(snapshot_id, path);
} else {
pr_debug("do_open_remote_image WDONLY path=%s snapshot_id=%s\n",
path, snapshot_id);
ret = write_remote_image_connection(snapshot_id, path, O_WRONLY);
}
if (fchdir(save) < 0) {
pr_perror("fchdir to save failed");
close(save);
return -1;
}
close(save);
return ret;
}
static int do_open_image(struct cr_img *img, int dfd, int type, unsigned long oflags, char *path)
{
int ret, flags;
flags = oflags & ~(O_NOBUF | O_SERVICE | O_FORCE_LOCAL);
if (opts.remote && !(oflags & O_FORCE_LOCAL))
ret = do_open_remote_image(dfd, path, flags);
else
ret = openat(dfd, path, flags, CR_FD_PERM);
if (ret < 0) {
if (!(flags & O_CREAT) && (errno == ENOENT || ret == -ENOENT)) {
pr_info("No %s image\n", path);
img->_x.fd = EMPTY_IMG_FD;
goto skip_magic;
}
pr_perror("Unable to open %s", path);
goto err;
}
img->_x.fd = ret;
if (oflags & O_NOBUF)
bfd_setraw(&img->_x);
else {
if (flags == O_RDONLY)
ret = bfdopenr(&img->_x);
else
ret = bfdopenw(&img->_x);
if (ret)
goto err;
}
if (imgset_template[type].magic == RAW_IMAGE_MAGIC)
goto skip_magic;
if (flags == O_RDONLY)
ret = img_check_magic(img, oflags, type, path);
else
ret = img_write_magic(img, oflags, type);
if (ret)
goto err;
skip_magic:
return 0;
err:
return -1;
}
int open_image_lazy(struct cr_img *img)
{
int dfd;
char *path = img->path;
img->path = NULL;
dfd = get_service_fd(IMG_FD_OFF);
if (do_open_image(img, dfd, img->type, img->oflags, path)) {
xfree(path);
return -1;
}
xfree(path);
return 0;
}
void close_image(struct cr_img *img)
{
if (lazy_image(img)) {
/*
* Remove the image file if it's there so that
* subsequent restore doesn't read wrong or fake
* data from it.
*/
unlinkat(get_service_fd(IMG_FD_OFF), img->path, 0);
xfree(img->path);
} else if (!empty_image(img))
bclose(&img->_x);
xfree(img);
}
struct cr_img *img_from_fd(int fd)
{
struct cr_img *img;
img = xmalloc(sizeof(*img));
if (img) {
img->_x.fd = fd;
bfd_setraw(&img->_x);
}
return img;
}
int open_image_dir(char *dir)
{
int fd, ret;
fd = open(dir, O_RDONLY);
if (fd < 0) {
pr_perror("Can't open dir %s", dir);
return -1;
}
ret = install_service_fd(IMG_FD_OFF, fd);
close(fd);
fd = ret;
if (opts.remote) {
init_snapshot_id(dir);
} else if (opts.img_parent) {
ret = symlinkat(opts.img_parent, fd, CR_PARENT_LINK);
if (ret < 0 && errno != EEXIST) {
pr_perror("Can't link parent snapshot");
goto err;
}
if (opts.img_parent[0] == '/')
pr_warn("Absolute paths for parent links "
"may not work on restore!\n");
}
return 0;
err:
close_image_dir();
return -1;
}
void close_image_dir(void)
{
close_service_fd(IMG_FD_OFF);
}
static unsigned long page_ids = 1;
void up_page_ids_base(void)
{
/*
* When page server and criu dump work on
* the same dir, the shmem pagemaps and regular
* pagemaps may have IDs conflicts. Fix this by
* making page server produce page images with
* higher IDs.
*/
BUG_ON(page_ids != 1);
page_ids += 0x10000;
}
struct cr_img *open_pages_image_at(int dfd, unsigned long flags, struct cr_img *pmi, u32 *id)
{
if (flags == O_RDONLY || flags == O_RDWR) {
PagemapHead *h;
if (pb_read_one(pmi, &h, PB_PAGEMAP_HEAD) < 0)
return NULL;
*id = h->pages_id;
pagemap_head__free_unpacked(h, NULL);
} else {
PagemapHead h = PAGEMAP_HEAD__INIT;
*id = h.pages_id = page_ids++;
if (pb_write_one(pmi, &h, PB_PAGEMAP_HEAD) < 0)
return NULL;
}
return open_image_at(dfd, CR_FD_PAGES, flags, *id);
}
struct cr_img *open_pages_image(unsigned long flags, struct cr_img *pmi, u32 *id)
{
return open_pages_image_at(get_service_fd(IMG_FD_OFF), flags, pmi, id);
}
/*
* Write buffer @ptr of @size bytes into @fd file
* Returns
* 0 on success
* -1 on error (error message is printed)
*/
int write_img_buf(struct cr_img *img, const void *ptr, int size)
{
int ret;
ret = bwrite(&img->_x, ptr, size);
if (ret == size)
return 0;
if (ret < 0)
pr_perror("Can't write img file");
else
pr_err("Img trimmed %d/%d\n", ret, size);
return -1;
}
/*
* Read buffer @ptr of @size bytes from @fd file
* Returns
* 1 on success
* 0 on EOF (silently)
* -1 on error (error message is printed)
*/
int read_img_buf_eof(struct cr_img *img, void *ptr, int size)
{
int ret;
ret = bread(&img->_x, ptr, size);
if (ret == size)
return 1;
if (ret == 0)
return 0;
if (ret < 0)
pr_perror("Can't read img file");
else
pr_err("Img trimmed %d/%d\n", ret, size);
return -1;
}
/*
* Read buffer @ptr of @size bytes from @fd file
* Returns
* 1 on success
* -1 on error or EOF (error message is printed)
*/
int read_img_buf(struct cr_img *img, void *ptr, int size)
{
int ret;
ret = read_img_buf_eof(img, ptr, size);
if (ret == 0) {
pr_err("Unexpected EOF\n");
ret = -1;
}
return ret;
}
/*
* read_img_str -- same as read_img_buf, but allocates memory for
* the buffer and puts the '\0' at the end
*/
int read_img_str(struct cr_img *img, char **pstr, int size)
{
int ret;
char *str;
str = xmalloc(size + 1);
if (!str)
return -1;
ret = read_img_buf(img, str, size);
if (ret < 0) {
xfree(str);
return -1;
}
str[size] = '\0';
*pstr = str;
return 0;
}
off_t img_raw_size(struct cr_img *img)
{
struct stat stat;
if (fstat(img->_x.fd, &stat)) {
pr_perror("Failed to get image stats");
return -1;
}
return stat.st_size;
}