era_copy/era_copy.c

186 lines
5.0 KiB
C
Raw Permalink Normal View History

2019-02-19 00:49:07 +03:00
/**
* Parses XML block lists produced by dm-era `era_invalidate` tool
2019-02-19 01:31:09 +03:00
* from `thin-provisioning-tools` from standard input, reads specified blocks
* from the data device and creates a stream with copied blocks on standard output.
* The stream can then be consumed by `era_apply`.
2019-02-19 00:49:07 +03:00
*
* Author: Vitaliy Filippov <vitalif@yourcmc.ru>, 2019
* License: GNU GPLv3.0 or later
*/
2019-02-21 02:28:36 +03:00
#define _GNU_SOURCE
2019-02-19 00:49:07 +03:00
#define _LARGEFILE64_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define XML_BUFSIZE 1024
#define MAX_COPY 0x10000000
#define SIGNATURE "ERARANGE"
2019-02-19 00:49:07 +03:00
void copy_blocks(int src, int dst, off64_t start, off64_t length)
{
off64_t copied = 0, cur = start;
ssize_t fact = 0;
while (length > copied)
{
fact = sendfile64(dst, src, &cur, length-copied > MAX_COPY ? MAX_COPY : length-copied);
if (fact < 0)
{
fprintf(stderr, "Failed to copy data: %s\n", strerror(errno));
exit(1);
}
copied += fact;
}
}
2019-02-20 01:02:10 +03:00
void read_era_invalidate_and_copy(FILE *fp, int src, int era_block_size, int print_progress)
2019-02-19 00:49:07 +03:00
{
// read input XML
char buf[XML_BUFSIZE] = { 0 };
2019-02-19 01:31:09 +03:00
char c = 0;
2023-05-13 11:08:04 +03:00
long long srcsize = 0;
long long start = 0, length = 0, total = 0;
2019-02-20 01:02:10 +03:00
long long *offsets = NULL;
int i, offsets_alloc = 0, offsets_len = 0;
2023-05-13 11:08:04 +03:00
lseek64(src, 0, SEEK_SET);
2019-02-19 00:49:07 +03:00
if (fgets(buf, XML_BUFSIZE, fp) == NULL)
{
fprintf(stderr, "Input block list is empty\n");
exit(0);
}
2019-02-19 01:31:09 +03:00
if (sscanf(buf, " <blocks > %c", &c) == 0 || c != 0)
2019-02-19 00:49:07 +03:00
{
fprintf(stderr, "<blocks> expected, but \"%s\" found\n", buf);
exit(1);
}
while (fgets(buf, XML_BUFSIZE, fp) != NULL)
{
if (sscanf(buf, " <range begin = \" %lld \" end = \" %lld \" />", &start, &length) == 2)
{
2019-02-19 01:31:09 +03:00
length = length-start;
2019-02-19 00:49:07 +03:00
}
else if (sscanf(buf, " <block block = \" %lld \" />", &start) == 1)
{
2019-02-19 01:31:09 +03:00
length = 1;
2019-02-19 00:49:07 +03:00
}
else
{
2019-02-19 01:31:09 +03:00
break;
2019-02-19 00:49:07 +03:00
}
start = start*era_block_size*512;
length = length*era_block_size*512;
2019-02-20 01:02:10 +03:00
if (offsets_len >= offsets_alloc)
{
2019-02-20 01:02:10 +03:00
offsets_alloc += 4096/sizeof(long long);
2019-02-21 02:17:21 +03:00
offsets = (long long*)realloc(offsets, offsets_alloc*sizeof(long long));
}
2019-02-20 01:02:10 +03:00
offsets[offsets_len++] = start;
offsets[offsets_len++] = length;
// calculate diff size
total += length;
2019-02-19 00:49:07 +03:00
}
2019-02-19 01:31:09 +03:00
if (sscanf(buf, " </blocks > %c", &c) == 0 || c != 0)
2019-02-19 00:49:07 +03:00
{
fprintf(stderr, "</blocks> expected, but \"%s\" found\n", buf);
exit(1);
}
2019-02-20 01:02:10 +03:00
if (src >= 0)
{
2023-05-13 11:08:04 +03:00
srcsize = lseek64(src, 0, SEEK_END);
if (srcsize < 0)
{
fprintf(stderr, "Can't determine input size\n");
exit(1);
}
for (i = 0; i < offsets_len; i += 2)
{
if (offsets[i] >= srcsize)
break;
if (offsets[i]+offsets[i+1] > srcsize)
offsets[i+1] = srcsize-offsets[i];
}
offsets_len = i;
2019-02-20 01:02:10 +03:00
long long copied = 0, prev_copied = 0;
if (print_progress)
{
setvbuf(stderr, NULL, _IONBF, 0);
}
for (i = 0; i < offsets_len; i += 2)
{
if (print_progress && (!copied || copied >= prev_copied + 1048576))
{
fprintf(stderr, "\rDone %lld MB / %lld MB, copying next %lld MB...", copied/1048576, total/1048576, offsets[i+1]/1048576);
prev_copied = copied;
}
// write a very simple binary format: SIGNATURE, start, length, data, ...
write(1, SIGNATURE, 8);
write(1, offsets+i, 8);
write(1, offsets+i+1, 8);
copy_blocks(src, 1, offsets[i], offsets[i+1]);
copied += offsets[i+1];
}
fprintf(stderr, "\n");
}
else
{
2019-02-20 01:02:10 +03:00
printf("%lld bytes to copy\n", total);
}
2019-02-19 00:49:07 +03:00
}
2019-02-20 01:02:10 +03:00
void era_copy(char *src_path, int era_block_size, int print_progress)
2019-02-19 00:49:07 +03:00
{
int src = -1;
if (src_path != NULL)
2019-02-19 00:49:07 +03:00
{
2019-02-21 02:28:36 +03:00
// dm-era device may return incorrect data when used without O_DIRECT O_o
src = open(src_path, O_RDONLY|O_LARGEFILE|O_DIRECT);
if (src < 0)
{
fprintf(stderr, "Failed to open %s for reading: %s\n", src_path, strerror(errno));
exit(1);
}
2019-02-19 00:49:07 +03:00
}
2019-02-20 01:02:10 +03:00
read_era_invalidate_and_copy(stdin, src, era_block_size, print_progress);
if (src_path != NULL)
{
close(src);
}
2019-02-19 00:49:07 +03:00
}
int main(int narg, char *args[])
{
2019-02-20 01:02:10 +03:00
int print_progress = 0;
if (narg < 2 || ((print_progress = !strcmp(args[1], "--progress")) && narg < 3))
2019-02-19 00:49:07 +03:00
{
fprintf(stderr,
"era_copy - parses era_invalidate output and saves changed blocks to a stream\n"
2019-02-19 00:49:07 +03:00
"(c) Vitaliy Filippov, 2019+, distributed under the terms of GNU GPLv3.0 or later license\n"
"\n"
2019-02-19 00:49:07 +03:00
"USAGE:\nera_invalidate --metadata-snapshot --written-since <ERA> <META_DEVICE> |\\\n"
2019-02-20 01:02:10 +03:00
" era_copy [--progress] <ERA_BLOCK_SIZE> [<DATA_DEVICE>] > era_copy.bin\n"
"\n"
"When invoked without <DATA_DEVICE>, era_copy only calculates the future diff size and prints it\n"
"\n"
"ERA_BLOCK_SIZE is dm-era's granularity in 512-byte sectors you used when creating dm-era device\n"
"You can take it from `dmsetup table <DATA_DEVICE>`: it's the last number in DM table\n"
2019-02-20 01:02:10 +03:00
"For example, in `0 3625546496 era 259:3 259:2 1024` it's 1024\n"
2019-02-19 00:49:07 +03:00
);
exit(1);
}
2019-02-20 01:02:10 +03:00
int bs = atoi(args[print_progress ? 2 : 1]);
2019-02-19 00:49:07 +03:00
if (bs < 1)
{
fprintf(stderr, "Incorrect era_block_size = %d\n", bs);
2019-02-19 00:49:07 +03:00
exit(1);
}
2019-02-20 01:02:10 +03:00
era_copy(narg == (print_progress ? 3 : 2) ? NULL : args[print_progress ? 3 : 2], bs, print_progress);
2019-02-19 00:49:07 +03:00
return 0;
}