#define _GNU_SOURCE /* for program_invocation_name */ #include /* for assert(3p) */ #include /* for errno, program_invocation_name */ #include /* for error(3gnu) */ #include /* for O_RDONLY, O_RDWR */ #include /* for getopt_long(3gnu), struct option, optind */ #include /* for bool, true, false */ #include /* for uint64_t */ #include /* for printf(3gnu), fprintf(3p), stderr */ #include /* for strtoll(3p), calloc(3p), free(3p), exit(3p), EXIT_SUCCESS */ #include /* for strlen(3p) */ #include /* 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] SRC_FILENAME SRC_OFFSET SRC_LENGTH \\\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 - 3) % 2 != 0) errusage("wrong number of arguments"); struct range src; src.filename = argv[optind]; src.flags = O_RDONLY; if (!atou64(argv[optind+1], &src.offset)) error(2, errno, "invalid offset '%s'", argv[optind+1]); if (!atou64(argv[optind+2], &src.length)) error(2, errno, "invalid length '%s'", argv[optind+2]); const size_t dst_count = (argc - optind - 3) / 2; struct range *dsts = calloc(dst_count + 1, sizeof(struct range)); 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, dsts); free(dsts); return EXIT_SUCCESS; }