summaryrefslogtreecommitdiff
path: root/src/checkpoint.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/checkpoint.c')
-rw-r--r--src/checkpoint.c352
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;
+}