Skip to content

Instantly share code, notes, and snippets.

@whitslack
Created March 3, 2020 01:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whitslack/f32ecc241ee1ac1e319104616f9a2df5 to your computer and use it in GitHub Desktop.
Save whitslack/f32ecc241ee1ac1e319104616f9a2df5 to your computer and use it in GitHub Desktop.
bsync: a write-minimizing block device synchronizing utility
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <sysexits.h>
#include <unistd.h>
#include <sys/mman.h>
static bool all_balls(const void *buf, size_t size) {
for (const char *p = buf, *q = p + size; p < q; ++p) {
if (*p) {
return false;
}
}
return true;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "usage: %s <input> <output>\n", argv[0]);
return -1;
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
int in_fd = open(argv[1], O_RDONLY);
if (in_fd < 0) {
error(EX_NOINPUT, errno, "%s: open", argv[1]);
}
int out_fd = open(argv[2], O_RDWR);
if (out_fd < 0) {
error(EX_NOINPUT, errno, "%s: open", argv[2]);
}
off_t size = lseek(in_fd, 0, SEEK_END);
if (size < 0) {
error(EX_OSERR, errno, "%s: lseek", argv[1]);
}
off_t out_size = lseek(out_fd, 0, SEEK_END);
if (out_size < 0) {
error(EX_OSERR, errno, "%s: lseek", argv[2]);
}
if (size != out_size) {
fputs("input and output sizes must match\n", stderr);
return EX_DATAERR;
}
long page_size = sysconf(_SC_PAGESIZE);
if (page_size < 0) {
perror("sysconf");
return EX_OSERR;
}
if (size % page_size != 0) {
fputs("size must be a multiple of page size\n", stderr);
return EX_DATAERR;
}
const void *in = mmap(NULL, size, PROT_READ, MAP_SHARED, in_fd, 0);
if (!~(uintptr_t) in) {
error(EX_OSERR, errno, "%s: mmap", argv[1]);
}
void *out = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, out_fd, 0);
if (!~(uintptr_t) out) {
error(EX_OSERR, errno, "%s: mmap", argv[2]);
}
madvise((void *) in, size, MADV_SEQUENTIAL);
madvise(out, size, MADV_SEQUENTIAL);
const void *in_tail = in;
void *out_tail = out;
bool try_discard = true;
void *discard_begin = out, *discard_end = out;
for (off_t rem = size; rem > 0;) {
if (memcmp(out, in, page_size) != 0) {
if (try_discard && all_balls(in, page_size)) {
if (discard_end != out) {
size_t discard_size = discard_end - discard_begin;
if (discard_size > 0 && madvise(discard_begin, discard_size, MADV_REMOVE) != 0) {
memset(discard_begin, 0, discard_size);
try_discard = false;
}
discard_begin = out;
}
discard_end = (char *) out + page_size;
}
else {
memcpy(out, in, page_size);
}
}
in = (const char *) in + page_size;
out = (char *) out + page_size;
rem -= page_size;
if ((rem & (1 << 25) - 1) == 0) {
madvise((void *) in_tail, in - in_tail, MADV_DONTNEED), in_tail = in;
madvise(out_tail, out - out_tail, MADV_DONTNEED), out_tail = out;
size_t advice_len = rem < 2 << 25 ? (size_t) rem : 2 << 25;
madvise((void *) in, advice_len, MADV_WILLNEED);
madvise(out, advice_len, MADV_WILLNEED);
fprintf(stderr, "\r%.1f%%", (double) (size - rem) / (double) size * 100);
fflush(stderr);
}
}
size_t discard_size = discard_end - discard_begin;
if (discard_size > 0 && madvise(discard_begin, discard_size, MADV_REMOVE) != 0) {
memset(discard_begin, 0, discard_size);
}
putc('\n', stderr);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment