summaryrefslogtreecommitdiff
path: root/src/checkpoint.c
blob: 1e5ce29f4927ae43860886accf5f8201abe0c293 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360

#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 = 0x0A,
	OBJ_TYPE_PATH = 0x0B,
	OBJ_TYPE_CHUNK = 0x0C,
};

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, nr_paths, nr_chunks;
	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;
	}

	pr_notice("checkppoint: save to %s", pathname);

	/* 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 */
	nr_paths = 0;
	pool_for_each(path_pool, p, i) {
		if (p->state == FILE_STATE_DONE)
			continue;
		if (checkpoint_write_path(fd, p, i) < 0)
			return -1;
		nr_paths++;
	}

	/* write chunks */
	nr_chunks = 0;
	pool_for_each(chunk_pool, c, i) {
		if (c->state == CHUNK_STATE_DONE)
			continue;
		if (checkpoint_write_chunk(fd, c) < 0)
			return -1;
		nr_chunks++;
	}

	pr_info("checkpoint: %u paths and %u chunks saved", nr_paths, nr_chunks);

	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 (len < ntohs(hdr->len) - sizeof(*meta)) {
		priv_set_errv("too short buffer");
		return -1;
	}
	snprintf(remote, len, "%s", meta->remote);
	*dir = meta->direction;

	pr_notice("checkpoint: loaded, 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_info("checkpoint: %s -> %s", 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;
}

static int checkpoint_read_obj(int fd, void *buf, size_t count)
{
	struct checkpoint_obj_hdr *hdr = (struct checkpoint_obj_hdr *)buf;
	ssize_t ret, objlen, objbuflen;

	memset(buf, 0, count);

	if (count < sizeof(*hdr)) {
		priv_set_errv("too short buffer");
		return -1;
	}

	ret = read(fd, hdr, sizeof(*hdr));
	if (ret == 0)
		return 0; /* no more objects */
	if (ret < 0)
		return -1;

	objlen = ntohs(hdr->len) - sizeof(*hdr);
	objbuflen = count - sizeof(*hdr);
	if (objbuflen < objlen) {
		priv_set_errv("too short buffer");
		return -1;
	}

	ret = read(fd, buf + sizeof(*hdr), objlen);
	if (ret < objlen) {
		priv_set_errv("checkpoint truncated");
		return -1;
	}

	return 1;
}

static 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, ret;

	if ((fd = open(pathname, O_RDONLY)) < 0) {
		priv_set_errv("open: %s: %s", pathname, strerrno());
		return -1;
	}

	hdr = (struct checkpoint_obj_hdr *)buf;
	while ((ret = checkpoint_read_obj(fd, buf, sizeof(buf))) > 0) {
		switch (hdr->type) {
		case OBJ_TYPE_META:
			if (!remote || !dir)
				break;
			if (checkpoint_load_meta(hdr, remote, len, dir) < 0)
				return -1;
			if (!path_pool || !chunk_pool)
				goto out;
			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;
		}
	}

out:
	close(fd);

	return 0;
}

int checkpoint_load_remote(const char *pathname, char *remote, size_t len, int *dir)
{
	return checkpoint_load(pathname, remote, len, dir, NULL, NULL);
}

int checkpoint_load_paths(const char *pathname, pool *path_pool, pool *chunk_pool)
{
	return checkpoint_load(pathname, NULL, 0, NULL, path_pool, chunk_pool);
}