| /*= -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- |
| * |
| * librsync -- the library for network deltas |
| * |
| * Copyright (C) 1999, 2000, 2001 by Martin Pool <mbp@sourcefrog.net> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation; either version 2.1 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| /*= |
| | .. after a year and a day, mourning is |
| | dangerous to the survivor and troublesome |
| | to the dead. |
| | -- Harold Bloom |
| */ |
| |
| /** \file rdiff.c |
| * Command-line network-delta tool. |
| * |
| * \todo Add a -z option to gzip/gunzip patches. This would be somewhat useful, |
| * but more importantly a good test of the streaming API. Also add -I for |
| * bzip2. |
| * |
| * \todo If built with debug support and we have mcheck, then turn it on. |
| * (Optionally?) |
| * |
| * \todo popt doesn't handle single dashes very well at the moment: we'd like |
| * to use them as arguments to indicate stdin/stdout, but it turns them into |
| * options. I sent a patch to the popt maintainers; hopefully it will be fixed |
| * in the future. |
| * |
| * \todo Add an option for delta to check whether the files are identical. */ |
| |
| #include "config.h" |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <popt.h> |
| #include "librsync.h" |
| #include "isprefix.h" |
| |
| static int block_len = 0; |
| static int strong_len = 0; |
| |
| static int show_stats = 0; |
| |
| static int bzip2_level = 0; |
| static int gzip_level = 0; |
| static int file_force = 0; |
| |
| enum { |
| OPT_GZIP = 1069, OPT_BZIP2 |
| }; |
| |
| char *rs_hash_name; |
| char *rs_rollsum_name; |
| |
| static void rdiff_usage(const char *error, ...) |
| { |
| va_list va; |
| char buf[256]; |
| |
| va_start(va, error); |
| vsnprintf(buf, sizeof(buf), error, va); |
| va_end(va); |
| fprintf(stderr, "rdiff: %s\n\nTry `rdiff --help' for more information.\n", |
| buf); |
| } |
| |
| static void rdiff_no_more_args(poptContext opcon) |
| { |
| if (poptGetArg(opcon)) { |
| rdiff_usage("Too many arguments."); |
| exit(RS_SYNTAX_ERROR); |
| } |
| } |
| |
| static void bad_option(poptContext opcon, int error) |
| { |
| rdiff_usage("%s: %s", poptStrerror(error), poptBadOption(opcon, 0)); |
| exit(RS_SYNTAX_ERROR); |
| } |
| |
| static void help(void) |
| { |
| printf("Usage: rdiff [OPTIONS] signature [BASIS [SIGNATURE]]\n" |
| " [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]]\n" |
| " [OPTIONS] patch BASIS [DELTA [NEWFILE]]\n" "\n" |
| "Options:\n" |
| " -v, --verbose Trace internal processing\n" |
| " -V, --version Show program version\n" |
| " -?, --help Show this help message\n" |
| " -s, --statistics Show performance statistics\n" |
| " -f, --force Force overwriting existing files\n" |
| "Signature generation options:\n" |
| " -H, --hash=ALG Hash algorithm: blake2 (default), md4\n" |
| " -R, --rollsum=ALG Rollsum algorithm: rabinkarp (default), rollsum\n" |
| "Delta-encoding options:\n" |
| " -b, --block-size=BYTES Signature block size, 0 (default) for recommended\n" |
| " -S, --sum-size=BYTES Signature strength, 0 (default) for max, -1 for min\n" |
| "IO options:\n" " -I, --input-size=BYTES Input buffer size\n" |
| " -O, --output-size=BYTES Output buffer size\n" |
| " -z, --gzip[=LEVEL] gzip-compress deltas\n" |
| " -i, --bzip2[=LEVEL] bzip2-compress deltas\n"); |
| } |
| |
| static void rdiff_show_version(void) |
| { |
| char const *bzlib = "", *zlib = "", *trace = ""; |
| |
| #if 0 |
| /* Compression isn't implemented so don't mention it. */ |
| # ifdef HAVE_LIBZ |
| zlib = ", gzip"; |
| # endif |
| |
| # ifdef HAVE_LIBBZ2 |
| bzlib = ", bzip2"; |
| # endif |
| #endif |
| |
| #ifndef DO_RS_TRACE |
| trace = ", trace disabled"; |
| #endif |
| |
| printf("rdiff (%s)\n" |
| "Copyright (C) 1997-2016 by Martin Pool, Andrew Tridgell and others.\n" |
| "http://librsync.sourcefrog.net/\n" |
| "Capabilities: %ld bit files%s%s%s\n" "\n" |
| "librsync comes with NO WARRANTY, to the extent permitted by law.\n" |
| "You may redistribute copies of librsync under the terms of the GNU\n" |
| "Lesser General Public License. For more information about these\n" |
| "matters, see the files named COPYING.\n", rs_librsync_version, |
| (long)(8 * sizeof(rs_long_t)), zlib, bzlib, trace); |
| } |
| |
| static void rdiff_options(poptContext opcon) |
| { |
| int c; |
| char const *a; |
| |
| while ((c = poptGetNextOpt(opcon)) != -1) { |
| switch (c) { |
| case 'h': |
| help(); |
| exit(RS_DONE); |
| case 'V': |
| rdiff_show_version(); |
| exit(RS_DONE); |
| case 'v': |
| if (!rs_supports_trace()) { |
| fprintf(stderr, "rdiff: Library does not support trace.\n"); |
| } |
| rs_trace_set_level(RS_LOG_DEBUG); |
| break; |
| |
| case OPT_GZIP: |
| case OPT_BZIP2: |
| if ((a = poptGetOptArg(opcon))) { |
| int l = atoi(a); |
| if (c == OPT_GZIP) |
| gzip_level = l; |
| else |
| bzip2_level = l; |
| } else { |
| if (c == OPT_GZIP) |
| gzip_level = -1; /* library default */ |
| else |
| bzip2_level = 9; /* demand the best */ |
| } |
| rdiff_usage("Sorry, compression is not implemented yet."); |
| exit(RS_UNIMPLEMENTED); |
| |
| default: |
| bad_option(opcon, c); |
| } |
| } |
| } |
| |
| /** Generate signature from remaining command line arguments. */ |
| static rs_result rdiff_sig(poptContext opcon) |
| { |
| FILE *basis_file, *sig_file; |
| rs_stats_t stats; |
| rs_result result; |
| rs_magic_number sig_magic; |
| |
| basis_file = rs_file_open(poptGetArg(opcon), "rb", file_force); |
| sig_file = rs_file_open(poptGetArg(opcon), "wb", file_force); |
| |
| rdiff_no_more_args(opcon); |
| |
| if (!rs_hash_name || !strcmp(rs_hash_name, "blake2")) { |
| sig_magic = RS_BLAKE2_SIG_MAGIC; |
| } else if (!strcmp(rs_hash_name, "md4")) { |
| sig_magic = RS_MD4_SIG_MAGIC; |
| } else { |
| rdiff_usage("Unknown hash algorithm '%s'.", rs_hash_name); |
| exit(RS_SYNTAX_ERROR); |
| } |
| if (!rs_rollsum_name || !strcmp(rs_rollsum_name, "rabinkarp")) { |
| /* The RabinKarp magics are 0x10 greater than the rollsum magics. */ |
| sig_magic += 0x10; |
| } else if (strcmp(rs_rollsum_name, "rollsum")) { |
| rdiff_usage("Unknown rollsum algorithm '%s'.", rs_rollsum_name); |
| exit(RS_SYNTAX_ERROR); |
| } |
| |
| result = |
| rs_sig_file(basis_file, sig_file, block_len, strong_len, sig_magic, |
| &stats); |
| |
| rs_file_close(sig_file); |
| rs_file_close(basis_file); |
| if (result != RS_DONE) |
| return result; |
| |
| if (show_stats) |
| rs_log_stats(&stats); |
| |
| return result; |
| } |
| |
| static rs_result rdiff_delta(poptContext opcon) |
| { |
| FILE *sig_file, *new_file, *delta_file; |
| char const *sig_name; |
| rs_result result; |
| rs_signature_t *sumset; |
| rs_stats_t stats; |
| |
| if (!(sig_name = poptGetArg(opcon))) { |
| rdiff_usage("Usage for delta: " |
| "rdiff [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]]"); |
| exit(RS_SYNTAX_ERROR); |
| } |
| |
| sig_file = rs_file_open(sig_name, "rb", file_force); |
| new_file = rs_file_open(poptGetArg(opcon), "rb", file_force); |
| delta_file = rs_file_open(poptGetArg(opcon), "wb", file_force); |
| |
| rdiff_no_more_args(opcon); |
| |
| result = rs_loadsig_file(sig_file, &sumset, &stats); |
| if (result != RS_DONE) |
| return result; |
| |
| if (show_stats) |
| rs_log_stats(&stats); |
| |
| if ((result = rs_build_hash_table(sumset)) != RS_DONE) |
| return result; |
| |
| result = rs_delta_file(sumset, new_file, delta_file, &stats); |
| |
| rs_file_close(delta_file); |
| rs_file_close(new_file); |
| rs_file_close(sig_file); |
| |
| if (show_stats) { |
| rs_signature_log_stats(sumset); |
| rs_log_stats(&stats); |
| } |
| |
| rs_free_sumset(sumset); |
| |
| return result; |
| } |
| |
| static rs_result rdiff_patch(poptContext opcon) |
| { |
| /* patch BASIS [DELTA [NEWFILE]] */ |
| FILE *basis_file, *delta_file, *new_file; |
| char const *basis_name; |
| rs_stats_t stats; |
| rs_result result; |
| |
| if (!(basis_name = poptGetArg(opcon))) { |
| rdiff_usage("Usage for patch: " |
| "rdiff [OPTIONS] patch BASIS [DELTA [NEW]]"); |
| exit(RS_SYNTAX_ERROR); |
| } |
| |
| basis_file = rs_file_open(basis_name, "rb", file_force); |
| delta_file = rs_file_open(poptGetArg(opcon), "rb", file_force); |
| new_file = rs_file_open(poptGetArg(opcon), "wb", file_force); |
| |
| rdiff_no_more_args(opcon); |
| |
| result = rs_patch_file(basis_file, delta_file, new_file, &stats); |
| |
| rs_file_close(new_file); |
| rs_file_close(delta_file); |
| rs_file_close(basis_file); |
| |
| if (show_stats) |
| rs_log_stats(&stats); |
| |
| return result; |
| } |
| |
| static rs_result rdiff_action(poptContext opcon) |
| { |
| const char *action; |
| |
| action = poptGetArg(opcon); |
| if (!action) ; |
| else if (isprefix(action, "signature")) |
| return rdiff_sig(opcon); |
| else if (isprefix(action, "delta")) |
| return rdiff_delta(opcon); |
| else if (isprefix(action, "patch")) |
| return rdiff_patch(opcon); |
| |
| rdiff_usage |
| ("You must specify an action: `signature', `delta', or `patch'."); |
| exit(RS_SYNTAX_ERROR); |
| } |
| |
| int main(const int argc, const char *argv[]) |
| { |
| /* Initialize opts at runtime to avoid unknown address values. */ |
| const struct poptOption opts[] = { |
| {"verbose", 'v', POPT_ARG_NONE, 0, 'v'}, |
| {"version", 'V', POPT_ARG_NONE, 0, 'V'}, |
| {"input-size", 'I', POPT_ARG_INT, &rs_inbuflen}, |
| {"output-size", 'O', POPT_ARG_INT, &rs_outbuflen}, |
| {"hash", 'H', POPT_ARG_STRING, &rs_hash_name}, |
| {"rollsum", 'R', POPT_ARG_STRING, &rs_rollsum_name}, |
| {"help", '?', POPT_ARG_NONE, 0, 'h'}, |
| {0, 'h', POPT_ARG_NONE, 0, 'h'}, |
| {"block-size", 'b', POPT_ARG_INT, &block_len}, |
| {"sum-size", 'S', POPT_ARG_INT, &strong_len}, |
| {"statistics", 's', POPT_ARG_NONE, &show_stats}, |
| {"stats", 0, POPT_ARG_NONE, &show_stats}, |
| {"gzip", 'z', POPT_ARG_NONE, 0, OPT_GZIP}, |
| {"bzip2", 'i', POPT_ARG_NONE, 0, OPT_BZIP2}, |
| {"force", 'f', POPT_ARG_NONE, &file_force}, |
| {0} |
| }; |
| |
| poptContext opcon; |
| rs_result result; |
| |
| opcon = poptGetContext("rdiff", argc, argv, opts, 0); |
| rdiff_options(opcon); |
| result = rdiff_action(opcon); |
| |
| if (result != RS_DONE) |
| fprintf(stderr, "rdiff: Failed, %s.\n", rs_strerror(result)); |
| |
| poptFreeContext(opcon); |
| return result; |
| } |