librsync  2.3.1
rdiff.c
Go to the documentation of this file.
1 /*= -*- c-basic-offset: 4; indent-tabs-mode: nil; -*-
2  *
3  * librsync -- the library for network deltas
4  *
5  * Copyright (C) 1999, 2000, 2001 by Martin Pool <mbp@sourcefrog.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 
22  /*=
23  | .. after a year and a day, mourning is
24  | dangerous to the survivor and troublesome
25  | to the dead.
26  | -- Harold Bloom
27  */
28 
29 /** \file rdiff.c
30  * Command-line network-delta tool.
31  *
32  * \todo Add a -z option to gzip/gunzip patches. This would be somewhat useful,
33  * but more importantly a good test of the streaming API. Also add -I for
34  * bzip2.
35  *
36  * \todo If built with debug support and we have mcheck, then turn it on.
37  * (Optionally?)
38  *
39  * \todo popt doesn't handle single dashes very well at the moment: we'd like
40  * to use them as arguments to indicate stdin/stdout, but it turns them into
41  * options. I sent a patch to the popt maintainers; hopefully it will be fixed
42  * in the future.
43  *
44  * \todo Add an option for delta to check whether the files are identical. */
45 
46 #include "config.h"
47 #include <stdlib.h>
48 #include <stdarg.h>
49 #include <string.h>
50 #include <popt.h>
51 #include "librsync.h"
52 #include "isprefix.h"
53 
54 static int block_len = 0;
55 static int strong_len = 0;
56 
57 static int show_stats = 0;
58 
59 static int bzip2_level = 0;
60 static int gzip_level = 0;
61 static int file_force = 0;
62 
63 enum {
64  OPT_GZIP = 1069, OPT_BZIP2
65 };
66 
67 char *rs_hash_name;
68 char *rs_rollsum_name;
69 
70 static void rdiff_usage(const char *error, ...)
71 {
72  va_list va;
73  char buf[256];
74 
75  va_start(va, error);
76  vsnprintf(buf, sizeof(buf), error, va);
77  va_end(va);
78  fprintf(stderr, "rdiff: %s\n\nTry `rdiff --help' for more information.\n",
79  buf);
80 }
81 
82 static void rdiff_no_more_args(poptContext opcon)
83 {
84  if (poptGetArg(opcon)) {
85  rdiff_usage("Too many arguments.");
86  exit(RS_SYNTAX_ERROR);
87  }
88 }
89 
90 static void bad_option(poptContext opcon, int error)
91 {
92  rdiff_usage("%s: %s", poptStrerror(error), poptBadOption(opcon, 0));
93  exit(RS_SYNTAX_ERROR);
94 }
95 
96 static void help(void)
97 {
98  printf("Usage: rdiff [OPTIONS] signature [BASIS [SIGNATURE]]\n"
99  " [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]]\n"
100  " [OPTIONS] patch BASIS [DELTA [NEWFILE]]\n" "\n"
101  "Options:\n"
102  " -v, --verbose Trace internal processing\n"
103  " -V, --version Show program version\n"
104  " -?, --help Show this help message\n"
105  " -s, --statistics Show performance statistics\n"
106  " -f, --force Force overwriting existing files\n"
107  "Signature generation options:\n"
108  " -H, --hash=ALG Hash algorithm: blake2 (default), md4\n"
109  " -R, --rollsum=ALG Rollsum algorithm: rabinkarp (default), rollsum\n"
110  "Delta-encoding options:\n"
111  " -b, --block-size=BYTES Signature block size, 0 (default) for recommended\n"
112  " -S, --sum-size=BYTES Signature strength, 0 (default) for max, -1 for min\n"
113  "IO options:\n" " -I, --input-size=BYTES Input buffer size\n"
114  " -O, --output-size=BYTES Output buffer size\n"
115  " -z, --gzip[=LEVEL] gzip-compress deltas\n"
116  " -i, --bzip2[=LEVEL] bzip2-compress deltas\n");
117 }
118 
119 static void rdiff_show_version(void)
120 {
121  char const *bzlib = "", *zlib = "", *trace = "";
122 
123 #if 0
124  /* Compression isn't implemented so don't mention it. */
125 # ifdef HAVE_LIBZ
126  zlib = ", gzip";
127 # endif
128 
129 # ifdef HAVE_LIBBZ2
130  bzlib = ", bzip2";
131 # endif
132 #endif
133 
134 #ifndef DO_RS_TRACE
135  trace = ", trace disabled";
136 #endif
137 
138  printf("rdiff (%s)\n"
139  "Copyright (C) 1997-2016 by Martin Pool, Andrew Tridgell and others.\n"
140  "http://librsync.sourcefrog.net/\n"
141  "Capabilities: %ld bit files%s%s%s\n" "\n"
142  "librsync comes with NO WARRANTY, to the extent permitted by law.\n"
143  "You may redistribute copies of librsync under the terms of the GNU\n"
144  "Lesser General Public License. For more information about these\n"
145  "matters, see the files named COPYING.\n", rs_librsync_version,
146  (long)(8 * sizeof(rs_long_t)), zlib, bzlib, trace);
147 }
148 
149 static void rdiff_options(poptContext opcon)
150 {
151  int c;
152  char const *a;
153 
154  while ((c = poptGetNextOpt(opcon)) != -1) {
155  switch (c) {
156  case 'h':
157  help();
158  exit(RS_DONE);
159  case 'V':
160  rdiff_show_version();
161  exit(RS_DONE);
162  case 'v':
163  if (!rs_supports_trace()) {
164  fprintf(stderr, "rdiff: Library does not support trace.\n");
165  }
167  break;
168 
169  case OPT_GZIP:
170  case OPT_BZIP2:
171  if ((a = poptGetOptArg(opcon))) {
172  int l = atoi(a);
173  if (c == OPT_GZIP)
174  gzip_level = l;
175  else
176  bzip2_level = l;
177  } else {
178  if (c == OPT_GZIP)
179  gzip_level = -1; /* library default */
180  else
181  bzip2_level = 9; /* demand the best */
182  }
183  rdiff_usage("Sorry, compression is not implemented yet.");
184  exit(RS_UNIMPLEMENTED);
185 
186  default:
187  bad_option(opcon, c);
188  }
189  }
190 }
191 
192 /** Generate signature from remaining command line arguments. */
193 static rs_result rdiff_sig(poptContext opcon)
194 {
195  FILE *basis_file, *sig_file;
196  rs_stats_t stats;
197  rs_result result;
198  rs_long_t sig_magic;
199 
200  basis_file = rs_file_open(poptGetArg(opcon), "rb", file_force);
201  sig_file = rs_file_open(poptGetArg(opcon), "wb", file_force);
202 
203  rdiff_no_more_args(opcon);
204 
205  if (!rs_hash_name || !strcmp(rs_hash_name, "blake2")) {
206  sig_magic = RS_BLAKE2_SIG_MAGIC;
207  } else if (!strcmp(rs_hash_name, "md4")) {
208  sig_magic = RS_MD4_SIG_MAGIC;
209  } else {
210  rdiff_usage("Unknown hash algorithm '%s'.", rs_hash_name);
211  exit(RS_SYNTAX_ERROR);
212  }
213  if (!rs_rollsum_name || !strcmp(rs_rollsum_name, "rabinkarp")) {
214  /* The RabinKarp magics are 0x10 greater than the rollsum magics. */
215  sig_magic += 0x10;
216  } else if (strcmp(rs_rollsum_name, "rollsum")) {
217  rdiff_usage("Unknown rollsum algorithm '%s'.", rs_rollsum_name);
218  exit(RS_SYNTAX_ERROR);
219  }
220 
221  result =
222  rs_sig_file(basis_file, sig_file, block_len, strong_len, sig_magic,
223  &stats);
224 
225  rs_file_close(sig_file);
226  rs_file_close(basis_file);
227  if (result != RS_DONE)
228  return result;
229 
230  if (show_stats)
231  rs_log_stats(&stats);
232 
233  return result;
234 }
235 
236 static rs_result rdiff_delta(poptContext opcon)
237 {
238  FILE *sig_file, *new_file, *delta_file;
239  char const *sig_name;
240  rs_result result;
241  rs_signature_t *sumset;
242  rs_stats_t stats;
243 
244  if (!(sig_name = poptGetArg(opcon))) {
245  rdiff_usage("Usage for delta: "
246  "rdiff [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]]");
247  exit(RS_SYNTAX_ERROR);
248  }
249 
250  sig_file = rs_file_open(sig_name, "rb", file_force);
251  new_file = rs_file_open(poptGetArg(opcon), "rb", file_force);
252  delta_file = rs_file_open(poptGetArg(opcon), "wb", file_force);
253 
254  rdiff_no_more_args(opcon);
255 
256  result = rs_loadsig_file(sig_file, &sumset, &stats);
257  if (result != RS_DONE)
258  return result;
259 
260  if (show_stats)
261  rs_log_stats(&stats);
262 
263  if ((result = rs_build_hash_table(sumset)) != RS_DONE)
264  return result;
265 
266  result = rs_delta_file(sumset, new_file, delta_file, &stats);
267 
268  rs_file_close(delta_file);
269  rs_file_close(new_file);
270  rs_file_close(sig_file);
271 
272  if (show_stats) {
273  rs_signature_log_stats(sumset);
274  rs_log_stats(&stats);
275  }
276 
277  rs_free_sumset(sumset);
278 
279  return result;
280 }
281 
282 static rs_result rdiff_patch(poptContext opcon)
283 {
284  /* patch BASIS [DELTA [NEWFILE]] */
285  FILE *basis_file, *delta_file, *new_file;
286  char const *basis_name;
287  rs_stats_t stats;
288  rs_result result;
289 
290  if (!(basis_name = poptGetArg(opcon))) {
291  rdiff_usage("Usage for patch: "
292  "rdiff [OPTIONS] patch BASIS [DELTA [NEW]]");
293  exit(RS_SYNTAX_ERROR);
294  }
295 
296  basis_file = rs_file_open(basis_name, "rb", file_force);
297  delta_file = rs_file_open(poptGetArg(opcon), "rb", file_force);
298  new_file = rs_file_open(poptGetArg(opcon), "wb", file_force);
299 
300  rdiff_no_more_args(opcon);
301 
302  result = rs_patch_file(basis_file, delta_file, new_file, &stats);
303 
304  rs_file_close(new_file);
305  rs_file_close(delta_file);
306  rs_file_close(basis_file);
307 
308  if (show_stats)
309  rs_log_stats(&stats);
310 
311  return result;
312 }
313 
314 static rs_result rdiff_action(poptContext opcon)
315 {
316  const char *action;
317 
318  action = poptGetArg(opcon);
319  if (!action) ;
320  else if (isprefix(action, "signature"))
321  return rdiff_sig(opcon);
322  else if (isprefix(action, "delta"))
323  return rdiff_delta(opcon);
324  else if (isprefix(action, "patch"))
325  return rdiff_patch(opcon);
326 
327  rdiff_usage
328  ("You must specify an action: `signature', `delta', or `patch'.");
329  exit(RS_SYNTAX_ERROR);
330 }
331 
332 int main(const int argc, const char *argv[])
333 {
334  /* Initialize opts at runtime to avoid unknown address values. */
335  const struct poptOption opts[] = {
336  {"verbose", 'v', POPT_ARG_NONE, 0, 'v'},
337  {"version", 'V', POPT_ARG_NONE, 0, 'V'},
338  {"input-size", 'I', POPT_ARG_INT, &rs_inbuflen},
339  {"output-size", 'O', POPT_ARG_INT, &rs_outbuflen},
340  {"hash", 'H', POPT_ARG_STRING, &rs_hash_name},
341  {"rollsum", 'R', POPT_ARG_STRING, &rs_rollsum_name},
342  {"help", '?', POPT_ARG_NONE, 0, 'h'},
343  {0, 'h', POPT_ARG_NONE, 0, 'h'},
344  {"block-size", 'b', POPT_ARG_INT, &block_len},
345  {"sum-size", 'S', POPT_ARG_INT, &strong_len},
346  {"statistics", 's', POPT_ARG_NONE, &show_stats},
347  {"stats", 0, POPT_ARG_NONE, &show_stats},
348  {"gzip", 'z', POPT_ARG_NONE, 0, OPT_GZIP},
349  {"bzip2", 'i', POPT_ARG_NONE, 0, OPT_BZIP2},
350  {"force", 'f', POPT_ARG_NONE, &file_force},
351  {0}
352  };
353 
354  poptContext opcon;
355  rs_result result;
356 
357  opcon = poptGetContext("rdiff", argc, argv, opts, 0);
358  rdiff_options(opcon);
359  result = rdiff_action(opcon);
360 
361  if (result != RS_DONE)
362  fprintf(stderr, "rdiff: Failed, %s.\n", rs_strerror(result));
363 
364  poptFreeContext(opcon);
365  return result;
366 }
Command line syntax error.
Definition: librsync.h:188
LIBRSYNC_EXPORT rs_result rs_delta_file(rs_signature_t *, FILE *new_file, FILE *delta_file, rs_stats_t *)
Generate a delta between a signature and a new file into a delta file.
Definition: whole.c:124
A signature file using the BLAKE2 hash.
Definition: librsync.h:89
LIBRSYNC_EXPORT int rs_log_stats(rs_stats_t const *stats)
Write statistics into the current log as text.
Definition: stats.c:31
LIBRSYNC_EXPORT rs_result rs_patch_file(FILE *basis_file, FILE *delta_file, FILE *new_file, rs_stats_t *)
Apply a patch, relative to a basis, into a new file.
Definition: whole.c:140
LIBRSYNC_EXPORT int rs_inbuflen
Buffer sizes for file IO.
Definition: whole.c:42
LIBRSYNC_EXPORT char const * rs_strerror(rs_result r)
Return an English description of a rs_result value.
Definition: msg.c:46
A signature file with MD4 signatures.
Definition: librsync.h:82
Public header for librsync.
LIBRSYNC_EXPORT void rs_trace_set_level(rs_loglevel level)
Set the least important message severity that will be output.
Definition: trace.c:70
LIBRSYNC_EXPORT int rs_file_close(FILE *file)
Close a file with special handling for stdin or stdout.
Definition: fileutil.c:115
Signature of a whole file.
Definition: sumset.h:37
Author is lazy.
Definition: librsync.h:197
LIBRSYNC_EXPORT void rs_free_sumset(rs_signature_t *)
Deep deallocation of checksums.
Definition: sumset.c:298
LIBRSYNC_EXPORT rs_result rs_build_hash_table(rs_signature_t *sums)
Call this after loading a signature to index it.
Definition: sumset.c:278
Performance statistics from a librsync encoding or decoding operation.
Definition: librsync.h:210
rs_result
Return codes from nonblocking rsync operations.
Definition: librsync.h:180
LIBRSYNC_EXPORT int rs_supports_trace(void)
Check whether the library was compiled with debugging trace.
Definition: trace.c:110
LIBRSYNC_EXPORT rs_result rs_loadsig_file(FILE *sig_file, rs_signature_t **sumset, rs_stats_t *stats)
Load signatures from a signature file into memory.
Definition: whole.c:106
static rs_result rdiff_sig(poptContext opcon)
Generate signature from remaining command line arguments.
Definition: rdiff.c:193
LIBRSYNC_EXPORT FILE * rs_file_open(char const *filename, char const *mode, int force)
Open a file with special handling for stdin or stdout.
Definition: fileutil.c:76
LIBRSYNC_EXPORT rs_result rs_sig_file(FILE *old_file, FILE *sig_file, size_t block_len, size_t strong_len, rs_magic_number sig_magic, rs_stats_t *stats)
Generate the signature of a basis file, and write it out to another.
Definition: whole.c:83
LIBRSYNC_EXPORT void rs_signature_log_stats(rs_signature_t const *sig)
Log the rs_signature_delta match stats.
Definition: sumset.c:261
Completed successfully.
Definition: librsync.h:181
LIBRSYNC_EXPORT char const rs_librsync_version[]
Library version string.
Definition: version.c:25
Debug-level messages.
Definition: librsync.h:126