librsync  2.3.2
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
54static int block_len = 0;
55static int strong_len = 0;
56
57static int show_stats = 0;
58
59static int bzip2_level = 0;
60static int gzip_level = 0;
61static int file_force = 0;
62
63enum {
64 OPT_GZIP = 1069, OPT_BZIP2
65};
66
67char *rs_hash_name;
68char *rs_rollsum_name;
69
70static 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
82static 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
90static 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
96static 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
119static 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
149static 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. */
193static rs_result rdiff_sig(poptContext opcon)
194{
195 FILE *basis_file, *sig_file;
196 rs_stats_t stats;
197 rs_result result;
198 rs_magic_number 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
236static 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) {
274 rs_log_stats(&stats);
275 }
276
277 rs_free_sumset(sumset);
278
279 return result;
280}
281
282static 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
314static 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
332int 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}
Public header for librsync.
LIBRSYNC_EXPORT rs_result rs_build_hash_table(rs_signature_t *sums)
Call this after loading a signature to index it.
Definition: sumset.c:275
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:257
LIBRSYNC_EXPORT int rs_supports_trace(void)
Check whether the library was compiled with debugging trace.
Definition: trace.c:110
LIBRSYNC_EXPORT void rs_free_sumset(rs_signature_t *)
Deep deallocation of checksums.
Definition: sumset.c:295
LIBRSYNC_EXPORT char const * rs_strerror(rs_result r)
Return an English description of a rs_result value.
Definition: msg.c:46
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
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
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
rs_result
Return codes from nonblocking rsync operations.
Definition: librsync.h:180
@ RS_UNIMPLEMENTED
Author is lazy.
Definition: librsync.h:197
@ RS_DONE
Completed successfully.
Definition: librsync.h:181
@ RS_SYNTAX_ERROR
Command line syntax error.
Definition: librsync.h:188
@ RS_LOG_DEBUG
Debug-level messages.
Definition: librsync.h:126
rs_magic_number
A uint32 magic number, emitted in bigendian/network order at the start of librsync files.
Definition: librsync.h:65
@ RS_BLAKE2_SIG_MAGIC
A signature file using the BLAKE2 hash.
Definition: librsync.h:89
@ RS_MD4_SIG_MAGIC
A signature file with MD4 signatures.
Definition: librsync.h:82
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 int rs_inbuflen
Buffer sizes for file IO.
Definition: whole.c:42
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 char const rs_librsync_version[]
Library version string.
Definition: version.c:25
static rs_result rdiff_sig(poptContext opcon)
Generate signature from remaining command line arguments.
Definition: rdiff.c:193
Signature of a whole file.
Definition: sumset.h:37
Performance statistics from a librsync encoding or decoding operation.
Definition: librsync.h:210