1 #include "bsdtar_platform.h"
2 
3 #include <sys/file.h>
4 #include <sys/stat.h>
5 
6 #include <assert.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <inttypes.h>
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 
16 #include "chunks.h"
17 #include "crypto_entropy.h"
18 #include "dirutil.h"
19 #include "hexify.h"
20 #include "hexlink.h"
21 #include "storage.h"
22 #include "warnp.h"
23 
24 #include "multitape_internal.h"
25 
26 static int multitape_docheckpoint(const char *, uint64_t, uint8_t);
27 static int multitape_docommit(const char *, uint64_t, uint8_t);
28 
29 /**
30  * multitape_cleanstate(cachedir, machinenum, key):
31  * Complete any pending checkpoint or commit.  The value ${key} should be 0
32  * if the write access key should be used to sign a commit request, or 1 if
33  * the delete access key should be used.
34  */
35 int
multitape_cleanstate(const char * cachedir,uint64_t machinenum,uint8_t key)36 multitape_cleanstate(const char * cachedir, uint64_t machinenum, uint8_t key)
37 {
38 
39 	/* Complete any pending checkpoint. */
40 	if (multitape_docheckpoint(cachedir, machinenum, key))
41 		goto err0;
42 
43 	/* Complete any pending commit. */
44 	if (multitape_docommit(cachedir, machinenum, key))
45 		goto err0;
46 
47 	/* Success! */
48 	return (0);
49 
50 err0:
51 	/* Failure! */
52 	return (-1);
53 }
54 
55 /**
56  * multitape_docheckpoint(cachedir, machinenum, key):
57  * Complete any pending checkpoint.
58  */
59 static int
multitape_docheckpoint(const char * cachedir,uint64_t machinenum,uint8_t key)60 multitape_docheckpoint(const char * cachedir, uint64_t machinenum,
61     uint8_t key)
62 {
63 	char * s, * t;
64 	uint8_t seqnum[32];
65 	uint8_t ckptnonce[32];
66 	uint8_t seqnum_ckptnonce[64];
67 
68 	/* We need a cachedir. */
69 	assert(cachedir != NULL);
70 
71 	/* Make sure ${cachedir} is flushed to disk. */
72 	if (dirutil_fsyncdir(cachedir))
73 		goto err0;
74 
75 	/* Read ${cachedir}/ckpt_m if it exists. */
76 	if (asprintf(&s, "%s/ckpt_m", cachedir) == -1) {
77 		warnp("asprintf");
78 		goto err0;
79 	}
80 	if (hexlink_read(s, seqnum_ckptnonce, 64)) {
81 		if (errno != ENOENT)
82 			goto err1;
83 
84 		/*
85 		 * ${cachedir}/ckpt_m doesn't exist; we don't need to do
86 		 * anything.
87 		 */
88 		goto done;
89 	}
90 
91 	/* Split symlink data into separate seqnum and ckptnonce. */
92 	memcpy(seqnum, seqnum_ckptnonce, 32);
93 	memcpy(ckptnonce, &seqnum_ckptnonce[32], 32);
94 
95 	/* Ask the chunk layer to complete the checkpoint. */
96 	if (chunks_transaction_checkpoint(cachedir))
97 		goto err1;
98 
99 	/* Ask the storage layer to create the checkpoint. */
100 	if (storage_transaction_checkpoint(machinenum, seqnum, ckptnonce,
101 	    key))
102 		goto err1;
103 
104 	/* Remove ${cachedir}/commit_m if it exists. */
105 	if (asprintf(&t, "%s/commit_m", cachedir) == -1) {
106 		warnp("asprintf");
107 		goto err1;
108 	}
109 	if (unlink(t)) {
110 		/* ENOENT isn't a problem. */
111 		if (errno != ENOENT) {
112 			warnp("unlink(%s)", t);
113 			goto err2;
114 		}
115 	}
116 
117 	/* This checkpoint is commitable -- create a new commit marker. */
118 	if (hexlink_write(t, seqnum, 32))
119 		goto err2;
120 	free(t);
121 
122 	/* Make sure ${cachedir} is flushed to disk. */
123 	if (dirutil_fsyncdir(cachedir))
124 		goto err1;
125 
126 	/* Delete ${cachedir}/ckpt_m. */
127 	if (unlink(s)) {
128 		warnp("unlink(%s)", s);
129 		goto err1;
130 	}
131 
132 	/* Make sure ${cachedir} is flushed to disk. */
133 	if (dirutil_fsyncdir(cachedir))
134 		goto err1;
135 
136 done:
137 	free(s);
138 
139 	/* Success! */
140 	return (0);
141 
142 err2:
143 	free(t);
144 err1:
145 	free(s);
146 err0:
147 	/* Failure! */
148 	return (-1);
149 }
150 
151 /**
152  * multitape_checkpoint(cachedir, machinenum, seqnum):
153  * Create a checkpoint in the current write transaction.
154  */
155 int
multitape_checkpoint(const char * cachedir,uint64_t machinenum,const uint8_t seqnum[32])156 multitape_checkpoint(const char * cachedir, uint64_t machinenum,
157     const uint8_t seqnum[32])
158 {
159 	char * s;
160 	uint8_t ckptnonce[32];
161 	uint8_t seqnum_ckptnonce[64];
162 
163 	/* We need a cachedir. */
164 	assert(cachedir != NULL);
165 
166 	/* Generate random checkpoint nonce. */
167 	if (crypto_entropy_read(ckptnonce, 32))
168 		goto err0;
169 
170 	/* Create symlink from ckpt_m to [seqnum][ckptnonce]. */
171 	memcpy(seqnum_ckptnonce, seqnum, 32);
172 	memcpy(&seqnum_ckptnonce[32], ckptnonce, 32);
173 	if (asprintf(&s, "%s/ckpt_m", cachedir) == -1) {
174 		warnp("asprintf");
175 		goto err0;
176 	}
177 	if (hexlink_write(s, seqnum_ckptnonce, 64))
178 		goto err1;
179 	free(s);
180 
181 	/*
182 	 * Complete the checkpoint creation (using the write key, since in
183 	 * this code path we know that we always have the write key).
184 	 */
185 	if (multitape_docheckpoint(cachedir, machinenum, 0))
186 		goto err0;
187 
188 	/* Success! */
189 	return (0);
190 
191 err1:
192 	free(s);
193 err0:
194 	/* Failure! */
195 	return (-1);
196 }
197 
198 /**
199  * multitape_docommit(cachedir, machinenum, key):
200  * Complete any pending commit.
201  */
202 static int
multitape_docommit(const char * cachedir,uint64_t machinenum,uint8_t key)203 multitape_docommit(const char * cachedir, uint64_t machinenum, uint8_t key)
204 {
205 	char * s, * t;
206 	uint8_t seqnum[32];
207 
208 	/* We need a cachedir. */
209 	assert(cachedir != NULL);
210 
211 	/* Make sure ${cachedir} is flushed to disk. */
212 	if (dirutil_fsyncdir(cachedir))
213 		goto err0;
214 
215 	/* Read ${cachedir}/commit_m if it exists. */
216 	if (asprintf(&s, "%s/commit_m", cachedir) == -1) {
217 		warnp("asprintf");
218 		goto err0;
219 	}
220 	if (hexlink_read(s, seqnum, 32)) {
221 		if (errno != ENOENT)
222 			goto err1;
223 
224 		/*
225 		 * ${cachedir}/commit_m doesn't exist; we don't need to do
226 		 * anything.
227 		 */
228 		goto done;
229 	}
230 
231 	/* Ask the chunk layer to commit the transaction. */
232 	if (chunks_transaction_commit(cachedir))
233 		goto err1;
234 
235 	/* Ask the storage layer to commit the transaction. */
236 	if (storage_transaction_commit(machinenum, seqnum, key))
237 		goto err1;
238 
239 	/* Remove ${cachedir}/cseq if it exists. */
240 	if (asprintf(&t, "%s/cseq", cachedir) == -1) {
241 		warnp("asprintf");
242 		goto err1;
243 	}
244 	if (unlink(t)) {
245 		/* ENOENT isn't a problem. */
246 		if (errno != ENOENT) {
247 			warnp("unlink(%s)", t);
248 			goto err2;
249 		}
250 	}
251 
252 	/* Store the new sequence number. */
253 	if (hexlink_write(t, seqnum, 32))
254 		goto err2;
255 	free(t);
256 
257 	/* Make sure ${cachedir} is flushed to disk. */
258 	if (dirutil_fsyncdir(cachedir))
259 		goto err1;
260 
261 	/* Delete ${cachedir}/commit_m. */
262 	if (unlink(s)) {
263 		warnp("unlink(%s)", s);
264 		goto err1;
265 	}
266 
267 	/* Make sure ${cachedir} is flushed to disk. */
268 	if (dirutil_fsyncdir(cachedir))
269 		goto err1;
270 
271 done:
272 	free(s);
273 
274 	/* Success! */
275 	return (0);
276 
277 err2:
278 	free(t);
279 err1:
280 	free(s);
281 err0:
282 	/* Failure! */
283 	return (-1);
284 }
285 
286 /**
287  * multitape_commit(cachedir, machinenum, seqnum, key):
288  * Commit the most recent transaction.  The value ${key} is defined as in
289  * multitape_cleanstate.
290  */
291 int
multitape_commit(const char * cachedir,uint64_t machinenum,const uint8_t seqnum[32],uint8_t key)292 multitape_commit(const char * cachedir, uint64_t machinenum,
293     const uint8_t seqnum[32], uint8_t key)
294 {
295 	char * s;
296 
297 	/* We need a cachedir. */
298 	assert(cachedir != NULL);
299 
300 	/* Make ${cachedir}/commit_m point to ${seqnum}. */
301 	if (asprintf(&s, "%s/commit_m", cachedir) == -1) {
302 		warnp("asprintf");
303 		goto err0;
304 	}
305 	if (hexlink_write(s, seqnum, 32))
306 		goto err1;
307 	free(s);
308 
309 	/* Complete the commit. */
310 	if (multitape_docommit(cachedir, machinenum, key))
311 		goto err0;
312 
313 	/* Success! */
314 	return (0);
315 
316 err1:
317 	free(s);
318 err0:
319 	/* Failure! */
320 	return (-1);
321 }
322 
323 /**
324  * multitape_lock(cachedir):
325  * Lock the given cache directory using lockf(3) or flock(2); return the file
326  * descriptor of the lock file, or -1 on error.
327  */
328 int
multitape_lock(const char * cachedir)329 multitape_lock(const char * cachedir)
330 {
331 	char * s;
332 	int fd;
333 
334 	/* We need a cachedir. */
335 	assert(cachedir != NULL);
336 
337 	/* Open ${cachedir}/lockf. */
338 	if (asprintf(&s, "%s/lockf", cachedir) == -1) {
339 		warnp("asprintf");
340 		goto err0;
341 	}
342 	if ((fd = open(s, O_CREAT | O_RDWR, 0666)) == -1) {
343 		warnp("open(%s)", s);
344 		goto err1;
345 	}
346 
347 	/* Lock the file. */
348 #ifdef HAVE_LOCKF
349 	while (lockf(fd, F_TLOCK, 0)) {
350 		/* Retry on EINTR. */
351 		if (errno == EINTR)
352 			continue;
353 
354 		/* Already locked? */
355 		if (errno == EACCES || errno == EAGAIN) {
356 			warn0("Transaction already in progress");
357 			goto err2;
358 		}
359 
360 		/* Something went wrong. */
361 		warnp("lockf(%s)", s);
362 		goto err2;
363 	}
364 #else
365 	if (flock(fd, LOCK_EX | LOCK_NB)) {
366 		/* Already locked? */
367 		if (errno == EWOULDBLOCK) {
368 			warn0("Transaction already in progress");
369 			goto err2;
370 		}
371 
372 		/* Something went wrong. */
373 		warnp("flock(%s)", s);
374 		goto err2;
375 	}
376 #endif
377 
378 	/* Free string allocated by asprintf. */
379 	free(s);
380 
381 	/* Success! */
382 	return (fd);
383 
384 err2:
385 	close(fd);
386 err1:
387 	free(s);
388 err0:
389 	/* Failure! */
390 	return (-1);
391 }
392 
393 /**
394  * multitape_sequence(cachedir, seqnum):
395  * Return the sequence number of the last committed transaction in the
396  * cache directory ${cachedir}, or 0 if no transactions have ever been
397  * committed.
398  */
399 int
multitape_sequence(const char * cachedir,uint8_t seqnum[32])400 multitape_sequence(const char * cachedir, uint8_t seqnum[32])
401 {
402 	char * s;
403 
404 	/* We need a cachedir. */
405 	assert(cachedir != NULL);
406 
407 	/* Read the link ${cachedir}/cseq. */
408 	if (asprintf(&s, "%s/cseq", cachedir) == -1) {
409 		warnp("asprintf");
410 		goto err0;
411 	}
412 	if (hexlink_read(s, seqnum, 32)) {
413 		if (errno != ENOENT)
414 			goto err1;
415 
416 		/* ENOENT means the sequence number is zero. */
417 		memset(seqnum, 0, 32);
418 	}
419 	free(s);
420 
421 	/* Success! */
422 	return (0);
423 
424 err1:
425 	free(s);
426 err0:
427 	/* Failure! */
428 	return (-1);
429 }
430