/src/libgit2/deps/reftable/stack.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2020 Google LLC |
3 | | * |
4 | | * Use of this source code is governed by a BSD-style |
5 | | * license that can be found in the LICENSE file or at |
6 | | * https://developers.google.com/open-source/licenses/bsd |
7 | | */ |
8 | | |
9 | | #include "stack.h" |
10 | | |
11 | | #include "system.h" |
12 | | #include "constants.h" |
13 | | #include "merged.h" |
14 | | #include "reftable-error.h" |
15 | | #include "reftable-record.h" |
16 | | #include "reftable-merged.h" |
17 | | #include "table.h" |
18 | | #include "writer.h" |
19 | | |
20 | | static int stack_filename(struct reftable_buf *dest, struct reftable_stack *st, |
21 | | const char *name) |
22 | 0 | { |
23 | 0 | int err; |
24 | 0 | reftable_buf_reset(dest); |
25 | 0 | if ((err = reftable_buf_addstr(dest, st->reftable_dir)) < 0 || |
26 | 0 | (err = reftable_buf_addstr(dest, "/")) < 0 || |
27 | 0 | (err = reftable_buf_addstr(dest, name)) < 0) |
28 | 0 | return err; |
29 | 0 | return 0; |
30 | 0 | } |
31 | | |
32 | | static ssize_t reftable_write_data(int fd, const void *data, size_t size) |
33 | 0 | { |
34 | 0 | size_t total_written = 0; |
35 | 0 | const char *p = data; |
36 | |
|
37 | 0 | while (total_written < size) { |
38 | 0 | ssize_t bytes_written = write(fd, p, size - total_written); |
39 | 0 | if (bytes_written < 0 && (errno == EAGAIN || errno == EINTR)) |
40 | 0 | continue; |
41 | 0 | if (bytes_written < 0) |
42 | 0 | return REFTABLE_IO_ERROR; |
43 | | |
44 | 0 | total_written += bytes_written; |
45 | 0 | p += bytes_written; |
46 | 0 | } |
47 | | |
48 | 0 | return total_written; |
49 | 0 | } |
50 | | |
51 | | struct fd_writer { |
52 | | const struct reftable_write_options *opts; |
53 | | int fd; |
54 | | }; |
55 | | |
56 | | static ssize_t fd_writer_write(void *arg, const void *data, size_t sz) |
57 | 0 | { |
58 | 0 | struct fd_writer *writer = arg; |
59 | 0 | return reftable_write_data(writer->fd, data, sz); |
60 | 0 | } |
61 | | |
62 | | static int fd_writer_flush(void *arg) |
63 | 0 | { |
64 | 0 | struct fd_writer *writer = arg; |
65 | 0 | return fsync(writer->fd); |
66 | 0 | } |
67 | | |
68 | | static int fd_read_lines(int fd, char ***namesp) |
69 | 0 | { |
70 | 0 | char *buf = NULL; |
71 | 0 | int err = 0; |
72 | 0 | off_t size; |
73 | |
|
74 | 0 | size = lseek(fd, 0, SEEK_END); |
75 | 0 | if (size < 0) { |
76 | 0 | err = REFTABLE_IO_ERROR; |
77 | 0 | goto done; |
78 | 0 | } |
79 | | |
80 | 0 | err = lseek(fd, 0, SEEK_SET); |
81 | 0 | if (err < 0) { |
82 | 0 | err = REFTABLE_IO_ERROR; |
83 | 0 | goto done; |
84 | 0 | } |
85 | | |
86 | 0 | REFTABLE_ALLOC_ARRAY(buf, size + 1); |
87 | 0 | if (!buf) { |
88 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
89 | 0 | goto done; |
90 | 0 | } |
91 | | |
92 | 0 | for (off_t total_read = 0; total_read < size; ) { |
93 | 0 | ssize_t bytes_read = read(fd, buf + total_read, size - total_read); |
94 | 0 | if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR)) |
95 | 0 | continue; |
96 | 0 | if (bytes_read < 0 || !bytes_read) { |
97 | 0 | err = REFTABLE_IO_ERROR; |
98 | 0 | goto done; |
99 | 0 | } |
100 | | |
101 | 0 | total_read += bytes_read; |
102 | 0 | } |
103 | 0 | buf[size] = 0; |
104 | |
|
105 | 0 | err = parse_names(buf, size, namesp); |
106 | 0 | done: |
107 | 0 | reftable_free(buf); |
108 | 0 | return err; |
109 | 0 | } |
110 | | |
111 | | int read_lines(const char *filename, char ***namesp) |
112 | 0 | { |
113 | 0 | int fd = open(filename, O_RDONLY); |
114 | 0 | int err = 0; |
115 | 0 | if (fd < 0) { |
116 | 0 | if (errno == ENOENT) { |
117 | 0 | REFTABLE_CALLOC_ARRAY(*namesp, 1); |
118 | 0 | if (!*namesp) |
119 | 0 | return REFTABLE_OUT_OF_MEMORY_ERROR; |
120 | 0 | return 0; |
121 | 0 | } |
122 | | |
123 | 0 | return REFTABLE_IO_ERROR; |
124 | 0 | } |
125 | 0 | err = fd_read_lines(fd, namesp); |
126 | 0 | close(fd); |
127 | 0 | return err; |
128 | 0 | } |
129 | | |
130 | | int reftable_stack_init_ref_iterator(struct reftable_stack *st, |
131 | | struct reftable_iterator *it) |
132 | 0 | { |
133 | 0 | return merged_table_init_iter(reftable_stack_merged_table(st), |
134 | 0 | it, REFTABLE_BLOCK_TYPE_REF); |
135 | 0 | } |
136 | | |
137 | | int reftable_stack_init_log_iterator(struct reftable_stack *st, |
138 | | struct reftable_iterator *it) |
139 | 0 | { |
140 | 0 | return merged_table_init_iter(reftable_stack_merged_table(st), |
141 | 0 | it, REFTABLE_BLOCK_TYPE_LOG); |
142 | 0 | } |
143 | | |
144 | | struct reftable_merged_table * |
145 | | reftable_stack_merged_table(struct reftable_stack *st) |
146 | 0 | { |
147 | 0 | return st->merged; |
148 | 0 | } |
149 | | |
150 | | static int has_name(char **names, const char *name) |
151 | 0 | { |
152 | 0 | while (*names) { |
153 | 0 | if (!strcmp(*names, name)) |
154 | 0 | return 1; |
155 | 0 | names++; |
156 | 0 | } |
157 | 0 | return 0; |
158 | 0 | } |
159 | | |
160 | | /* Close and free the stack */ |
161 | | void reftable_stack_destroy(struct reftable_stack *st) |
162 | 0 | { |
163 | 0 | char **names = NULL; |
164 | 0 | int err = 0; |
165 | |
|
166 | 0 | if (!st) |
167 | 0 | return; |
168 | | |
169 | 0 | if (st->merged) { |
170 | 0 | reftable_merged_table_free(st->merged); |
171 | 0 | st->merged = NULL; |
172 | 0 | } |
173 | |
|
174 | 0 | err = read_lines(st->list_file, &names); |
175 | 0 | if (err < 0) { |
176 | 0 | REFTABLE_FREE_AND_NULL(names); |
177 | 0 | } |
178 | |
|
179 | 0 | if (st->tables) { |
180 | 0 | struct reftable_buf filename = REFTABLE_BUF_INIT; |
181 | |
|
182 | 0 | for (size_t i = 0; i < st->tables_len; i++) { |
183 | 0 | const char *name = reftable_table_name(st->tables[i]); |
184 | 0 | int try_unlinking = 1; |
185 | |
|
186 | 0 | reftable_buf_reset(&filename); |
187 | 0 | if (names && !has_name(names, name)) { |
188 | 0 | if (stack_filename(&filename, st, name) < 0) |
189 | 0 | try_unlinking = 0; |
190 | 0 | } |
191 | 0 | reftable_table_decref(st->tables[i]); |
192 | |
|
193 | 0 | if (try_unlinking && filename.len) { |
194 | | /* On Windows, can only unlink after closing. */ |
195 | 0 | unlink(filename.buf); |
196 | 0 | } |
197 | 0 | } |
198 | |
|
199 | 0 | reftable_buf_release(&filename); |
200 | 0 | st->tables_len = 0; |
201 | 0 | REFTABLE_FREE_AND_NULL(st->tables); |
202 | 0 | } |
203 | |
|
204 | 0 | if (st->list_fd >= 0) { |
205 | 0 | close(st->list_fd); |
206 | 0 | st->list_fd = -1; |
207 | 0 | } |
208 | |
|
209 | 0 | REFTABLE_FREE_AND_NULL(st->list_file); |
210 | 0 | REFTABLE_FREE_AND_NULL(st->reftable_dir); |
211 | 0 | reftable_free(st); |
212 | 0 | free_names(names); |
213 | 0 | } |
214 | | |
215 | | static struct reftable_table **stack_copy_tables(struct reftable_stack *st, |
216 | | size_t cur_len) |
217 | 0 | { |
218 | 0 | struct reftable_table **cur = reftable_calloc(cur_len, sizeof(*cur)); |
219 | 0 | if (!cur) |
220 | 0 | return NULL; |
221 | 0 | for (size_t i = 0; i < cur_len; i++) |
222 | 0 | cur[i] = st->tables[i]; |
223 | 0 | return cur; |
224 | 0 | } |
225 | | |
226 | | static int reftable_stack_reload_once(struct reftable_stack *st, |
227 | | const char **names, |
228 | | int reuse_open) |
229 | 0 | { |
230 | 0 | size_t cur_len = !st->merged ? 0 : st->merged->tables_len; |
231 | 0 | struct reftable_table **cur = NULL; |
232 | 0 | struct reftable_table **reused = NULL; |
233 | 0 | struct reftable_table **new_tables = NULL; |
234 | 0 | size_t reused_len = 0, reused_alloc = 0, names_len; |
235 | 0 | size_t new_tables_len = 0; |
236 | 0 | struct reftable_merged_table *new_merged = NULL; |
237 | 0 | struct reftable_buf table_path = REFTABLE_BUF_INIT; |
238 | 0 | int err = 0; |
239 | 0 | size_t i; |
240 | |
|
241 | 0 | if (cur_len) { |
242 | 0 | cur = stack_copy_tables(st, cur_len); |
243 | 0 | if (!cur) { |
244 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
245 | 0 | goto done; |
246 | 0 | } |
247 | 0 | } |
248 | | |
249 | 0 | names_len = names_length(names); |
250 | |
|
251 | 0 | if (names_len) { |
252 | 0 | new_tables = reftable_calloc(names_len, sizeof(*new_tables)); |
253 | 0 | if (!new_tables) { |
254 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
255 | 0 | goto done; |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | 0 | while (*names) { |
260 | 0 | struct reftable_table *table = NULL; |
261 | 0 | const char *name = *names++; |
262 | | |
263 | | /* this is linear; we assume compaction keeps the number of |
264 | | tables under control so this is not quadratic. */ |
265 | 0 | for (i = 0; reuse_open && i < cur_len; i++) { |
266 | 0 | if (cur[i] && 0 == strcmp(cur[i]->name, name)) { |
267 | 0 | table = cur[i]; |
268 | 0 | cur[i] = NULL; |
269 | | |
270 | | /* |
271 | | * When reloading the stack fails, we end up |
272 | | * releasing all new tables. This also |
273 | | * includes the reused tables, even though |
274 | | * they are still in used by the old stack. We |
275 | | * thus need to keep them alive here, which we |
276 | | * do by bumping their refcount. |
277 | | */ |
278 | 0 | REFTABLE_ALLOC_GROW_OR_NULL(reused, |
279 | 0 | reused_len + 1, |
280 | 0 | reused_alloc); |
281 | 0 | if (!reused) { |
282 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
283 | 0 | goto done; |
284 | 0 | } |
285 | 0 | reused[reused_len++] = table; |
286 | 0 | reftable_table_incref(table); |
287 | 0 | break; |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | 0 | if (!table) { |
292 | 0 | struct reftable_block_source src = { NULL }; |
293 | |
|
294 | 0 | err = stack_filename(&table_path, st, name); |
295 | 0 | if (err < 0) |
296 | 0 | goto done; |
297 | | |
298 | 0 | err = reftable_block_source_from_file(&src, |
299 | 0 | table_path.buf); |
300 | 0 | if (err < 0) |
301 | 0 | goto done; |
302 | | |
303 | 0 | err = reftable_table_new(&table, &src, name); |
304 | 0 | if (err < 0) |
305 | 0 | goto done; |
306 | 0 | } |
307 | | |
308 | 0 | new_tables[new_tables_len] = table; |
309 | 0 | new_tables_len++; |
310 | 0 | } |
311 | | |
312 | | /* success! */ |
313 | 0 | err = reftable_merged_table_new(&new_merged, new_tables, |
314 | 0 | new_tables_len, st->opts.hash_id); |
315 | 0 | if (err < 0) |
316 | 0 | goto done; |
317 | | |
318 | | /* |
319 | | * Close the old, non-reused tables and proactively try to unlink |
320 | | * them. This is done for systems like Windows, where the underlying |
321 | | * file of such an open table wouldn't have been possible to be |
322 | | * unlinked by the compacting process. |
323 | | */ |
324 | 0 | for (i = 0; i < cur_len; i++) { |
325 | 0 | if (cur[i]) { |
326 | 0 | const char *name = reftable_table_name(cur[i]); |
327 | |
|
328 | 0 | err = stack_filename(&table_path, st, name); |
329 | 0 | if (err < 0) |
330 | 0 | goto done; |
331 | | |
332 | 0 | reftable_table_decref(cur[i]); |
333 | 0 | unlink(table_path.buf); |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | /* Update the stack to point to the new tables. */ |
338 | 0 | if (st->merged) |
339 | 0 | reftable_merged_table_free(st->merged); |
340 | 0 | new_merged->suppress_deletions = 1; |
341 | 0 | st->merged = new_merged; |
342 | |
|
343 | 0 | if (st->tables) |
344 | 0 | reftable_free(st->tables); |
345 | 0 | st->tables = new_tables; |
346 | 0 | st->tables_len = new_tables_len; |
347 | 0 | new_tables = NULL; |
348 | 0 | new_tables_len = 0; |
349 | | |
350 | | /* |
351 | | * Decrement the refcount of reused tables again. This only needs to |
352 | | * happen on the successful case, because on the unsuccessful one we |
353 | | * decrement their refcount via `new_tables`. |
354 | | */ |
355 | 0 | for (i = 0; i < reused_len; i++) |
356 | 0 | reftable_table_decref(reused[i]); |
357 | |
|
358 | 0 | done: |
359 | 0 | for (i = 0; i < new_tables_len; i++) |
360 | 0 | reftable_table_decref(new_tables[i]); |
361 | 0 | reftable_free(new_tables); |
362 | 0 | reftable_free(reused); |
363 | 0 | reftable_free(cur); |
364 | 0 | reftable_buf_release(&table_path); |
365 | 0 | return err; |
366 | 0 | } |
367 | | |
368 | | static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, |
369 | | int reuse_open) |
370 | 0 | { |
371 | 0 | char **names = NULL, **names_after = NULL; |
372 | 0 | uint64_t deadline; |
373 | 0 | int64_t delay = 0; |
374 | 0 | int tries = 0, err; |
375 | 0 | int fd = -1; |
376 | |
|
377 | 0 | deadline = reftable_time_ms() + 3000; |
378 | |
|
379 | 0 | while (1) { |
380 | 0 | uint64_t now = reftable_time_ms(); |
381 | | |
382 | | /* |
383 | | * Only look at deadlines after the first few times. This |
384 | | * simplifies debugging in GDB. |
385 | | */ |
386 | 0 | tries++; |
387 | 0 | if (tries > 3 && now >= deadline) |
388 | 0 | goto out; |
389 | | |
390 | 0 | fd = open(st->list_file, O_RDONLY); |
391 | 0 | if (fd < 0) { |
392 | 0 | if (errno != ENOENT) { |
393 | 0 | err = REFTABLE_IO_ERROR; |
394 | 0 | goto out; |
395 | 0 | } |
396 | | |
397 | 0 | REFTABLE_CALLOC_ARRAY(names, 1); |
398 | 0 | if (!names) { |
399 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
400 | 0 | goto out; |
401 | 0 | } |
402 | 0 | } else { |
403 | 0 | err = fd_read_lines(fd, &names); |
404 | 0 | if (err < 0) |
405 | 0 | goto out; |
406 | 0 | } |
407 | | |
408 | 0 | err = reftable_stack_reload_once(st, (const char **) names, reuse_open); |
409 | 0 | if (!err) |
410 | 0 | break; |
411 | 0 | if (err != REFTABLE_NOT_EXIST_ERROR) |
412 | 0 | goto out; |
413 | | |
414 | | /* |
415 | | * REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent |
416 | | * writer. Check if there was one by checking if the name list |
417 | | * changed. |
418 | | */ |
419 | 0 | err = read_lines(st->list_file, &names_after); |
420 | 0 | if (err < 0) |
421 | 0 | goto out; |
422 | 0 | if (names_equal((const char **) names_after, |
423 | 0 | (const char **) names)) { |
424 | 0 | err = REFTABLE_NOT_EXIST_ERROR; |
425 | 0 | goto out; |
426 | 0 | } |
427 | | |
428 | 0 | free_names(names); |
429 | 0 | names = NULL; |
430 | 0 | free_names(names_after); |
431 | 0 | names_after = NULL; |
432 | 0 | close(fd); |
433 | 0 | fd = -1; |
434 | |
|
435 | 0 | delay = delay + (delay * reftable_rand()) / UINT32_MAX + 1; |
436 | 0 | poll(NULL, 0, delay); |
437 | 0 | } |
438 | | |
439 | 0 | out: |
440 | | /* |
441 | | * Invalidate the stat cache. It is sufficient to only close the file |
442 | | * descriptor and keep the cached stat info because we never use the |
443 | | * latter when the former is negative. |
444 | | */ |
445 | 0 | if (st->list_fd >= 0) { |
446 | 0 | close(st->list_fd); |
447 | 0 | st->list_fd = -1; |
448 | 0 | } |
449 | | |
450 | | /* |
451 | | * Cache stat information in case it provides a useful signal to us. |
452 | | * According to POSIX, "The st_ino and st_dev fields taken together |
453 | | * uniquely identify the file within the system." That being said, |
454 | | * Windows is not POSIX compliant and we do not have these fields |
455 | | * available. So the information we have there is insufficient to |
456 | | * determine whether two file descriptors point to the same file. |
457 | | * |
458 | | * While we could fall back to using other signals like the file's |
459 | | * mtime, those are not sufficient to avoid races. We thus refrain from |
460 | | * using the stat cache on such systems and fall back to the secondary |
461 | | * caching mechanism, which is to check whether contents of the file |
462 | | * have changed. |
463 | | * |
464 | | * On other systems which are POSIX compliant we must keep the file |
465 | | * descriptor open. This is to avoid a race condition where two |
466 | | * processes access the reftable stack at the same point in time: |
467 | | * |
468 | | * 1. A reads the reftable stack and caches its stat info. |
469 | | * |
470 | | * 2. B updates the stack, appending a new table to "tables.list". |
471 | | * This will both use a new inode and result in a different file |
472 | | * size, thus invalidating A's cache in theory. |
473 | | * |
474 | | * 3. B decides to auto-compact the stack and merges two tables. The |
475 | | * file size now matches what A has cached again. Furthermore, the |
476 | | * filesystem may decide to recycle the inode number of the file |
477 | | * we have replaced in (2) because it is not in use anymore. |
478 | | * |
479 | | * 4. A reloads the reftable stack. Neither the inode number nor the |
480 | | * file size changed. If the timestamps did not change either then |
481 | | * we think the cached copy of our stack is up-to-date. |
482 | | * |
483 | | * By keeping the file descriptor open the inode number cannot be |
484 | | * recycled, mitigating the race. |
485 | | */ |
486 | 0 | if (!err && fd >= 0 && !fstat(fd, &st->list_st) && |
487 | 0 | st->list_st.st_dev && st->list_st.st_ino) { |
488 | 0 | st->list_fd = fd; |
489 | 0 | fd = -1; |
490 | 0 | } |
491 | |
|
492 | 0 | if (fd >= 0) |
493 | 0 | close(fd); |
494 | 0 | free_names(names); |
495 | 0 | free_names(names_after); |
496 | |
|
497 | 0 | if (st->opts.on_reload) |
498 | 0 | st->opts.on_reload(st->opts.on_reload_payload); |
499 | |
|
500 | 0 | return err; |
501 | 0 | } |
502 | | |
503 | | int reftable_new_stack(struct reftable_stack **dest, const char *dir, |
504 | | const struct reftable_write_options *_opts) |
505 | 0 | { |
506 | 0 | struct reftable_buf list_file_name = REFTABLE_BUF_INIT; |
507 | 0 | struct reftable_write_options opts = { 0 }; |
508 | 0 | struct reftable_stack *p; |
509 | 0 | int err; |
510 | |
|
511 | 0 | p = reftable_calloc(1, sizeof(*p)); |
512 | 0 | if (!p) { |
513 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
514 | 0 | goto out; |
515 | 0 | } |
516 | | |
517 | 0 | if (_opts) |
518 | 0 | opts = *_opts; |
519 | 0 | if (opts.hash_id == 0) |
520 | 0 | opts.hash_id = REFTABLE_HASH_SHA1; |
521 | |
|
522 | 0 | *dest = NULL; |
523 | |
|
524 | 0 | reftable_buf_reset(&list_file_name); |
525 | 0 | if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 || |
526 | 0 | (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0) |
527 | 0 | goto out; |
528 | | |
529 | 0 | p->list_file = reftable_buf_detach(&list_file_name); |
530 | 0 | p->list_fd = -1; |
531 | 0 | p->opts = opts; |
532 | 0 | p->reftable_dir = reftable_strdup(dir); |
533 | 0 | if (!p->reftable_dir) { |
534 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
535 | 0 | goto out; |
536 | 0 | } |
537 | | |
538 | 0 | err = reftable_stack_reload_maybe_reuse(p, 1); |
539 | 0 | if (err < 0) |
540 | 0 | goto out; |
541 | | |
542 | 0 | *dest = p; |
543 | 0 | err = 0; |
544 | |
|
545 | 0 | out: |
546 | 0 | if (err < 0) |
547 | 0 | reftable_stack_destroy(p); |
548 | 0 | return err; |
549 | 0 | } |
550 | | |
551 | | /* |
552 | | * Check whether the given stack is up-to-date with what we have in memory. |
553 | | * Returns 0 if so, 1 if the stack is out-of-date or a negative error code |
554 | | * otherwise. |
555 | | */ |
556 | | static int stack_uptodate(struct reftable_stack *st) |
557 | 0 | { |
558 | 0 | char **names = NULL; |
559 | 0 | int err; |
560 | | |
561 | | /* |
562 | | * When we have cached stat information available then we use it to |
563 | | * verify whether the file has been rewritten. |
564 | | * |
565 | | * Note that we explicitly do not want to use `stat_validity_check()` |
566 | | * and friends here because they may end up not comparing the `st_dev` |
567 | | * and `st_ino` fields. These functions thus cannot guarantee that we |
568 | | * indeed still have the same file. |
569 | | */ |
570 | 0 | if (st->list_fd >= 0) { |
571 | 0 | struct stat list_st; |
572 | |
|
573 | 0 | if (stat(st->list_file, &list_st) < 0) { |
574 | | /* |
575 | | * It's fine for "tables.list" to not exist. In that |
576 | | * case, we have to refresh when the loaded stack has |
577 | | * any tables. |
578 | | */ |
579 | 0 | if (errno == ENOENT) |
580 | 0 | return !!st->tables_len; |
581 | 0 | return REFTABLE_IO_ERROR; |
582 | 0 | } |
583 | | |
584 | | /* |
585 | | * When "tables.list" refers to the same file we can assume |
586 | | * that it didn't change. This is because we always use |
587 | | * rename(3P) to update the file and never write to it |
588 | | * directly. |
589 | | */ |
590 | 0 | if (st->list_st.st_dev == list_st.st_dev && |
591 | 0 | st->list_st.st_ino == list_st.st_ino) |
592 | 0 | return 0; |
593 | 0 | } |
594 | | |
595 | 0 | err = read_lines(st->list_file, &names); |
596 | 0 | if (err < 0) |
597 | 0 | return err; |
598 | | |
599 | 0 | for (size_t i = 0; i < st->tables_len; i++) { |
600 | 0 | if (!names[i]) { |
601 | 0 | err = 1; |
602 | 0 | goto done; |
603 | 0 | } |
604 | | |
605 | 0 | if (strcmp(st->tables[i]->name, names[i])) { |
606 | 0 | err = 1; |
607 | 0 | goto done; |
608 | 0 | } |
609 | 0 | } |
610 | | |
611 | 0 | if (names[st->merged->tables_len]) { |
612 | 0 | err = 1; |
613 | 0 | goto done; |
614 | 0 | } |
615 | | |
616 | 0 | done: |
617 | 0 | free_names(names); |
618 | 0 | return err; |
619 | 0 | } |
620 | | |
621 | | int reftable_stack_reload(struct reftable_stack *st) |
622 | 0 | { |
623 | 0 | int err = stack_uptodate(st); |
624 | 0 | if (err > 0) |
625 | 0 | return reftable_stack_reload_maybe_reuse(st, 1); |
626 | 0 | return err; |
627 | 0 | } |
628 | | |
629 | | struct reftable_addition { |
630 | | struct reftable_flock tables_list_lock; |
631 | | struct reftable_stack *stack; |
632 | | |
633 | | char **new_tables; |
634 | | size_t new_tables_len, new_tables_cap; |
635 | | uint64_t next_update_index; |
636 | | }; |
637 | | |
638 | | static void reftable_addition_close(struct reftable_addition *add) |
639 | 0 | { |
640 | 0 | struct reftable_buf nm = REFTABLE_BUF_INIT; |
641 | 0 | size_t i; |
642 | |
|
643 | 0 | for (i = 0; i < add->new_tables_len; i++) { |
644 | 0 | if (!stack_filename(&nm, add->stack, add->new_tables[i])) |
645 | 0 | unlink(nm.buf); |
646 | 0 | reftable_free(add->new_tables[i]); |
647 | 0 | add->new_tables[i] = NULL; |
648 | 0 | } |
649 | 0 | reftable_free(add->new_tables); |
650 | 0 | add->new_tables = NULL; |
651 | 0 | add->new_tables_len = 0; |
652 | 0 | add->new_tables_cap = 0; |
653 | |
|
654 | 0 | flock_release(&add->tables_list_lock); |
655 | 0 | reftable_buf_release(&nm); |
656 | 0 | } |
657 | | |
658 | | static int reftable_stack_init_addition(struct reftable_addition *add, |
659 | | struct reftable_stack *st, |
660 | | unsigned int flags) |
661 | 0 | { |
662 | 0 | struct reftable_buf lock_file_name = REFTABLE_BUF_INIT; |
663 | 0 | int err; |
664 | |
|
665 | 0 | memset(add, 0, sizeof(*add)); |
666 | 0 | add->stack = st; |
667 | |
|
668 | 0 | err = flock_acquire(&add->tables_list_lock, st->list_file, |
669 | 0 | st->opts.lock_timeout_ms); |
670 | 0 | if (err < 0) |
671 | 0 | goto done; |
672 | | |
673 | 0 | if (st->opts.default_permissions) { |
674 | 0 | if (chmod(add->tables_list_lock.path, |
675 | 0 | st->opts.default_permissions) < 0) { |
676 | 0 | err = REFTABLE_IO_ERROR; |
677 | 0 | goto done; |
678 | 0 | } |
679 | 0 | } |
680 | | |
681 | 0 | err = stack_uptodate(st); |
682 | 0 | if (err < 0) |
683 | 0 | goto done; |
684 | 0 | if (err > 0 && flags & REFTABLE_STACK_NEW_ADDITION_RELOAD) { |
685 | 0 | err = reftable_stack_reload_maybe_reuse(add->stack, 1); |
686 | 0 | if (err) |
687 | 0 | goto done; |
688 | 0 | } |
689 | 0 | if (err > 0) { |
690 | 0 | err = REFTABLE_OUTDATED_ERROR; |
691 | 0 | goto done; |
692 | 0 | } |
693 | | |
694 | 0 | add->next_update_index = reftable_stack_next_update_index(st); |
695 | 0 | done: |
696 | 0 | if (err) |
697 | 0 | reftable_addition_close(add); |
698 | 0 | reftable_buf_release(&lock_file_name); |
699 | 0 | return err; |
700 | 0 | } |
701 | | |
702 | | static int stack_try_add(struct reftable_stack *st, |
703 | | int (*write_table)(struct reftable_writer *wr, |
704 | | void *arg), |
705 | | void *arg, unsigned flags) |
706 | 0 | { |
707 | 0 | struct reftable_addition add; |
708 | 0 | int err; |
709 | |
|
710 | 0 | err = reftable_stack_init_addition(&add, st, flags); |
711 | 0 | if (err < 0) |
712 | 0 | goto done; |
713 | | |
714 | 0 | err = reftable_addition_add(&add, write_table, arg); |
715 | 0 | if (err < 0) |
716 | 0 | goto done; |
717 | | |
718 | 0 | err = reftable_addition_commit(&add); |
719 | 0 | done: |
720 | 0 | reftable_addition_close(&add); |
721 | 0 | return err; |
722 | 0 | } |
723 | | |
724 | | int reftable_stack_add(struct reftable_stack *st, |
725 | | int (*write)(struct reftable_writer *wr, void *arg), |
726 | | void *arg, unsigned flags) |
727 | 0 | { |
728 | 0 | int err = stack_try_add(st, write, arg, flags); |
729 | 0 | if (err < 0) { |
730 | 0 | if (err == REFTABLE_OUTDATED_ERROR) { |
731 | | /* Ignore error return, we want to propagate |
732 | | REFTABLE_OUTDATED_ERROR. |
733 | | */ |
734 | 0 | reftable_stack_reload(st); |
735 | 0 | } |
736 | 0 | return err; |
737 | 0 | } |
738 | | |
739 | 0 | return 0; |
740 | 0 | } |
741 | | |
742 | | static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max) |
743 | 0 | { |
744 | 0 | char buf[100]; |
745 | 0 | uint32_t rnd = reftable_rand(); |
746 | 0 | snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x", |
747 | 0 | min, max, rnd); |
748 | 0 | reftable_buf_reset(dest); |
749 | 0 | return reftable_buf_addstr(dest, buf); |
750 | 0 | } |
751 | | |
752 | | void reftable_addition_destroy(struct reftable_addition *add) |
753 | 0 | { |
754 | 0 | if (!add) { |
755 | 0 | return; |
756 | 0 | } |
757 | 0 | reftable_addition_close(add); |
758 | 0 | reftable_free(add); |
759 | 0 | } |
760 | | |
761 | | int reftable_addition_commit(struct reftable_addition *add) |
762 | 0 | { |
763 | 0 | struct reftable_buf table_list = REFTABLE_BUF_INIT; |
764 | 0 | int err = 0; |
765 | 0 | size_t i; |
766 | |
|
767 | 0 | if (add->new_tables_len == 0) |
768 | 0 | goto done; |
769 | | |
770 | 0 | for (i = 0; i < add->stack->merged->tables_len; i++) { |
771 | 0 | if ((err = reftable_buf_addstr(&table_list, add->stack->tables[i]->name)) < 0 || |
772 | 0 | (err = reftable_buf_addstr(&table_list, "\n")) < 0) |
773 | 0 | goto done; |
774 | 0 | } |
775 | 0 | for (i = 0; i < add->new_tables_len; i++) { |
776 | 0 | if ((err = reftable_buf_addstr(&table_list, add->new_tables[i])) < 0 || |
777 | 0 | (err = reftable_buf_addstr(&table_list, "\n")) < 0) |
778 | 0 | goto done; |
779 | 0 | } |
780 | | |
781 | 0 | err = reftable_write_data(add->tables_list_lock.fd, |
782 | 0 | table_list.buf, table_list.len); |
783 | 0 | reftable_buf_release(&table_list); |
784 | 0 | if (err < 0) { |
785 | 0 | err = REFTABLE_IO_ERROR; |
786 | 0 | goto done; |
787 | 0 | } |
788 | | |
789 | 0 | err = fsync(add->tables_list_lock.fd); |
790 | 0 | if (err < 0) { |
791 | 0 | err = REFTABLE_IO_ERROR; |
792 | 0 | goto done; |
793 | 0 | } |
794 | | |
795 | 0 | err = flock_commit(&add->tables_list_lock); |
796 | 0 | if (err < 0) { |
797 | 0 | err = REFTABLE_IO_ERROR; |
798 | 0 | goto done; |
799 | 0 | } |
800 | | |
801 | | /* success, no more state to clean up. */ |
802 | 0 | for (i = 0; i < add->new_tables_len; i++) |
803 | 0 | reftable_free(add->new_tables[i]); |
804 | 0 | reftable_free(add->new_tables); |
805 | 0 | add->new_tables = NULL; |
806 | 0 | add->new_tables_len = 0; |
807 | 0 | add->new_tables_cap = 0; |
808 | |
|
809 | 0 | err = reftable_stack_reload_maybe_reuse(add->stack, 1); |
810 | 0 | if (err) |
811 | 0 | goto done; |
812 | | |
813 | 0 | if (!add->stack->opts.disable_auto_compact) { |
814 | | /* |
815 | | * Auto-compact the stack to keep the number of tables in |
816 | | * control. It is possible that a concurrent writer is already |
817 | | * trying to compact parts of the stack, which would lead to a |
818 | | * `REFTABLE_LOCK_ERROR` because parts of the stack are locked |
819 | | * already. Similarly, the stack may have been rewritten by a |
820 | | * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`. |
821 | | * Both of these errors are benign, so we simply ignore them. |
822 | | */ |
823 | 0 | err = reftable_stack_auto_compact(add->stack); |
824 | 0 | if (err < 0 && err != REFTABLE_LOCK_ERROR && |
825 | 0 | err != REFTABLE_OUTDATED_ERROR) |
826 | 0 | goto done; |
827 | 0 | err = 0; |
828 | 0 | } |
829 | | |
830 | 0 | done: |
831 | 0 | reftable_addition_close(add); |
832 | 0 | return err; |
833 | 0 | } |
834 | | |
835 | | int reftable_stack_new_addition(struct reftable_addition **dest, |
836 | | struct reftable_stack *st, |
837 | | unsigned int flags) |
838 | 0 | { |
839 | 0 | int err; |
840 | |
|
841 | 0 | REFTABLE_CALLOC_ARRAY(*dest, 1); |
842 | 0 | if (!*dest) |
843 | 0 | return REFTABLE_OUT_OF_MEMORY_ERROR; |
844 | | |
845 | 0 | err = reftable_stack_init_addition(*dest, st, flags); |
846 | 0 | if (err) { |
847 | 0 | reftable_free(*dest); |
848 | 0 | *dest = NULL; |
849 | 0 | } |
850 | |
|
851 | 0 | return err; |
852 | 0 | } |
853 | | |
854 | | int reftable_addition_add(struct reftable_addition *add, |
855 | | int (*write_table)(struct reftable_writer *wr, |
856 | | void *arg), |
857 | | void *arg) |
858 | 0 | { |
859 | 0 | struct reftable_buf temp_tab_file_name = REFTABLE_BUF_INIT; |
860 | 0 | struct reftable_buf tab_file_name = REFTABLE_BUF_INIT; |
861 | 0 | struct reftable_buf next_name = REFTABLE_BUF_INIT; |
862 | 0 | struct reftable_writer *wr = NULL; |
863 | 0 | struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; |
864 | 0 | struct fd_writer writer = { |
865 | 0 | .opts = &add->stack->opts, |
866 | 0 | }; |
867 | 0 | int err = 0; |
868 | |
|
869 | 0 | reftable_buf_reset(&next_name); |
870 | |
|
871 | 0 | err = format_name(&next_name, add->next_update_index, add->next_update_index); |
872 | 0 | if (err < 0) |
873 | 0 | goto done; |
874 | | |
875 | 0 | err = stack_filename(&temp_tab_file_name, add->stack, next_name.buf); |
876 | 0 | if (err < 0) |
877 | 0 | goto done; |
878 | | |
879 | 0 | err = reftable_buf_addstr(&temp_tab_file_name, ".temp.XXXXXX"); |
880 | 0 | if (err < 0) |
881 | 0 | goto done; |
882 | | |
883 | 0 | err = tmpfile_from_pattern(&tab_file, temp_tab_file_name.buf); |
884 | 0 | if (err < 0) |
885 | 0 | goto done; |
886 | 0 | if (add->stack->opts.default_permissions) { |
887 | 0 | if (chmod(tab_file.path, |
888 | 0 | add->stack->opts.default_permissions)) { |
889 | 0 | err = REFTABLE_IO_ERROR; |
890 | 0 | goto done; |
891 | 0 | } |
892 | 0 | } |
893 | | |
894 | 0 | writer.fd = tab_file.fd; |
895 | 0 | err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, |
896 | 0 | &writer, &add->stack->opts); |
897 | 0 | if (err < 0) |
898 | 0 | goto done; |
899 | | |
900 | 0 | err = write_table(wr, arg); |
901 | 0 | if (err < 0) |
902 | 0 | goto done; |
903 | | |
904 | 0 | err = reftable_writer_close(wr); |
905 | 0 | if (err == REFTABLE_EMPTY_TABLE_ERROR) { |
906 | 0 | err = 0; |
907 | 0 | goto done; |
908 | 0 | } |
909 | 0 | if (err < 0) |
910 | 0 | goto done; |
911 | | |
912 | 0 | err = tmpfile_close(&tab_file); |
913 | 0 | if (err < 0) |
914 | 0 | goto done; |
915 | | |
916 | 0 | if (wr->min_update_index < add->next_update_index) { |
917 | 0 | err = REFTABLE_API_ERROR; |
918 | 0 | goto done; |
919 | 0 | } |
920 | | |
921 | 0 | err = format_name(&next_name, wr->min_update_index, wr->max_update_index); |
922 | 0 | if (err < 0) |
923 | 0 | goto done; |
924 | | |
925 | 0 | err = reftable_buf_addstr(&next_name, ".ref"); |
926 | 0 | if (err < 0) |
927 | 0 | goto done; |
928 | | |
929 | 0 | err = stack_filename(&tab_file_name, add->stack, next_name.buf); |
930 | 0 | if (err < 0) |
931 | 0 | goto done; |
932 | | |
933 | | /* |
934 | | On windows, this relies on rand() picking a unique destination name. |
935 | | Maybe we should do retry loop as well? |
936 | | */ |
937 | 0 | err = tmpfile_rename(&tab_file, tab_file_name.buf); |
938 | 0 | if (err < 0) |
939 | 0 | goto done; |
940 | | |
941 | 0 | REFTABLE_ALLOC_GROW_OR_NULL(add->new_tables, add->new_tables_len + 1, |
942 | 0 | add->new_tables_cap); |
943 | 0 | if (!add->new_tables) { |
944 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
945 | 0 | goto done; |
946 | 0 | } |
947 | 0 | add->new_tables[add->new_tables_len++] = reftable_buf_detach(&next_name); |
948 | |
|
949 | 0 | done: |
950 | 0 | tmpfile_delete(&tab_file); |
951 | 0 | reftable_buf_release(&temp_tab_file_name); |
952 | 0 | reftable_buf_release(&tab_file_name); |
953 | 0 | reftable_buf_release(&next_name); |
954 | 0 | reftable_writer_free(wr); |
955 | 0 | return err; |
956 | 0 | } |
957 | | |
958 | | uint64_t reftable_stack_next_update_index(struct reftable_stack *st) |
959 | 0 | { |
960 | 0 | int sz = st->merged->tables_len; |
961 | 0 | if (sz > 0) |
962 | 0 | return reftable_table_max_update_index(st->tables[sz - 1]) + |
963 | 0 | 1; |
964 | 0 | return 1; |
965 | 0 | } |
966 | | |
967 | | static int stack_write_compact(struct reftable_stack *st, |
968 | | struct reftable_writer *wr, |
969 | | size_t first, size_t last, |
970 | | struct reftable_log_expiry_config *config) |
971 | 0 | { |
972 | 0 | struct reftable_merged_table *mt = NULL; |
973 | 0 | struct reftable_iterator it = { NULL }; |
974 | 0 | struct reftable_ref_record ref = { NULL }; |
975 | 0 | struct reftable_log_record log = { NULL }; |
976 | 0 | size_t subtabs_len = last - first + 1; |
977 | 0 | uint64_t entries = 0; |
978 | 0 | int err = 0; |
979 | |
|
980 | 0 | for (size_t i = first; i <= last; i++) |
981 | 0 | st->stats.bytes += st->tables[i]->size; |
982 | 0 | err = reftable_writer_set_limits(wr, st->tables[first]->min_update_index, |
983 | 0 | st->tables[last]->max_update_index); |
984 | 0 | if (err < 0) |
985 | 0 | goto done; |
986 | | |
987 | 0 | err = reftable_merged_table_new(&mt, st->tables + first, subtabs_len, |
988 | 0 | st->opts.hash_id); |
989 | 0 | if (err < 0) |
990 | 0 | goto done; |
991 | | |
992 | 0 | err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_REF); |
993 | 0 | if (err < 0) |
994 | 0 | goto done; |
995 | | |
996 | 0 | err = reftable_iterator_seek_ref(&it, ""); |
997 | 0 | if (err < 0) |
998 | 0 | goto done; |
999 | | |
1000 | 0 | while (1) { |
1001 | 0 | err = reftable_iterator_next_ref(&it, &ref); |
1002 | 0 | if (err > 0) { |
1003 | 0 | err = 0; |
1004 | 0 | break; |
1005 | 0 | } |
1006 | 0 | if (err < 0) |
1007 | 0 | goto done; |
1008 | | |
1009 | 0 | if (first == 0 && reftable_ref_record_is_deletion(&ref)) { |
1010 | 0 | continue; |
1011 | 0 | } |
1012 | | |
1013 | 0 | err = reftable_writer_add_ref(wr, &ref); |
1014 | 0 | if (err < 0) |
1015 | 0 | goto done; |
1016 | 0 | entries++; |
1017 | 0 | } |
1018 | 0 | reftable_iterator_destroy(&it); |
1019 | |
|
1020 | 0 | err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_LOG); |
1021 | 0 | if (err < 0) |
1022 | 0 | goto done; |
1023 | | |
1024 | 0 | err = reftable_iterator_seek_log(&it, ""); |
1025 | 0 | if (err < 0) |
1026 | 0 | goto done; |
1027 | | |
1028 | 0 | while (1) { |
1029 | 0 | err = reftable_iterator_next_log(&it, &log); |
1030 | 0 | if (err > 0) { |
1031 | 0 | err = 0; |
1032 | 0 | break; |
1033 | 0 | } |
1034 | 0 | if (err < 0) |
1035 | 0 | goto done; |
1036 | 0 | if (first == 0 && reftable_log_record_is_deletion(&log)) { |
1037 | 0 | continue; |
1038 | 0 | } |
1039 | | |
1040 | 0 | if (config && config->min_update_index > 0 && |
1041 | 0 | log.update_index < config->min_update_index) { |
1042 | 0 | continue; |
1043 | 0 | } |
1044 | | |
1045 | 0 | if (config && config->time > 0 && |
1046 | 0 | log.value.update.time < config->time) { |
1047 | 0 | continue; |
1048 | 0 | } |
1049 | | |
1050 | 0 | err = reftable_writer_add_log(wr, &log); |
1051 | 0 | if (err < 0) |
1052 | 0 | goto done; |
1053 | 0 | entries++; |
1054 | 0 | } |
1055 | | |
1056 | 0 | done: |
1057 | 0 | reftable_iterator_destroy(&it); |
1058 | 0 | if (mt) |
1059 | 0 | reftable_merged_table_free(mt); |
1060 | 0 | reftable_ref_record_release(&ref); |
1061 | 0 | reftable_log_record_release(&log); |
1062 | 0 | st->stats.entries_written += entries; |
1063 | 0 | return err; |
1064 | 0 | } |
1065 | | |
1066 | | static int stack_compact_locked(struct reftable_stack *st, |
1067 | | size_t first, size_t last, |
1068 | | struct reftable_log_expiry_config *config, |
1069 | | struct reftable_tmpfile *tab_file_out) |
1070 | 0 | { |
1071 | 0 | struct reftable_buf next_name = REFTABLE_BUF_INIT; |
1072 | 0 | struct reftable_buf tab_file_path = REFTABLE_BUF_INIT; |
1073 | 0 | struct reftable_writer *wr = NULL; |
1074 | 0 | struct fd_writer writer= { |
1075 | 0 | .opts = &st->opts, |
1076 | 0 | }; |
1077 | 0 | struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; |
1078 | 0 | int err = 0; |
1079 | |
|
1080 | 0 | err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]), |
1081 | 0 | reftable_table_max_update_index(st->tables[last])); |
1082 | 0 | if (err < 0) |
1083 | 0 | goto done; |
1084 | | |
1085 | 0 | err = stack_filename(&tab_file_path, st, next_name.buf); |
1086 | 0 | if (err < 0) |
1087 | 0 | goto done; |
1088 | | |
1089 | 0 | err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX"); |
1090 | 0 | if (err < 0) |
1091 | 0 | goto done; |
1092 | | |
1093 | 0 | err = tmpfile_from_pattern(&tab_file, tab_file_path.buf); |
1094 | 0 | if (err < 0) |
1095 | 0 | goto done; |
1096 | | |
1097 | 0 | if (st->opts.default_permissions && |
1098 | 0 | chmod(tab_file.path, st->opts.default_permissions) < 0) { |
1099 | 0 | err = REFTABLE_IO_ERROR; |
1100 | 0 | goto done; |
1101 | 0 | } |
1102 | | |
1103 | 0 | writer.fd = tab_file.fd; |
1104 | 0 | err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, |
1105 | 0 | &writer, &st->opts); |
1106 | 0 | if (err < 0) |
1107 | 0 | goto done; |
1108 | | |
1109 | 0 | err = stack_write_compact(st, wr, first, last, config); |
1110 | 0 | if (err < 0) |
1111 | 0 | goto done; |
1112 | | |
1113 | 0 | err = reftable_writer_close(wr); |
1114 | 0 | if (err < 0) |
1115 | 0 | goto done; |
1116 | | |
1117 | 0 | err = tmpfile_close(&tab_file); |
1118 | 0 | if (err < 0) |
1119 | 0 | goto done; |
1120 | | |
1121 | 0 | *tab_file_out = tab_file; |
1122 | 0 | tab_file = REFTABLE_TMPFILE_INIT; |
1123 | |
|
1124 | 0 | done: |
1125 | 0 | tmpfile_delete(&tab_file); |
1126 | 0 | reftable_writer_free(wr); |
1127 | 0 | reftable_buf_release(&next_name); |
1128 | 0 | reftable_buf_release(&tab_file_path); |
1129 | 0 | return err; |
1130 | 0 | } |
1131 | | |
1132 | | enum stack_compact_range_flags { |
1133 | | /* |
1134 | | * Perform a best-effort compaction. That is, even if we cannot lock |
1135 | | * all tables in the specified range, we will try to compact the |
1136 | | * remaining slice. |
1137 | | */ |
1138 | | STACK_COMPACT_RANGE_BEST_EFFORT = (1 << 0), |
1139 | | }; |
1140 | | |
1141 | | /* |
1142 | | * Compact all tables in the range `[first, last)` into a single new table. |
1143 | | * |
1144 | | * This function returns `0` on success or a code `< 0` on failure. When the |
1145 | | * stack or any of the tables in the specified range are already locked then |
1146 | | * this function returns `REFTABLE_LOCK_ERROR`. This is a benign error that |
1147 | | * callers can either ignore, or they may choose to retry compaction after some |
1148 | | * amount of time. |
1149 | | */ |
1150 | | static int stack_compact_range(struct reftable_stack *st, |
1151 | | size_t first, size_t last, |
1152 | | struct reftable_log_expiry_config *expiry, |
1153 | | unsigned int flags) |
1154 | 0 | { |
1155 | 0 | struct reftable_buf tables_list_buf = REFTABLE_BUF_INIT; |
1156 | 0 | struct reftable_buf new_table_name = REFTABLE_BUF_INIT; |
1157 | 0 | struct reftable_buf new_table_path = REFTABLE_BUF_INIT; |
1158 | 0 | struct reftable_buf table_name = REFTABLE_BUF_INIT; |
1159 | 0 | struct reftable_flock tables_list_lock = REFTABLE_FLOCK_INIT; |
1160 | 0 | struct reftable_flock *table_locks = NULL; |
1161 | 0 | struct reftable_tmpfile new_table = REFTABLE_TMPFILE_INIT; |
1162 | 0 | int is_empty_table = 0, err = 0; |
1163 | 0 | size_t first_to_replace, last_to_replace; |
1164 | 0 | size_t i, nlocks = 0; |
1165 | 0 | char **names = NULL; |
1166 | |
|
1167 | 0 | if (first > last || (!expiry && first == last)) { |
1168 | 0 | err = 0; |
1169 | 0 | goto done; |
1170 | 0 | } |
1171 | | |
1172 | 0 | st->stats.attempts++; |
1173 | | |
1174 | | /* |
1175 | | * Hold the lock so that we can read "tables.list" and lock all tables |
1176 | | * which are part of the user-specified range. |
1177 | | */ |
1178 | 0 | err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); |
1179 | 0 | if (err < 0) |
1180 | 0 | goto done; |
1181 | | |
1182 | | /* |
1183 | | * Check whether the stack is up-to-date. We unfortunately cannot |
1184 | | * handle the situation gracefully in case it's _not_ up-to-date |
1185 | | * because the range of tables that the user has requested us to |
1186 | | * compact may have been changed. So instead we abort. |
1187 | | * |
1188 | | * We could in theory improve the situation by having the caller not |
1189 | | * pass in a range, but instead the list of tables to compact. If so, |
1190 | | * we could check that relevant tables still exist. But for now it's |
1191 | | * good enough to just abort. |
1192 | | */ |
1193 | 0 | err = stack_uptodate(st); |
1194 | 0 | if (err < 0) |
1195 | 0 | goto done; |
1196 | 0 | if (err > 0) { |
1197 | 0 | err = REFTABLE_OUTDATED_ERROR; |
1198 | 0 | goto done; |
1199 | 0 | } |
1200 | | |
1201 | | /* |
1202 | | * Lock all tables in the user-provided range. This is the slice of our |
1203 | | * stack which we'll compact. |
1204 | | * |
1205 | | * Note that we lock tables in reverse order from last to first. The |
1206 | | * intent behind this is to allow a newer process to perform best |
1207 | | * effort compaction of tables that it has added in the case where an |
1208 | | * older process is still busy compacting tables which are preexisting |
1209 | | * from the point of view of the newer process. |
1210 | | */ |
1211 | 0 | REFTABLE_ALLOC_ARRAY(table_locks, last - first + 1); |
1212 | 0 | if (!table_locks) { |
1213 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
1214 | 0 | goto done; |
1215 | 0 | } |
1216 | 0 | for (i = 0; i < last - first + 1; i++) |
1217 | 0 | table_locks[i] = REFTABLE_FLOCK_INIT; |
1218 | |
|
1219 | 0 | for (i = last + 1; i > first; i--) { |
1220 | 0 | err = stack_filename(&table_name, st, reftable_table_name(st->tables[i - 1])); |
1221 | 0 | if (err < 0) |
1222 | 0 | goto done; |
1223 | | |
1224 | 0 | err = flock_acquire(&table_locks[nlocks], table_name.buf, 0); |
1225 | 0 | if (err < 0) { |
1226 | | /* |
1227 | | * When the table is locked already we may do a |
1228 | | * best-effort compaction and compact only the tables |
1229 | | * that we have managed to lock so far. This of course |
1230 | | * requires that we have been able to lock at least two |
1231 | | * tables, otherwise there would be nothing to compact. |
1232 | | * In that case, we return a lock error to our caller. |
1233 | | */ |
1234 | 0 | if (err == REFTABLE_LOCK_ERROR && last - (i - 1) >= 2 && |
1235 | 0 | flags & STACK_COMPACT_RANGE_BEST_EFFORT) { |
1236 | 0 | err = 0; |
1237 | | /* |
1238 | | * The subtraction is to offset the index, the |
1239 | | * addition is to only compact up to the table |
1240 | | * of the preceding iteration. They obviously |
1241 | | * cancel each other out, but that may be |
1242 | | * non-obvious when it was omitted. |
1243 | | */ |
1244 | 0 | first = (i - 1) + 1; |
1245 | 0 | break; |
1246 | 0 | } |
1247 | | |
1248 | 0 | goto done; |
1249 | 0 | } |
1250 | | |
1251 | | /* |
1252 | | * We need to close the lockfiles as we might otherwise easily |
1253 | | * run into file descriptor exhaustion when we compress a lot |
1254 | | * of tables. |
1255 | | */ |
1256 | 0 | err = flock_close(&table_locks[nlocks++]); |
1257 | 0 | if (err < 0) |
1258 | 0 | goto done; |
1259 | 0 | } |
1260 | | |
1261 | | /* |
1262 | | * We have locked all tables in our range and can thus release the |
1263 | | * "tables.list" lock while compacting the locked tables. This allows |
1264 | | * concurrent updates to the stack to proceed. |
1265 | | */ |
1266 | 0 | err = flock_release(&tables_list_lock); |
1267 | 0 | if (err < 0) { |
1268 | 0 | err = REFTABLE_IO_ERROR; |
1269 | 0 | goto done; |
1270 | 0 | } |
1271 | | |
1272 | | /* |
1273 | | * Compact the now-locked tables into a new table. Note that compacting |
1274 | | * these tables may end up with an empty new table in case tombstones |
1275 | | * end up cancelling out all refs in that range. |
1276 | | */ |
1277 | 0 | err = stack_compact_locked(st, first, last, expiry, &new_table); |
1278 | 0 | if (err < 0) { |
1279 | 0 | if (err != REFTABLE_EMPTY_TABLE_ERROR) |
1280 | 0 | goto done; |
1281 | 0 | is_empty_table = 1; |
1282 | 0 | } |
1283 | | |
1284 | | /* |
1285 | | * Now that we have written the new, compacted table we need to re-lock |
1286 | | * "tables.list". We'll then replace the compacted range of tables with |
1287 | | * the new table. |
1288 | | */ |
1289 | 0 | err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); |
1290 | 0 | if (err < 0) |
1291 | 0 | goto done; |
1292 | | |
1293 | 0 | if (st->opts.default_permissions) { |
1294 | 0 | if (chmod(tables_list_lock.path, |
1295 | 0 | st->opts.default_permissions) < 0) { |
1296 | 0 | err = REFTABLE_IO_ERROR; |
1297 | 0 | goto done; |
1298 | 0 | } |
1299 | 0 | } |
1300 | | |
1301 | | /* |
1302 | | * As we have unlocked the stack while compacting our slice of tables |
1303 | | * it may have happened that a concurrently running process has updated |
1304 | | * the stack while we were compacting. In that case, we need to check |
1305 | | * whether the tables that we have just compacted still exist in the |
1306 | | * stack in the exact same order as we have compacted them. |
1307 | | * |
1308 | | * If they do exist, then it is fine to continue and replace those |
1309 | | * tables with our compacted version. If they don't, then we need to |
1310 | | * abort. |
1311 | | */ |
1312 | 0 | err = stack_uptodate(st); |
1313 | 0 | if (err < 0) |
1314 | 0 | goto done; |
1315 | 0 | if (err > 0) { |
1316 | 0 | ssize_t new_offset = -1; |
1317 | 0 | int fd; |
1318 | |
|
1319 | 0 | fd = open(st->list_file, O_RDONLY); |
1320 | 0 | if (fd < 0) { |
1321 | 0 | err = REFTABLE_IO_ERROR; |
1322 | 0 | goto done; |
1323 | 0 | } |
1324 | | |
1325 | 0 | err = fd_read_lines(fd, &names); |
1326 | 0 | close(fd); |
1327 | 0 | if (err < 0) |
1328 | 0 | goto done; |
1329 | | |
1330 | | /* |
1331 | | * Search for the offset of the first table that we have |
1332 | | * compacted in the updated "tables.list" file. |
1333 | | */ |
1334 | 0 | for (size_t i = 0; names[i]; i++) { |
1335 | 0 | if (strcmp(names[i], st->tables[first]->name)) |
1336 | 0 | continue; |
1337 | | |
1338 | | /* |
1339 | | * We have found the first entry. Verify that all the |
1340 | | * subsequent tables we have compacted still exist in |
1341 | | * the modified stack in the exact same order as we |
1342 | | * have compacted them. |
1343 | | */ |
1344 | 0 | for (size_t j = 1; j < last - first + 1; j++) { |
1345 | 0 | const char *old = first + j < st->merged->tables_len ? |
1346 | 0 | st->tables[first + j]->name : NULL; |
1347 | 0 | const char *new = names[i + j]; |
1348 | | |
1349 | | /* |
1350 | | * If some entries are missing or in case the tables |
1351 | | * have changed then we need to bail out. Again, this |
1352 | | * shouldn't ever happen because we have locked the |
1353 | | * tables we are compacting. |
1354 | | */ |
1355 | 0 | if (!old || !new || strcmp(old, new)) { |
1356 | 0 | err = REFTABLE_OUTDATED_ERROR; |
1357 | 0 | goto done; |
1358 | 0 | } |
1359 | 0 | } |
1360 | | |
1361 | 0 | new_offset = i; |
1362 | 0 | break; |
1363 | 0 | } |
1364 | | |
1365 | | /* |
1366 | | * In case we didn't find our compacted tables in the stack we |
1367 | | * need to bail out. In theory, this should have never happened |
1368 | | * because we locked the tables we are compacting. |
1369 | | */ |
1370 | 0 | if (new_offset < 0) { |
1371 | 0 | err = REFTABLE_OUTDATED_ERROR; |
1372 | 0 | goto done; |
1373 | 0 | } |
1374 | | |
1375 | | /* |
1376 | | * We have found the new range that we want to replace, so |
1377 | | * let's update the range of tables that we want to replace. |
1378 | | */ |
1379 | 0 | first_to_replace = new_offset; |
1380 | 0 | last_to_replace = last + (new_offset - first); |
1381 | 0 | } else { |
1382 | | /* |
1383 | | * `fd_read_lines()` uses a `NULL` sentinel to indicate that |
1384 | | * the array is at its end. As we use `free_names()` to free |
1385 | | * the array, we need to include this sentinel value here and |
1386 | | * thus have to allocate `tables_len + 1` many entries. |
1387 | | */ |
1388 | 0 | REFTABLE_CALLOC_ARRAY(names, st->merged->tables_len + 1); |
1389 | 0 | if (!names) { |
1390 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
1391 | 0 | goto done; |
1392 | 0 | } |
1393 | | |
1394 | 0 | for (size_t i = 0; i < st->merged->tables_len; i++) { |
1395 | 0 | names[i] = reftable_strdup(st->tables[i]->name); |
1396 | 0 | if (!names[i]) { |
1397 | 0 | err = REFTABLE_OUT_OF_MEMORY_ERROR; |
1398 | 0 | goto done; |
1399 | 0 | } |
1400 | 0 | } |
1401 | 0 | first_to_replace = first; |
1402 | 0 | last_to_replace = last; |
1403 | 0 | } |
1404 | | |
1405 | | /* |
1406 | | * If the resulting compacted table is not empty, then we need to move |
1407 | | * it into place now. |
1408 | | */ |
1409 | 0 | if (!is_empty_table) { |
1410 | 0 | err = format_name(&new_table_name, st->tables[first]->min_update_index, |
1411 | 0 | st->tables[last]->max_update_index); |
1412 | 0 | if (err < 0) |
1413 | 0 | goto done; |
1414 | | |
1415 | 0 | err = reftable_buf_addstr(&new_table_name, ".ref"); |
1416 | 0 | if (err < 0) |
1417 | 0 | goto done; |
1418 | | |
1419 | 0 | err = stack_filename(&new_table_path, st, new_table_name.buf); |
1420 | 0 | if (err < 0) |
1421 | 0 | goto done; |
1422 | | |
1423 | 0 | err = tmpfile_rename(&new_table, new_table_path.buf); |
1424 | 0 | if (err < 0) |
1425 | 0 | goto done; |
1426 | 0 | } |
1427 | | |
1428 | | /* |
1429 | | * Write the new "tables.list" contents with the compacted table we |
1430 | | * have just written. In case the compacted table became empty we |
1431 | | * simply skip writing it. |
1432 | | */ |
1433 | 0 | for (i = 0; i < first_to_replace; i++) { |
1434 | 0 | if ((err = reftable_buf_addstr(&tables_list_buf, names[i])) < 0 || |
1435 | 0 | (err = reftable_buf_addstr(&tables_list_buf, "\n")) < 0) |
1436 | 0 | goto done; |
1437 | 0 | } |
1438 | 0 | if (!is_empty_table) { |
1439 | 0 | if ((err = reftable_buf_addstr(&tables_list_buf, new_table_name.buf)) < 0 || |
1440 | 0 | (err = reftable_buf_addstr(&tables_list_buf, "\n")) < 0) |
1441 | 0 | goto done; |
1442 | 0 | } |
1443 | 0 | for (i = last_to_replace + 1; names[i]; i++) { |
1444 | 0 | if ((err = reftable_buf_addstr(&tables_list_buf, names[i])) < 0 || |
1445 | 0 | (err = reftable_buf_addstr(&tables_list_buf, "\n")) < 0) |
1446 | 0 | goto done; |
1447 | 0 | } |
1448 | | |
1449 | 0 | err = reftable_write_data(tables_list_lock.fd, |
1450 | 0 | tables_list_buf.buf, tables_list_buf.len); |
1451 | 0 | if (err < 0) { |
1452 | 0 | err = REFTABLE_IO_ERROR; |
1453 | 0 | unlink(new_table_path.buf); |
1454 | 0 | goto done; |
1455 | 0 | } |
1456 | | |
1457 | 0 | err = fsync(tables_list_lock.fd); |
1458 | 0 | if (err < 0) { |
1459 | 0 | err = REFTABLE_IO_ERROR; |
1460 | 0 | unlink(new_table_path.buf); |
1461 | 0 | goto done; |
1462 | 0 | } |
1463 | | |
1464 | 0 | err = flock_commit(&tables_list_lock); |
1465 | 0 | if (err < 0) { |
1466 | 0 | err = REFTABLE_IO_ERROR; |
1467 | 0 | unlink(new_table_path.buf); |
1468 | 0 | goto done; |
1469 | 0 | } |
1470 | | |
1471 | | /* |
1472 | | * Reload the stack before deleting the compacted tables. We can only |
1473 | | * delete the files after we closed them on Windows, so this needs to |
1474 | | * happen first. |
1475 | | */ |
1476 | 0 | err = reftable_stack_reload_maybe_reuse(st, first < last); |
1477 | 0 | if (err < 0) |
1478 | 0 | goto done; |
1479 | | |
1480 | | /* |
1481 | | * Delete the old tables. They may still be in use by concurrent |
1482 | | * readers, so it is expected that unlinking tables may fail. |
1483 | | */ |
1484 | 0 | for (i = 0; i < nlocks; i++) { |
1485 | 0 | struct reftable_flock *table_lock = &table_locks[i]; |
1486 | |
|
1487 | 0 | reftable_buf_reset(&table_name); |
1488 | 0 | err = reftable_buf_add(&table_name, table_lock->path, |
1489 | 0 | strlen(table_lock->path) - strlen(".lock")); |
1490 | 0 | if (err) |
1491 | 0 | continue; |
1492 | | |
1493 | 0 | unlink(table_name.buf); |
1494 | 0 | } |
1495 | |
|
1496 | 0 | done: |
1497 | 0 | flock_release(&tables_list_lock); |
1498 | 0 | for (i = 0; table_locks && i < nlocks; i++) |
1499 | 0 | flock_release(&table_locks[i]); |
1500 | 0 | reftable_free(table_locks); |
1501 | |
|
1502 | 0 | tmpfile_delete(&new_table); |
1503 | 0 | reftable_buf_release(&new_table_name); |
1504 | 0 | reftable_buf_release(&new_table_path); |
1505 | 0 | reftable_buf_release(&tables_list_buf); |
1506 | 0 | reftable_buf_release(&table_name); |
1507 | 0 | free_names(names); |
1508 | |
|
1509 | 0 | if (err == REFTABLE_LOCK_ERROR) |
1510 | 0 | st->stats.failures++; |
1511 | |
|
1512 | 0 | return err; |
1513 | 0 | } |
1514 | | |
1515 | | int reftable_stack_compact_all(struct reftable_stack *st, |
1516 | | struct reftable_log_expiry_config *config) |
1517 | 0 | { |
1518 | 0 | size_t last = st->merged->tables_len ? st->merged->tables_len - 1 : 0; |
1519 | 0 | return stack_compact_range(st, 0, last, config, 0); |
1520 | 0 | } |
1521 | | |
1522 | | static int segment_size(struct segment *s) |
1523 | 0 | { |
1524 | 0 | return s->end - s->start; |
1525 | 0 | } |
1526 | | |
1527 | | struct segment suggest_compaction_segment(uint64_t *sizes, size_t n, |
1528 | | uint8_t factor) |
1529 | 0 | { |
1530 | 0 | struct segment seg = { 0 }; |
1531 | 0 | uint64_t bytes; |
1532 | 0 | size_t i; |
1533 | |
|
1534 | 0 | if (!factor) |
1535 | 0 | factor = DEFAULT_GEOMETRIC_FACTOR; |
1536 | | |
1537 | | /* |
1538 | | * If there are no tables or only a single one then we don't have to |
1539 | | * compact anything. The sequence is geometric by definition already. |
1540 | | */ |
1541 | 0 | if (n <= 1) |
1542 | 0 | return seg; |
1543 | | |
1544 | | /* |
1545 | | * Find the ending table of the compaction segment needed to restore the |
1546 | | * geometric sequence. Note that the segment end is exclusive. |
1547 | | * |
1548 | | * To do so, we iterate backwards starting from the most recent table |
1549 | | * until a valid segment end is found. If the preceding table is smaller |
1550 | | * than the current table multiplied by the geometric factor (2), the |
1551 | | * compaction segment end has been identified. |
1552 | | * |
1553 | | * Tables after the ending point are not added to the byte count because |
1554 | | * they are already valid members of the geometric sequence. Due to the |
1555 | | * properties of a geometric sequence, it is not possible for the sum of |
1556 | | * these tables to exceed the value of the ending point table. |
1557 | | * |
1558 | | * Example table size sequence requiring no compaction: |
1559 | | * 64, 32, 16, 8, 4, 2, 1 |
1560 | | * |
1561 | | * Example table size sequence where compaction segment end is set to |
1562 | | * the last table. Since the segment end is exclusive, the last table is |
1563 | | * excluded during subsequent compaction and the table with size 3 is |
1564 | | * the final table included: |
1565 | | * 64, 32, 16, 8, 4, 3, 1 |
1566 | | */ |
1567 | 0 | for (i = n - 1; i > 0; i--) { |
1568 | 0 | if (sizes[i - 1] < sizes[i] * factor) { |
1569 | 0 | seg.end = i + 1; |
1570 | 0 | bytes = sizes[i]; |
1571 | 0 | break; |
1572 | 0 | } |
1573 | 0 | } |
1574 | | |
1575 | | /* |
1576 | | * Find the starting table of the compaction segment by iterating |
1577 | | * through the remaining tables and keeping track of the accumulated |
1578 | | * size of all tables seen from the segment end table. The previous |
1579 | | * table is compared to the accumulated size because the tables from the |
1580 | | * segment end are merged backwards recursively. |
1581 | | * |
1582 | | * Note that we keep iterating even after we have found the first |
1583 | | * starting point. This is because there may be tables in the stack |
1584 | | * preceding that first starting point which violate the geometric |
1585 | | * sequence. |
1586 | | * |
1587 | | * Example compaction segment start set to table with size 32: |
1588 | | * 128, 32, 16, 8, 4, 3, 1 |
1589 | | */ |
1590 | 0 | for (; i > 0; i--) { |
1591 | 0 | uint64_t curr = bytes; |
1592 | 0 | bytes += sizes[i - 1]; |
1593 | |
|
1594 | 0 | if (sizes[i - 1] < curr * factor) { |
1595 | 0 | seg.start = i - 1; |
1596 | 0 | seg.bytes = bytes; |
1597 | 0 | } |
1598 | 0 | } |
1599 | |
|
1600 | 0 | return seg; |
1601 | 0 | } |
1602 | | |
1603 | | static int stack_segments_for_compaction(struct reftable_stack *st, |
1604 | | struct segment *seg) |
1605 | 0 | { |
1606 | 0 | int version = (st->opts.hash_id == REFTABLE_HASH_SHA1) ? 1 : 2; |
1607 | 0 | int overhead = header_size(version) - 1; |
1608 | 0 | uint64_t *sizes; |
1609 | |
|
1610 | 0 | REFTABLE_CALLOC_ARRAY(sizes, st->merged->tables_len); |
1611 | 0 | if (!sizes) |
1612 | 0 | return REFTABLE_OUT_OF_MEMORY_ERROR; |
1613 | | |
1614 | 0 | for (size_t i = 0; i < st->merged->tables_len; i++) |
1615 | 0 | sizes[i] = st->tables[i]->size - overhead; |
1616 | |
|
1617 | 0 | *seg = suggest_compaction_segment(sizes, st->merged->tables_len, |
1618 | 0 | st->opts.auto_compaction_factor); |
1619 | 0 | reftable_free(sizes); |
1620 | |
|
1621 | 0 | return 0; |
1622 | 0 | } |
1623 | | |
1624 | | static int update_segment_if_compaction_required(struct reftable_stack *st, |
1625 | | struct segment *seg, |
1626 | | bool use_geometric, |
1627 | | bool *required) |
1628 | 0 | { |
1629 | 0 | int err; |
1630 | |
|
1631 | 0 | if (st->merged->tables_len < 2) { |
1632 | 0 | *required = false; |
1633 | 0 | return 0; |
1634 | 0 | } |
1635 | | |
1636 | 0 | if (!use_geometric) { |
1637 | 0 | *required = true; |
1638 | 0 | return 0; |
1639 | 0 | } |
1640 | | |
1641 | 0 | err = stack_segments_for_compaction(st, seg); |
1642 | 0 | if (err) |
1643 | 0 | return err; |
1644 | | |
1645 | 0 | *required = segment_size(seg) > 0; |
1646 | 0 | return 0; |
1647 | 0 | } |
1648 | | |
1649 | | int reftable_stack_compaction_required(struct reftable_stack *st, |
1650 | | bool use_heuristics, |
1651 | | bool *required) |
1652 | 0 | { |
1653 | 0 | struct segment seg; |
1654 | 0 | return update_segment_if_compaction_required(st, &seg, use_heuristics, |
1655 | 0 | required); |
1656 | 0 | } |
1657 | | |
1658 | | int reftable_stack_auto_compact(struct reftable_stack *st) |
1659 | 0 | { |
1660 | 0 | struct segment seg; |
1661 | 0 | bool required; |
1662 | 0 | int err; |
1663 | |
|
1664 | 0 | err = update_segment_if_compaction_required(st, &seg, true, &required); |
1665 | 0 | if (err) |
1666 | 0 | return err; |
1667 | | |
1668 | 0 | if (required) |
1669 | 0 | return stack_compact_range(st, seg.start, seg.end - 1, |
1670 | 0 | NULL, STACK_COMPACT_RANGE_BEST_EFFORT); |
1671 | | |
1672 | 0 | return 0; |
1673 | 0 | } |
1674 | | |
1675 | | struct reftable_compaction_stats * |
1676 | | reftable_stack_compaction_stats(struct reftable_stack *st) |
1677 | 0 | { |
1678 | 0 | return &st->stats; |
1679 | 0 | } |
1680 | | |
1681 | | int reftable_stack_read_ref(struct reftable_stack *st, const char *refname, |
1682 | | struct reftable_ref_record *ref) |
1683 | 0 | { |
1684 | 0 | struct reftable_iterator it = { 0 }; |
1685 | 0 | int ret; |
1686 | |
|
1687 | 0 | ret = reftable_merged_table_init_ref_iterator(st->merged, &it); |
1688 | 0 | if (ret) |
1689 | 0 | goto out; |
1690 | | |
1691 | 0 | ret = reftable_iterator_seek_ref(&it, refname); |
1692 | 0 | if (ret) |
1693 | 0 | goto out; |
1694 | | |
1695 | 0 | ret = reftable_iterator_next_ref(&it, ref); |
1696 | 0 | if (ret) |
1697 | 0 | goto out; |
1698 | | |
1699 | 0 | if (strcmp(ref->refname, refname) || |
1700 | 0 | reftable_ref_record_is_deletion(ref)) { |
1701 | 0 | reftable_ref_record_release(ref); |
1702 | 0 | ret = 1; |
1703 | 0 | goto out; |
1704 | 0 | } |
1705 | | |
1706 | 0 | out: |
1707 | 0 | reftable_iterator_destroy(&it); |
1708 | 0 | return ret; |
1709 | 0 | } |
1710 | | |
1711 | | int reftable_stack_read_log(struct reftable_stack *st, const char *refname, |
1712 | | struct reftable_log_record *log) |
1713 | 0 | { |
1714 | 0 | struct reftable_iterator it = {0}; |
1715 | 0 | int err; |
1716 | |
|
1717 | 0 | err = reftable_stack_init_log_iterator(st, &it); |
1718 | 0 | if (err) |
1719 | 0 | goto done; |
1720 | | |
1721 | 0 | err = reftable_iterator_seek_log(&it, refname); |
1722 | 0 | if (err) |
1723 | 0 | goto done; |
1724 | | |
1725 | 0 | err = reftable_iterator_next_log(&it, log); |
1726 | 0 | if (err) |
1727 | 0 | goto done; |
1728 | | |
1729 | 0 | if (strcmp(log->refname, refname) || |
1730 | 0 | reftable_log_record_is_deletion(log)) { |
1731 | 0 | err = 1; |
1732 | 0 | goto done; |
1733 | 0 | } |
1734 | | |
1735 | 0 | done: |
1736 | 0 | if (err) { |
1737 | 0 | reftable_log_record_release(log); |
1738 | 0 | } |
1739 | 0 | reftable_iterator_destroy(&it); |
1740 | 0 | return err; |
1741 | 0 | } |
1742 | | |
1743 | | static int is_table_name(const char *s) |
1744 | 0 | { |
1745 | 0 | const char *dot = strrchr(s, '.'); |
1746 | 0 | return dot && !strcmp(dot, ".ref"); |
1747 | 0 | } |
1748 | | |
1749 | | static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max, |
1750 | | const char *name) |
1751 | 0 | { |
1752 | 0 | int err = 0; |
1753 | 0 | uint64_t update_idx = 0; |
1754 | 0 | struct reftable_block_source src = { NULL }; |
1755 | 0 | struct reftable_table *table = NULL; |
1756 | 0 | struct reftable_buf table_path = REFTABLE_BUF_INIT; |
1757 | |
|
1758 | 0 | err = stack_filename(&table_path, st, name); |
1759 | 0 | if (err < 0) |
1760 | 0 | goto done; |
1761 | | |
1762 | 0 | err = reftable_block_source_from_file(&src, table_path.buf); |
1763 | 0 | if (err < 0) |
1764 | 0 | goto done; |
1765 | | |
1766 | 0 | err = reftable_table_new(&table, &src, name); |
1767 | 0 | if (err < 0) |
1768 | 0 | goto done; |
1769 | | |
1770 | 0 | update_idx = reftable_table_max_update_index(table); |
1771 | 0 | reftable_table_decref(table); |
1772 | |
|
1773 | 0 | if (update_idx <= max) { |
1774 | 0 | unlink(table_path.buf); |
1775 | 0 | } |
1776 | 0 | done: |
1777 | 0 | reftable_buf_release(&table_path); |
1778 | 0 | } |
1779 | | |
1780 | | static int reftable_stack_clean_locked(struct reftable_stack *st) |
1781 | 0 | { |
1782 | 0 | uint64_t max = reftable_merged_table_max_update_index( |
1783 | 0 | reftable_stack_merged_table(st)); |
1784 | 0 | DIR *dir = opendir(st->reftable_dir); |
1785 | 0 | struct dirent *d = NULL; |
1786 | 0 | if (!dir) { |
1787 | 0 | return REFTABLE_IO_ERROR; |
1788 | 0 | } |
1789 | | |
1790 | 0 | while ((d = readdir(dir))) { |
1791 | 0 | int found = 0; |
1792 | 0 | if (!is_table_name(d->d_name)) |
1793 | 0 | continue; |
1794 | | |
1795 | 0 | for (size_t i = 0; !found && i < st->tables_len; i++) |
1796 | 0 | found = !strcmp(reftable_table_name(st->tables[i]), d->d_name); |
1797 | 0 | if (found) |
1798 | 0 | continue; |
1799 | | |
1800 | 0 | remove_maybe_stale_table(st, max, d->d_name); |
1801 | 0 | } |
1802 | |
|
1803 | 0 | closedir(dir); |
1804 | 0 | return 0; |
1805 | 0 | } |
1806 | | |
1807 | | int reftable_stack_clean(struct reftable_stack *st) |
1808 | 0 | { |
1809 | 0 | struct reftable_addition *add = NULL; |
1810 | 0 | int err = reftable_stack_new_addition(&add, st, 0); |
1811 | 0 | if (err < 0) { |
1812 | 0 | goto done; |
1813 | 0 | } |
1814 | | |
1815 | 0 | err = reftable_stack_reload(st); |
1816 | 0 | if (err < 0) { |
1817 | 0 | goto done; |
1818 | 0 | } |
1819 | | |
1820 | 0 | err = reftable_stack_clean_locked(st); |
1821 | |
|
1822 | 0 | done: |
1823 | 0 | reftable_addition_destroy(add); |
1824 | 0 | return err; |
1825 | 0 | } |
1826 | | |
1827 | | enum reftable_hash reftable_stack_hash_id(struct reftable_stack *st) |
1828 | 0 | { |
1829 | 0 | return reftable_merged_table_hash_id(st->merged); |
1830 | 0 | } |