aboutsummaryrefslogtreecommitdiffstats
path: root/map.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--map.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/map.c b/map.c
new file mode 100644
index 0000000..e72b02a
--- /dev/null
+++ b/map.c
@@ -0,0 +1,325 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+add_span(struct status *status, off_t off, off_t amount, size_t blocksize, int try_join)
+{
+ off_t end = off + amount;
+
+ while ((off_t)(blocksize >> 1) >= amount)
+ blocksize >>= 1;
+
+ if (try_join) {
+ if (off == status->spans[status->nspans - 1U].end || end == status->spans[status->nspans - 1U].start) {
+ status->spans[status->nspans - 1U].start = MIN(off, status->spans[status->nspans - 1U].start);
+ status->spans[status->nspans - 1U].end = MAX(end, status->spans[status->nspans - 1U].end);
+ status->spans[status->nspans - 1U].bad += amount;
+ status->spans[status->nspans - 1U].blocksize = MAX(blocksize, status->spans[status->nspans - 1U].blocksize);
+ return;
+ }
+ }
+
+ if (status->nspans == status->spans_size) {
+ status->spans_size += 1024;
+ status->spans = ereallocarray(status->spans, status->spans_size, sizeof(*status->spans));
+ }
+
+ status->spans[status->nspans].start = off;
+ status->spans[status->nspans].end = end;
+ status->spans[status->nspans].bad = amount;
+ status->spans[status->nspans].blocksize = blocksize;
+ status->nspans++;
+}
+
+
+void
+dump_map(int fd, const struct status *status, const char *fname)
+{
+ size_t i;
+ int r;
+
+ if (status->nspans == status->span_off)
+ return;
+
+ for (i = status->span_off; i < status->nspans; i++) {
+ r = dprintf(fd, "%s%jx-%jx/%zx",
+ i ? "," : "0x",
+ (uintmax_t)status->spans[i].start,
+ (uintmax_t)status->spans[i].end,
+ status->spans[i].blocksize);
+ if (r < 0)
+ goto fail;
+ }
+ r = dprintf(fd, "\n");
+ if (r < 0)
+ goto fail;
+
+ return;
+
+fail:
+ eprintf("dprintf %s:", fname);
+}
+
+
+off_t
+measure_map_dump(const struct status *status)
+{
+ size_t i;
+ int r;
+ off_t ret = 1;
+
+ if (status->nspans == status->span_off)
+ return 0;
+
+ for (i = status->span_off; i < status->nspans; i++) {
+ r = snprintf(NULL, 0, "%s%jx-%jx/%zx",
+ i ? "," : "0x",
+ (uintmax_t)status->spans[i].start,
+ (uintmax_t)status->spans[i].end,
+ status->spans[i].blocksize);
+ if (r < 0)
+ eprintf("snprintf:");
+ ret += r;
+ }
+
+ return ret;
+}
+
+
+static int
+spancmp(const void *av, const void *bv)
+{
+ const struct span *a = av, *b = bv;
+ return a->start < b->start ? -1 : a->start > b->start;
+}
+
+
+off_t
+load_map(int fd, struct status *status, enum direction direction, const char *fname)
+{
+ char buf[8UL << 10], *end;
+ size_t off = 0;
+ size_t p = 0, start = 0, i, j;
+ ssize_t r;
+ int empty = 1, state = 0, ended = 0;
+ uintmax_t v1 = 0, v2 = 0, v3 = 0, *vp;
+ struct span refspan = status->spans[0], t;
+ off_t preshredded, min_start;
+
+ for (;;) {
+ r = read(fd, &buf[off], sizeof(buf) - off);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ eprintf("read %s:", fname);
+ }
+ off += (size_t)r;
+ if (empty) {
+ if (off < 2)
+ continue;
+ status->nspans = 0;
+ empty = 0;
+ if (buf[0] != '0' || buf[1] != 'x')
+ goto invalid;
+ p = 2U;
+ }
+ if (ended)
+ goto invalid;
+
+ while (p < off) {
+ switch (state) {
+ case 0:
+ errno = 0;
+ /* fall through */
+ case 2:
+ case 4:
+ start = p;
+ if (!isxdigit(buf[p]))
+ goto invalid;
+ p++;
+ state += 1;
+ break;
+ case 1:
+ vp = &v1;
+ goto value;
+ case 3:
+ vp = &v2;
+ goto value;
+ case 5:
+ vp = &v3;
+ value:
+ if (isxdigit(buf[p])) {
+ p++;
+ break;
+ }
+ if (state == 1) {
+ if (buf[p] != '-')
+ goto invalid;
+ state += 1;
+ } else {
+ if (state == 3 && buf[p] == '/') {
+ state += 1;
+ } else if (buf[p] == ',') {
+ v3 = refspan.blocksize;
+ state += 3;
+ } else if (buf[p] == '\n') {
+ v3 = refspan.blocksize;
+ state += 3;
+ ended = 1;
+ } else {
+ goto invalid;
+ }
+
+ }
+ buf[p++] = '\0';
+ *vp = strtoumax(&buf[start], &end, 16);
+ if (errno || *end)
+ goto invalid;
+ if (state == 6) {
+ state = 0;
+ if (v1 >= v2 || v2 > (uintmax_t)OFF_MAX)
+ goto invalid;
+ if (v3 > v2 - v1)
+ v3 = v2 - v1;
+ if (v3 > (uintmax_t)SIZE_MAX)
+ v3 = ((uintmax_t)SIZE_MAX >> 1) + 1U;
+ if (v3 > refspan.blocksize)
+ v3 = refspan.blocksize;
+
+ if ((off_t)v1 < refspan.start || (off_t)v2 > refspan.end)
+ eprintf("%s: contains out of bounds span", fname);
+
+ if (status->nspans == status->spans_size) {
+ status->spans_size += 1024;
+ status->spans = ereallocarray(status->spans, status->spans_size,
+ sizeof(*status->spans));
+ }
+
+ status->spans[status->nspans].start = (off_t)v1;
+ status->spans[status->nspans].end = (off_t)v2;
+ status->spans[status->nspans].bad = 0;
+ status->spans[status->nspans].blocksize = (size_t)v3;
+ status->nspans++;
+ }
+ break;
+ }
+ }
+
+ off = 0;
+ }
+
+ if (!ended)
+ eprintf("%s: truncated file content", fname);
+
+ if (empty)
+ return 0;
+
+ qsort(status->spans, status->nspans, sizeof(*status->spans), &spancmp);
+ min_start = -1;
+ preshredded = refspan.end - refspan.start;
+ for (i = 0; i < status->nspans; i++) {
+ if (status->spans[i].start < min_start)
+ goto invalid;
+ if (status->spans[i].start == min_start &&
+ status->spans[i].blocksize == status->spans[i - 1U].blocksize) {
+ status->spans[i - 1U].end = status->spans[i].end;
+ memmove(&status->spans[i], &status->spans[i + 1U],
+ (--status->nspans - i) * sizeof(*status->spans));
+ i--;
+ }
+ min_start = status->spans[i].end;
+ preshredded -= status->spans[i].end - status->spans[i].start;
+ }
+ if (direction == BACKWARDS) {
+ for (i = 0, j = status->nspans; --j > i; i++) {
+ t = status->spans[i];
+ status->spans[i] = status->spans[j];
+ status->spans[j] = t;
+ }
+ }
+
+ return preshredded;
+
+invalid:
+ eprintf("%s: invalid file content", fname);
+}
+
+
+void
+update_map(int fd, const struct status *status, const char *fname)
+{
+#define STR(S) (S), sizeof(S) - 1U
+
+ static char dots[4096] = {0};
+
+ off_t old_len, new_len, pos;
+ ssize_t r;
+
+ if (!dots[0])
+ memset(dots, '.', sizeof(dots));
+
+ old_len = lseek(fd, 0, SEEK_CUR);
+ if (old_len < 0)
+ eprintf("lseek %s 0 SEEK_CUR:", fname);
+
+ new_len = measure_map_dump(status);
+
+ pos = old_len;
+ if (pos < new_len) {
+ while (pos < new_len - 1) {
+ r = write(fd, dots, (size_t)MIN((off_t)sizeof(dots), new_len - 1 - pos));
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ eprintf("write %s:", fname);
+ }
+ pos += (off_t)r;
+ }
+ do {
+ r = write(fd, "\n", 1U);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0)
+ eprintf("write %s:", fname);
+ }
+
+ writeall(fd, STR(">> FIRST LINE IS CORRECT UNLESS THE AS ANOTHER NOTICE BELOW THIS ONE >>\n"), fname);
+
+ filecpy(fd, 0, old_len, fname);
+
+ writeall(fd, STR(">> LINE ABOVE IS THE CORRECT ONE >>\n"), fname);
+
+ for (;;) {
+ if (!fdatasync(fd))
+ break;
+ if (errno != EINTR)
+ eprintf("fdatasync %s:", fname);
+ }
+
+ if (lseek(fd, 0, SEEK_SET))
+ eprintf("lseek %s 0 SEEK_SET:", fname);
+
+ dump_map(fd, status, fname);
+
+ pos = lseek(fd, 0, SEEK_CUR);
+ if (pos < 0)
+ eprintf("lseek %s 0 SEEK_CUR:", fname);
+
+ for (;;) {
+ if (!ftruncate(fd, pos))
+ break;
+ if (errno != EINTR)
+ eprintf("ftruncate %s %ji:", fname, (intmax_t)pos);
+ }
+
+ for (;;) {
+ if (!fsync(fd))
+ break;
+ if (errno != EINTR)
+ eprintf("fsync %s:", fname);
+ }
+
+#undef STR
+}