Deleted Added
full compact
1/*
2 * Accept one (or more) ASCII encoded chunks that together make a compressed
3 * CTM delta. Decode them and reconstruct the deltas. Any completed
4 * deltas may be passed to ctm for unpacking.
5 *
6 * Author: Stephen McKay
7 *
8 * NOTICE: This is free software. I hope you get some use from this program.
9 * In return you should think about all the nice people who give away software.
10 * Maybe you should write some free software too.
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <unistd.h>
16#include <string.h>
17#include <errno.h>
18#include <unistd.h>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include "error.h"
22#include "options.h"
23
24#define CTM_STATUS ".ctm_status"
25
26char *piece_dir = NULL; /* Where to store pieces of deltas. */
27char *delta_dir = NULL; /* Where to store completed deltas. */
28char *base_dir = NULL; /* The tree to apply deltas to. */
29int delete_after = 0; /* Delete deltas after ctm applies them. */
30
31void apply_complete(void);
32int read_piece(char *input_file);
33int combine_if_complete(char *delta, int pce, int npieces);
34int decode_line(char *line, char *out_buf);
35
36/*
37 * If given a '-p' flag, read encoded delta pieces from stdin or file
38 * arguments, decode them and assemble any completed deltas. If given
39 * a '-b' flag, pass any completed deltas to 'ctm' for application to
40 * the source tree. The '-d' flag is mandatory, but either of '-p' or
41 * '-b' can be omitted. If given the '-l' flag, notes and errors will
42 * be timestamped and written to the given file.
43 *
44 * Exit status is 0 for success or 1 for indigestible input. That is,
45 * 0 means the encode input pieces were decoded and stored, and 1 means
46 * some input was discarded. If a delta fails to apply, this won't be
47 * reflected in the exit status. In this case, the delta is left in
48 * 'deltadir'.
49 */
50
51int
52main(int argc, char **argv)
53 {
54 char *log_file = NULL;
55 int status = 0;
56
57 err_prog_name(argv[0]);
58
59 OPTIONS("[-D] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
60 FLAG('D', delete_after)
61 STRING('p', piece_dir)
62 STRING('d', delta_dir)
63 STRING('b', base_dir)
64 STRING('l', log_file)
65 ENDOPTS
66
64 if (delta_dir == NULL || piece_dir == NULL && (base_dir == NULL || argc>1))
67 if (delta_dir == NULL)
68 usage();
69
70 if (piece_dir == NULL && (base_dir == NULL || argc>1))
71 usage();
72
73 if (log_file != NULL)
74 err_set_log(log_file);
75
76 if (argc <= 1)
77 {
78 if (piece_dir != NULL)
79 status = read_piece(NULL);
80 }
81 else
82 {
83 while (*++argv != NULL)
84 status |= read_piece(*argv);
85 }
86
87 if (base_dir != NULL)
88 apply_complete();
89
90 return status;
91 }
92
93
94/*
95 * Construct the file name of a piece of a delta.
96 */
97#define mk_piece_name(fn,d,p,n) \
98 sprintf((fn), "%s/%s+%d-%d", piece_dir, (d), (p), (n))
99
100/*
101 * Construct the file name of an assembled delta.
102 */
103#define mk_delta_name(fn,d) \
104 sprintf((fn), "%s/%s", delta_dir, (d))
105
106/*
107 * If the next required delta is now present, let ctm lunch on it and any
108 * contiguous deltas.
109 */
110void
111apply_complete()
112 {
113 int i, dn;
114 FILE *fp, *ctm;
115 struct stat sb;
116 char class[20];
117 char delta[30];
118 char fname[1000];
119 char buf[2000];
120 char junk[2];
121 char here[1000];
122
123 sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
124 if ((fp = fopen(fname, "r")) == NULL)
125 return;
126
127 i = fscanf(fp, "%s %d %c", class, &dn, junk);
128 fclose(fp);
129 if (i != 2)
130 return;
131
132 /*
133 * We might need to convert the delta filename to an absolute pathname.
134 */
135 here[0] = '\0';
136 if (delta_dir[0] != '/')
137 {
138 getcwd(here, sizeof(here)-1);
139 i = strlen(here) - 1;
140 if (i >= 0 && here[i] != '/')
141 {
142 here[++i] = '/';
143 here[++i] = '\0';
144 }
145 }
146
147 /*
148 * Keep applying deltas until we run out or something bad happens.
149 */
150 for (;;)
151 {
152 sprintf(delta, "%s.%04d.gz", class, ++dn);
153 mk_delta_name(fname, delta);
154
155 if (stat(fname, &sb) < 0)
156 return;
157
158 sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname);
159 if ((ctm = popen(buf, "r")) == NULL)
160 {
161 err("ctm failed to apply %s", delta);
162 return;
163 }
164
165 while (fgets(buf, sizeof(buf), ctm) != NULL)
166 {
167 i = strlen(buf) - 1;
168 if (i >= 0 && buf[i] == '\n')
169 buf[i] = '\0';
170 err("ctm: %s", buf);
171 }
172
173 if (pclose(ctm) != 0)
174 {
175 err("ctm failed to apply %s", delta);
176 return;
177 }
178
179 if (delete_after)
180 unlink(fname);
181
182 err("%s applied%s", delta, delete_after ? " and deleted" : "");
183 }
184 }
185
186
187/*
188 * This cheap plastic checksum effectively rotates our checksum-so-far
189 * left one, then adds the character. We only want 16 bits of it, and
190 * don't care what happens to the rest. It ain't much, but it's small.
191 */
192#define add_ck(sum,x) \
193 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
194
195
196/*
197 * Decode the data between BEGIN and END, and stash it in the staging area.
198 * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
199 * If we have all pieces of a delta, combine them. Returns 0 on success,
200 * and 1 for any sort of failure.
201 */
202int
203read_piece(char *input_file)
204 {
205 int status = 0;
200 FILE *ifp, *ofp;
206 FILE *ifp, *ofp = 0;
207 int decoding = 0;
208 int line_no = 0;
209 int i, n;
210 int pce, npieces;
211 unsigned claimed_cksum;
206 unsigned short cksum;
212 unsigned short cksum = 0;
213 char out_buf[200];
214 char line[200];
215 char delta[30];
216 char pname[1000];
217 char junk[2];
218
219 ifp = stdin;
220 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
221 {
222 err("cannot open '%s' for reading", input_file);
223 return 1;
224 }
225
226 while (fgets(line, sizeof(line), ifp) != NULL)
227 {
228 line_no++;
229
230 /*
231 * Look for the beginning of an encoded piece.
232 */
233 if (!decoding)
234 {
229 if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c", delta, &pce, &npieces, junk) == 3)
230 {
231 char *s;
235 char *s;
236
233 while ((s = strchr(delta, '/')) != NULL)
234 *s = '_';
237 if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c",
238 delta, &pce, &npieces, junk) != 3)
239 continue;
240
236 mk_piece_name(pname, delta, pce, npieces);
237 if ((ofp = fopen(pname, "w")) == NULL)
238 {
239 err("cannot open '%s' for writing", pname);
240 status++;
241 continue;
242 }
241 while ((s = strchr(delta, '/')) != NULL)
242 *s = '_';
243
244 cksum = 0xffff;
245 decoding++;
244 mk_piece_name(pname, delta, pce, npieces);
245 if ((ofp = fopen(pname, "w")) == NULL)
246 {
247 err("cannot open '%s' for writing", pname);
248 status++;
249 continue;
250 }
251
252 cksum = 0xffff;
253 decoding++;
254 continue;
255 }
256
257 /*
258 * We are decoding. Stop if we see the end flag.
259 */
260 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
261 {
262 int e;
263
264 decoding = 0;
265
266 fflush(ofp);
267 e = ferror(ofp);
268 fclose(ofp);
269
270 if (e)
271 err("error writing %s", pname);
272
273 if (cksum != claimed_cksum)
274 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
275
276 if (e || cksum != claimed_cksum)
277 {
278 err("%s %d/%d discarded", delta, pce, npieces);
279 unlink(pname);
280 status++;
281 continue;
282 }
283
284 err("%s %d/%d stored", delta, pce, npieces);
285
286 if (!combine_if_complete(delta, pce, npieces))
287 status++;
288 continue;
289 }
290
291 /*
292 * Must be a line of encoded data. Decode it, sum it, and save it.
293 */
294 n = decode_line(line, out_buf);
295 if (n < 0)
296 {
297 err("line %d: illegal character: '%c'", line_no, line[-n]);
298 err("%s %d/%d discarded", delta, pce, npieces);
299
300 fclose(ofp);
301 unlink(pname);
302
303 status++;
304 decoding = 0;
305 continue;
306 }
307
308 for (i = 0; i < n; i++)
309 add_ck(cksum, out_buf[i]);
310
311 fwrite(out_buf, sizeof(char), n, ofp);
312 }
313
314 if (decoding)
315 {
316 err("truncated file");
317 err("%s %d/%d discarded", delta, pce, npieces);
318
319 fclose(ofp);
320 unlink(pname);
321
322 status++;
323 }
324
325 if (ferror(ifp))
326 {
327 err("error reading %s", input_file == NULL ? "stdin" : input_file);
328 status++;
329 }
330
331 if (input_file != NULL)
332 fclose(ifp);
333
334 return (status != 0);
335 }
336
337
338/*
339 * Put the pieces together to form a delta, if they are all present.
340 * Returns 1 on success (even if we didn't do anything), and 0 on failure.
341 */
342int
343combine_if_complete(char *delta, int pce, int npieces)
344 {
345 int i;
346 FILE *dfp, *pfp;
347 int c;
348 struct stat sb;
349 char pname[1000];
350 char dname[1000];
351
352 /*
353 * All here?
354 */
355 for (i = 1; i <= npieces; i++)
356 {
357 if (i == pce)
358 continue;
359 mk_piece_name(pname, delta, i, npieces);
360 if (stat(pname, &sb) < 0)
361 return 1;
362 }
363
364 mk_delta_name(dname, delta);
365
366 /*
367 * We can probably just rename() it in to place if it is a small delta.
368 */
369 if (npieces == 1)
370 {
371 mk_piece_name(pname, delta, 1, 1);
372 if (rename(pname, dname) == 0)
373 {
374 err("%s complete", delta);
375 return 1;
376 }
377 }
378
379 if ((dfp = fopen(dname, "w")) == NULL)
380 {
381 err("cannot open '%s' for writing", dname);
382 return 0;
383 }
384
385 /*
386 * Ok, the hard way. Reconstruct the delta by reading each piece in order.
387 */
388 for (i = 1; i <= npieces; i++)
389 {
390 mk_piece_name(pname, delta, i, npieces);
391 if ((pfp = fopen(pname, "r")) == NULL)
392 {
393 err("cannot open '%s' for reading", pname);
394 fclose(dfp);
395 unlink(dname);
396 return 0;
397 }
398 while ((c = getc(pfp)) != EOF)
399 putc(c, dfp);
400 fclose(pfp);
401 }
402 fflush(dfp);
403 if (ferror(dfp))
404 {
405 err("error writing '%s'", dname);
406 fclose(dfp);
407 unlink(dname);
408 return 0;
409 }
410 fclose(dfp);
411
412 /*
413 * Throw the pieces away.
414 */
415 for (i = 1; i <= npieces; i++)
416 {
417 mk_piece_name(pname, delta, i, npieces);
418 unlink(pname);
419 }
420
421 err("%s complete", delta);
422 return 1;
423 }
424
425
426/*
427 * MIME BASE64 decode table.
428 */
429static unsigned char from_b64[0x80] =
430 {
431 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
432 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
433 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
434 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
435 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
436 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
437 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
438 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
439 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
440 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
441 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
442 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
443 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
444 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
445 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
446 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
447 };
448
449
450/*
451 * Decode a line of ASCII into binary. Returns the number of bytes in
452 * the output buffer, or < 0 on indigestable input. Error output is
453 * the negative of the index of the inedible character.
454 */
455int
456decode_line(char *line, char *out_buf)
457 {
458 unsigned char *ip = (unsigned char *)line;
459 unsigned char *op = (unsigned char *)out_buf;
460 unsigned long bits;
461 unsigned x;
462
463 for (;;)
464 {
465 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
466 break;
467 bits = x << 18;
468 ip++;
469 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
470 {
471 bits |= x << 12;
472 *op++ = bits >> 16;
473 ip++;
474 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
475 {
476 bits |= x << 6;
477 *op++ = bits >> 8;
478 ip++;
479 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
480 {
481 bits |= x;
482 *op++ = bits;
483 ip++;
484 }
485 }
486 }
487 }
488
489 if (*ip == '\0' || *ip == '\n')
490 return op - (unsigned char *)out_buf;
491 else
492 return -(ip - (unsigned char *)line);
493 }