summaryrefslogtreecommitdiff
path: root/src/cow-dedupe-range.c
blob: b96ead1f7dc98342ebf5250d013d328e05cbe8cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#define _GNU_SOURCE  /* for program_invocation_name */
#include <assert.h>  /* for assert(3p) */
#include <errno.h>   /* for errno, program_invocation_name */
#include <error.h>   /* for error(3gnu) */
#include <fcntl.h>   /* for O_RDONLY, O_RDWR */
#include <getopt.h>  /* for getopt_long(3gnu), struct option, optind */
#include <stdbool.h> /* for bool, true, false */
#include <stdint.h>  /* for uint64_t */
#include <stdio.h>   /* for printf(3gnu), fprintf(3p), stderr */
#include <stdlib.h>  /* for strtoll(3p), calloc(3p), free(3p), exit(3p), EXIT_SUCCESS */
#include <string.h>  /* for strlen(3p) */
#include <unistd.h>  /* for dup2(3p) */

#include "dedupe-range.h" /* for dedupe_range, struct range */

#define EXIT_INVALIDARGUMENT 2
#define errusage(format, ...) do { \
	error(0, 0, format, ## __VA_ARGS__); \
	fprintf(stderr, "Try '%s --help' for more information.\n", program_invocation_name); \
	exit(EXIT_INVALIDARGUMENT); \
} while(0)

bool atou64(const char *str, uint64_t *ret) {
	assert(sizeof(uint64_t) <= sizeof(unsigned long long int));
	if (!('0' <= str[0] && str[0] <= '9'))
		return false;
	errno = 0;
	char *end;
	unsigned long long int n = strtoll(str, &end, 10);
	if (end[0] != '\0' || errno != 0)
		return false;
	*ret = (uint64_t)n;
	return true;
}

void usage() {
	printf("Usage: %2$*1$s [OPTIONS] LENGTH SRC_FILENAME SRC_OFFSET \\\n"
	       "       %3$*1$s                  DST_FILENAME DST_OFFSET \\\n"
	       "       %3$*1$s                 [DST_FILENAME DST_OFFSET]...\n"
	       "Submit a file deduplication request to the kernel.\n"
	       "If the file ranges are not duplicates, the kernel will ignore this request.\n"
	       "\n"
	       "OPTIONS:\n"
	       "   -r, --readonly  Open destination files read-only, rather than read/write.\n"
	       "                   Generically, the destination must be open for writing.\n"
	       "                   However, on Btrfs, this allows deduplication to be done\n"
	       "                   on read-only subvolumes when invoked as root.\n"
	       "   -h, --help      Display this help and exit.\n",
	       (int)strlen(program_invocation_name), program_invocation_name, "");
}

int main(int argc, char *argv[]) {
	int ro = 0;
	struct option long_options[] = {
		{"readonly", no_argument, &ro,  1},
		{"help",     no_argument, NULL, 'h'},
		{0}
	};
	int flag;
	while ((flag = getopt_long(argc, argv, "rh", long_options, NULL)) >= 0) {
		switch (flag) {
		case 0:
			break;
		case 'r':
			ro = 1;
			break;
		case 'h':
			usage();
			return EXIT_SUCCESS;
		case '?':
			fprintf(stderr, "Try '%s --help' for more information.\n", program_invocation_name);
			return EXIT_INVALIDARGUMENT;
		default:
			assert(false);
		}
	}
	if (argc - optind < 5)
		errusage("too few arguments");
	if ((argc - optind - 1) % 2 != 0)
		errusage("wrong number of arguments");

	uint64_t src_length;
	if (!atou64(argv[optind], &src_length))
		error(2, errno, "invalid length '%s'", argv[optind]);

	struct filepos src;
	src.filename = argv[optind+1];
	src.flags = O_RDONLY;
	if (!atou64(argv[optind+2], &src.offset))
		error(2, errno, "invalid offset '%s'", argv[optind+2]);
	
	const size_t dst_count = (argc - optind - 3) / 2;
	struct filepos *dsts = calloc(dst_count + 1, sizeof(struct filepos));
	for (size_t i = 0; i < dst_count; i++) {
		dsts[i].filename = argv[optind+3+(i*2)];
		dsts[i].flags = ro ? O_RDONLY : O_RDWR;
		if (!atou64(argv[optind+3+(i*2)+1], &dsts[i].offset))
			error(2, errno, "invalid offset '%s'", argv[optind+3+(i*2)+1]);
	}

	dedupe_range(src_length, src, dsts);
	free(dsts);
	return EXIT_SUCCESS;
}