diff options
Diffstat (limited to 'src/checkpoint.c')
-rw-r--r-- | src/checkpoint.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/src/checkpoint.c b/src/checkpoint.c new file mode 100644 index 0000000..8d3a5ee --- /dev/null +++ b/src/checkpoint.c @@ -0,0 +1,352 @@ + +#include <fcntl.h> +#include <sys/uio.h> +#include <arpa/inet.h> + +#include <path.h> +#include <print.h> +#include <platform.h> +#include <strerrno.h> +#include <openbsd-compat/openbsd-compat.h> + +#include <checkpoint.h> + +enum { + OBJ_TYPE_META = 1, + OBJ_TYPE_PATH = 2, + OBJ_TYPE_CHUNK = 3, +}; + +struct checkpoint_obj_hdr { + uint8_t type; + uint8_t rsv; + uint16_t len; /* length of an object including this hdr */ +} __attribute__((packed)); + +#define MSCP_CHECKPOINT_MAGIC 0x6d736370UL /* mscp in UTF-8 */ +#define MSCP_CHECKPOINT_VERSION 0x1 + +struct checkpoint_obj_meta { + struct checkpoint_obj_hdr hdr; + + uint32_t magic; + uint8_t version; + uint16_t reserved; + uint8_t direction; /* L2R or R2L */ + + char remote[0]; +} __attribute__((packed)); + +struct checkpoint_obj_path { + struct checkpoint_obj_hdr hdr; + + uint32_t idx; + uint16_t src_off; /* offset to the src path + * string (including \0) from + * the head of this object. */ + + uint16_t dst_off; /* offset to the dst path + * string (including \0) from + * the head of this object */ +} __attribute__((packed)); + +#define obj_path_src(obj) ((char *)(obj) + ntohs(obj->src_off)) +#define obj_path_dst(obj) ((char *)(obj) + ntohs(obj->dst_off)) + +struct checkpoint_obj_chunk { + struct checkpoint_obj_hdr hdr; + + uint32_t idx; /* index indicating associating path */ + uint64_t off; + uint64_t len; +} __attribute__((packed)); + +#define CHECKPOINT_OBJ_MAXLEN (sizeof(struct checkpoint_obj_path) + PATH_MAX * 2) + +static int checkpoint_write_path(int fd, struct path *p, unsigned int idx) +{ + char buf[CHECKPOINT_OBJ_MAXLEN]; + struct checkpoint_obj_path *path = (struct checkpoint_obj_path *)buf; + size_t src_len, dst_len; + struct iovec iov[3]; + + p->data = idx; /* save idx to be pointed by chunks */ + + src_len = strlen(p->path) + 1; + dst_len = strlen(p->dst_path) + 1; + + memset(buf, 0, sizeof(buf)); + path->hdr.type = OBJ_TYPE_PATH; + path->hdr.len = htons(sizeof(*path) + src_len + dst_len); + + path->idx = htonl(idx); + path->src_off = htons(sizeof(*path)); + path->dst_off = htons(sizeof(*path) + src_len); + + iov[0].iov_base = path; + iov[0].iov_len = sizeof(*path); + iov[1].iov_base = p->path; + iov[1].iov_len = src_len; + iov[2].iov_base = p->dst_path; + iov[2].iov_len = dst_len; + + if (writev(fd, iov, 3) < 0) { + priv_set_errv("writev: %s", strerrno()); + return -1; + } + return 0; +} + +static int checkpoint_write_chunk(int fd, struct chunk *c) +{ + struct checkpoint_obj_chunk chunk; + + memset(&chunk, 0, sizeof(chunk)); + chunk.hdr.type = OBJ_TYPE_CHUNK; + chunk.hdr.len = htons(sizeof(chunk)); + + chunk.idx = htonl(c->p->data); /* index stored by checkpoint_write_path */ + chunk.off = htonll(c->off); + chunk.len = htonll(c->len); + + if (write(fd, &chunk, sizeof(chunk)) < 0) { + priv_set_errv("writev: %s", strerrno()); + return -1; + } + return 0; +} + +int checkpoint_save(const char *pathname, int dir, char *remote, pool *path_pool, + pool *chunk_pool) +{ + struct checkpoint_obj_meta meta; + struct iovec iov[2]; + struct chunk *c; + struct path *p; + unsigned int i; + int fd; + + fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + if (fd < 0) { + priv_set_errv("open: %s: %s", pathname, strerrno()); + return -1; + } + + /* write meta */ + memset(&meta, 0, sizeof(meta)); + meta.hdr.type = OBJ_TYPE_META; + meta.hdr.len = htons(sizeof(meta) + strlen(remote) + 1); + meta.magic = htonl(MSCP_CHECKPOINT_MAGIC); + meta.version = MSCP_CHECKPOINT_VERSION; + meta.direction = dir; + + iov[0].iov_base = &meta; + iov[0].iov_len = sizeof(meta); + iov[1].iov_base = remote; + iov[1].iov_len = strlen(remote) + 1; + + if (writev(fd, iov, 2) < 0) { + priv_set_errv("writev: %s", strerrno()); + return -1; + } + + /* write paths */ + pool_for_each(path_pool, p, i) { + if (p->state == FILE_STATE_DONE) + continue; + if (checkpoint_write_path(fd, p, i) < 0) + return -1; + } + + /* write chunks */ + pool_for_each(chunk_pool, c, i) { + if (c->state == CHUNK_STATE_DONE) + continue; + if (checkpoint_write_chunk(fd, c) < 0) + return -1; + } + + return 0; +} + +static ssize_t readw(int fd, void *buf, size_t count) +{ + size_t ret; + + ret = read(fd, buf, count); + if (ret < 0) { + priv_set_errv("read: %s", strerrno()); + return -1; + } + if (ret < count) { + priv_set_errv("read truncated"); + return -1; + } + + return 0; +} + +static int checkpoint_read_obj(int fd, void *buf, size_t count) +{ + struct checkpoint_obj_hdr *hdr = (struct checkpoint_obj_hdr *)buf; + size_t ret, objlen; + + if (count < sizeof(*hdr)) { + priv_set_errv("too short buffer"); + return -1; + } + + if (readw(fd, hdr, sizeof(*hdr)) < 0) + return -1; + + objlen = ntohs(hdr->len); + if (count < objlen) { + priv_set_errv("too short buffer"); + return -1; + } + + if (readw(fd, hdr + 1, objlen - sizeof(*hdr)) < 0) + return -1; + + return 0; +} + +static int checkpoint_load_meta(struct checkpoint_obj_hdr *hdr, char *remote, size_t len, + int *dir) +{ + struct checkpoint_obj_meta *meta = (struct checkpoint_obj_meta *)hdr; + + if (ntohl(meta->magic) != MSCP_CHECKPOINT_MAGIC) { + priv_set_errv("checkpoint: invalid megic code"); + return -1; + } + + if (meta->version != MSCP_CHECKPOINT_VERSION) { + priv_set_errv("checkpoint: unknown version %u", meta->version); + return -1; + } + pr_debug("checkpoint: version %u", meta->version); + + if (dir) + *dir = meta->direction; + + if (remote) { + if (len < ntohs(hdr->len) - sizeof(*meta)) { + priv_set_errv("too short buffer"); + return -1; + } + snprintf(remote, len, "%s", meta->remote); + } + pr_info("checkpoint: remote=%s direction=%s", meta->remote, + meta->direction == MSCP_DIRECTION_L2R ? "local-to-remote" : + meta->direction == MSCP_DIRECTION_R2L ? "remote-to-local" : + "invalid"); + + return 0; +} + +static int checkpoint_load_path(struct checkpoint_obj_hdr *hdr, pool *path_pool) +{ + struct checkpoint_obj_path *path = (struct checkpoint_obj_path *)hdr; + struct path *p; + char *s, *d; + + if (!(s = strdup(obj_path_src(path)))) { + priv_set_errv("strdup: %s", strerrno()); + return -1; + } + + if (!(d = strdup(obj_path_dst(path)))) { + priv_set_errv("strdup: %s", strerrno()); + free(s); + return -1; + } + + if (!(p = alloc_path(s, d))) { + free(s); + free(d); + return -1; + } + + if (pool_push(path_pool, p) < 0) { + priv_set_errv("pool_push: %s", strerrno()); + return -1; + } + + pr_debug("checkpoint: %s -> %s\n", p->path, p->dst_path); + + return 0; +} + +static int checkpoint_load_chunk(struct checkpoint_obj_hdr *hdr, pool *path_pool, + pool *chunk_pool) +{ + struct checkpoint_obj_chunk *chunk = (struct checkpoint_obj_chunk *)hdr; + struct chunk *c; + struct path *p; + + if (!(p = pool_get(path_pool, ntohl(chunk->idx)))) { + /* we assumes all paths are already loaded in the order */ + priv_set_errv("path index %u not found", ntohl(chunk->idx)); + return -1; + } + + if (!(c = alloc_chunk(p, ntohll(chunk->off), ntohll(chunk->len)))) + return -1; + + if (pool_push(chunk_pool, c) < 0) { + priv_set_errv("pool_push: %s", strerrno()); + return -1; + } + + pr_debug("checkpoint: %s 0x%lx-0x%lx", p->path, c->off, c->off + c->len); + + return 0; +} + +int checkpoint_load(const char *pathname, char *remote, size_t len, int *dir, + pool *path_pool, pool *chunk_pool) +{ + char buf[CHECKPOINT_OBJ_MAXLEN]; + struct checkpoint_obj_hdr *hdr; + int fd; + + pr_notice("load checkpoint %s", pathname); + + if ((fd = open(pathname, O_RDONLY)) < 0) { + priv_set_errv("open: %s: %s", pathname, strerrno()); + return -1; + } + + hdr = (struct checkpoint_obj_hdr *)buf; + while (checkpoint_read_obj(fd, buf, sizeof(buf)) == 0) { + switch (hdr->type) { + case OBJ_TYPE_META: + if (checkpoint_load_meta(hdr, remote, len, dir) < 0) + return -1; + if (!path_pool || !chunk_pool) + break; + break; + case OBJ_TYPE_PATH: + if (!path_pool) + break; + if (checkpoint_load_path(hdr, path_pool) < 0) + return -1; + break; + case OBJ_TYPE_CHUNK: + if (!path_pool) + break; + if (checkpoint_load_chunk(hdr, path_pool, chunk_pool) < 0) + return -1; + break; + default: + priv_set_errv("unknown obj type %u", hdr->type); + return -1; + } + } + + close(fd); + + return 0; +} |