aboutsummaryrefslogblamecommitdiffstats
path: root/map.c
blob: e72b02a2fe712260016aa24cf030dea3d8cdf201 (plain) (tree)




































































































































































































































































































































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