/* 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
}