librsync  2.3.4
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" /* IWYU pragma: keep */
47#include <stdlib.h>
48#include <stdarg.h>
49#include <string.h>
50#include <popt.h>
51#include <stdio.h>
52#include "librsync.h"
53#include "isprefix.h"
54
55static int block_len = 0;
56static int strong_len = 0;
57
58static int show_stats = 0;
59
60static int bzip2_level = 0;
61static int gzip_level = 0;
62static int file_force = 0;
63
64enum {
65 OPT_GZIP = 1069, OPT_BZIP2
66};
67
68char *rs_hash_name;
69char *rs_rollsum_name;
70
71static void rdiff_usage(const char *error, ...)
72{
73 va_list va;
74 char buf[256];
75
76 va_start(va, error);
77 vsnprintf(buf, sizeof(buf), error, va);
78 va_end(va);
79 fprintf(stderr, "rdiff: %s\n\nTry `rdiff --help' for more information.\n",
80 buf);
81}
82
83static void rdiff_no_more_args(poptContext opcon)
84{
85 if (poptGetArg(opcon)) {
86 rdiff_usage("Too many arguments.");
87 exit(RS_SYNTAX_ERROR);
88 }
89}
90
91static void bad_option(poptContext opcon, int error)
92{
93 rdiff_usage("%s: %s", poptStrerror(error), poptBadOption(opcon, 0));
94 exit(RS_SYNTAX_ERROR);
95}
96
97static void help(void)
98{
99 printf("Usage: rdiff [OPTIONS] signature [BASIS [SIGNATURE]]\n"
100 " [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]]\n"
101 " [OPTIONS] patch BASIS [DELTA [NEWFILE]]\n" "\n"
102 "Options:\n"
103 " -v, --verbose Trace internal processing\n"
104 " -V, --version Show program version\n"
105 " -?, --help Show this help message\n"
106 " -s, --statistics Show performance statistics\n"
107 " -f, --force Force overwriting existing files\n"
108 "Signature generation options:\n"
109 " -H, --hash=ALG Hash algorithm: blake2 (default), md4\n"
110 " -R, --rollsum=ALG Rollsum algorithm: rabinkarp (default), rollsum\n"
111 "Delta-encoding options:\n"
112 " -b, --block-size=BYTES Signature block size, 0 (default) for recommended\n"
113 " -S, --sum-size=BYTES Signature strength, 0 (default) for max, -1 for min\n"
114 "IO options:\n" " -I, --input-size=BYTES Input buffer size\n"
115 " -O, --output-size=BYTES Output buffer size\n"
116 " -z, --gzip[=LEVEL] gzip-compress deltas\n"
117 " -i, --bzip2[=LEVEL] bzip2-compress deltas\n");
118}
119
120static void rdiff_show_version(void)
121{
122 char const *bzlib = "", *zlib = "", *trace = "";
123
124#if 0
125 /* Compression isn't implemented so don't mention it. */
126# ifdef HAVE_LIBZ
127 zlib = ", gzip";
128# endif
129
130# ifdef HAVE_LIBBZ2
131 bzlib = ", bzip2";
132# endif
133#endif
134
135#ifndef DO_RS_TRACE
136 trace = ", trace disabled";
137#endif
138
139 printf("rdiff (%s)\n"
140 "Copyright (C) 1997-2016 by Martin Pool, Andrew Tridgell and others.\n"
141 "http://librsync.sourcefrog.net/\n"
142 "Capabilities: %ld bit files%s%s%s\n" "\n"
143 "librsync comes with NO WARRANTY, to the extent permitted by law.\n"
144 "You may redistribute copies of librsync under the terms of the GNU\n"
145 "Lesser General Public License. For more information about these\n"
146 "matters, see the files named COPYING.\n", rs_librsync_version,
147 (long)(8 * sizeof(rs_long_t)), zlib, bzlib, trace);
148}
149
150static void rdiff_options(poptContext opcon)
151{
152 int c;
153 char const *a;
154
155 while ((c = poptGetNextOpt(opcon)) != -1) {
156 switch (c) {
157 case 'h':
158 help();
159 exit(RS_DONE);
160 case 'V':
161 rdiff_show_version();
162 exit(RS_DONE);
163 case 'v':
164 if (!rs_supports_trace()) {
165 fprintf(stderr, "rdiff: Library does not support trace.\n");
166 }
168 break;
169
170 case OPT_GZIP:
171 case OPT_BZIP2:
172 if ((a = poptGetOptArg(opcon))) {
173 int l = atoi(a);
174 if (c == OPT_GZIP)
175 gzip_level = l;
176 else
177 bzip2_level = l;
178 } else {
179 if (c == OPT_GZIP)
180 gzip_level = -1; /* library default */
181 else
182 bzip2_level = 9; /* demand the best */
183 }
184 rdiff_usage("Sorry, compression is not implemented yet.");
185 exit(RS_UNIMPLEMENTED);
186
187 default:
188 bad_option(opcon, c);
189 }
190 }
191}
192
193/** Generate signature from remaining command line arguments. */
194static rs_result rdiff_sig(poptContext opcon)
195{
196 FILE *basis_file, *sig_file;
197 rs_stats_t stats;
198 rs_result result;
199 rs_magic_number sig_magic;
200
201 basis_file = rs_file_open(poptGetArg(opcon), "rb", file_force);
202 sig_file = rs_file_open(poptGetArg(opcon), "wb", file_force);
203
204 rdiff_no_more_args(opcon);
205
206 if (!rs_hash_name || !strcmp(rs_hash_name, "blake2")) {
207 sig_magic = RS_BLAKE2_SIG_MAGIC;
208 } else if (!strcmp(rs_hash_name, "md4")) {
209 sig_magic = RS_MD4_SIG_MAGIC;
210 } else {
211 rdiff_usage("Unknown hash algorithm '%s'.", rs_hash_name);
212 exit(RS_SYNTAX_ERROR);
213 }
214 if (!rs_rollsum_name || !strcmp(rs_rollsum_name, "rabinkarp")) {
215 /* The RabinKarp magics are 0x10 greater than the rollsum magics. */
216 sig_magic += 0x10;
217 } else if (strcmp(rs_rollsum_name, "rollsum")) {
218 rdiff_usage("Unknown rollsum algorithm '%s'.", rs_rollsum_name);
219 exit(RS_SYNTAX_ERROR);
220 }
221
222 result =
223 rs_sig_file(basis_file, sig_file, block_len, strong_len, sig_magic,
224 &stats);
225
226 rs_file_close(sig_file);
227 rs_file_close(basis_file);
228 if (result != RS_DONE)
229 return result;
230
231 if (show_stats)
232 rs_log_stats(&stats);
233
234 return result;
235}
236
237static rs_result rdiff_delta(poptContext opcon)
238{
239 FILE *sig_file, *new_file, *delta_file;
240 char const *sig_name;
241 rs_result result;
242 rs_signature_t *sumset;
243 rs_stats_t stats;
244
245 if (!(sig_name = poptGetArg(opcon))) {
246 rdiff_usage("Usage for delta: "
247 "rdiff [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]]");
248 exit(RS_SYNTAX_ERROR);
249 }
250
251 sig_file = rs_file_open(sig_name, "rb", file_force);
252 new_file = rs_file_open(poptGetArg(opcon), "rb", file_force);
253 delta_file = rs_file_open(poptGetArg(opcon), "wb", file_force);
254
255 rdiff_no_more_args(opcon);
256
257 result = rs_loadsig_file(sig_file, &sumset, &stats);
258 if (result != RS_DONE)
259 return result;
260
261 if (show_stats)
262 rs_log_stats(&stats);
263
264 if ((result = rs_build_hash_table(sumset)) != RS_DONE)
265 return result;
266
267 result = rs_delta_file(sumset, new_file, delta_file, &stats);
268
269 rs_file_close(delta_file);
270 rs_file_close(new_file);
271 rs_file_close(sig_file);
272
273 if (show_stats) {
275 rs_log_stats(&stats);
276 }
277
278 rs_free_sumset(sumset);
279
280 return result;
281}
282
283static rs_result rdiff_patch(poptContext opcon)
284{
285 /* patch BASIS [DELTA [NEWFILE]] */
286 FILE *basis_file, *delta_file, *new_file;
287 char const *basis_name;
288 rs_stats_t stats;
289 rs_result result;
290
291 if (!(basis_name = poptGetArg(opcon))) {
292 rdiff_usage("Usage for patch: "
293 "rdiff [OPTIONS] patch BASIS [DELTA [NEW]]");
294 exit(RS_SYNTAX_ERROR);
295 }
296
297 basis_file = rs_file_open(basis_name, "rb", file_force);
298 delta_file = rs_file_open(poptGetArg(opcon), "rb", file_force);
299 new_file = rs_file_open(poptGetArg(opcon), "wb", file_force);
300
301 rdiff_no_more_args(opcon);
302
303 result = rs_patch_file(basis_file, delta_file, new_file, &stats);
304
305 rs_file_close(new_file);
306 rs_file_close(delta_file);
307 rs_file_close(basis_file);
308
309 if (show_stats)
310 rs_log_stats(&stats);
311
312 return result;
313}
314
315static rs_result rdiff_action(poptContext opcon)
316{
317 const char *action;
318
319 action = poptGetArg(opcon);
320 if (!action) ;
321 else if (isprefix(action, "signature"))
322 return rdiff_sig(opcon);
323 else if (isprefix(action, "delta"))
324 return rdiff_delta(opcon);
325 else if (isprefix(action, "patch"))
326 return rdiff_patch(opcon);
327
328 rdiff_usage
329 ("You must specify an action: `signature', `delta', or `patch'.");
330 exit(RS_SYNTAX_ERROR);
331}
332
333int main(const int argc, const char *argv[])
334{
335 /* Initialize opts at runtime to avoid unknown address values. */
336 const struct poptOption opts[] = {
337 {"verbose", 'v', POPT_ARG_NONE, 0, 'v'},
338 {"version", 'V', POPT_ARG_NONE, 0, 'V'},
339 {"input-size", 'I', POPT_ARG_INT, &rs_inbuflen},
340 {"output-size", 'O', POPT_ARG_INT, &rs_outbuflen},
341 {"hash", 'H', POPT_ARG_STRING, &rs_hash_name},
342 {"rollsum", 'R', POPT_ARG_STRING, &rs_rollsum_name},
343 {"help", '?', POPT_ARG_NONE, 0, 'h'},
344 {0, 'h', POPT_ARG_NONE, 0, 'h'},
345 {"block-size", 'b', POPT_ARG_INT, &block_len},
346 {"sum-size", 'S', POPT_ARG_INT, &strong_len},
347 {"statistics", 's', POPT_ARG_NONE, &show_stats},
348 {"stats", 0, POPT_ARG_NONE, &show_stats},
349 {"gzip", 'z', POPT_ARG_NONE, 0, OPT_GZIP},
350 {"bzip2", 'i', POPT_ARG_NONE, 0, OPT_BZIP2},
351 {"force", 'f', POPT_ARG_NONE, &file_force},
352 {0}
353 };
354
355 poptContext opcon;
356 rs_result result;
357
358 opcon = poptGetContext("rdiff", argc, argv, opts, 0);
359 rdiff_options(opcon);
360 result = rdiff_action(opcon);
361
362 if (result != RS_DONE)
363 fprintf(stderr, "rdiff: Failed, %s.\n", rs_strerror(result));
364
365 poptFreeContext(opcon);
366 return result;
367}
String prefix text function.
int isprefix(char const *tip, char const *iceberg)
Return true if TIP is a prefix of ICEBERG.
Definition: isprefix.c:24
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:274
LIBRSYNC_EXPORT int rs_file_close(FILE *file)
Close a file with special handling for stdin or stdout.
Definition: fileutil.c:120
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:67
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:81
LIBRSYNC_EXPORT void rs_signature_log_stats(rs_signature_t const *sig)
Log the rs_signature_delta match stats.
Definition: sumset.c:256
LIBRSYNC_EXPORT int rs_supports_trace(void)
Check whether the library was compiled with debugging trace.
Definition: trace.c:102
LIBRSYNC_EXPORT void rs_free_sumset(rs_signature_t *)
Deep deallocation of checksums.
Definition: sumset.c:294
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:108
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:90
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:124
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:41
LIBRSYNC_EXPORT void rs_trace_set_level(rs_loglevel level)
Set the least important message severity that will be output.
Definition: trace.c:62
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:194
Signature of a whole file.
Definition: sumset.h:44
Performance statistics from a librsync encoding or decoding operation.
Definition: librsync.h:210