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
15#include <strings.h>
16#include <ctype.h>
17#include <errno.h>
18#include <unistd.h>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <fcntl.h>
22#include <limits.h>
23#include "error.h"
24#include "options.h"
25
26#define CTM_STATUS ".ctm_status"
27
28char *piece_dir = NULL; /* Where to store pieces of deltas. */
29char *delta_dir = NULL; /* Where to store completed deltas. */
30char *base_dir = NULL; /* The tree to apply deltas to. */
31int delete_after = 0; /* Delete deltas after ctm applies them. */
32
33void apply_complete(void);
34int read_piece(char *input_file);
35int combine_if_complete(char *delta, int pce, int npieces);
36int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
37int decode_line(char *line, char *out_buf);
38int lock_file(char *name);
39
40/*
41 * If given a '-p' flag, read encoded delta pieces from stdin or file
42 * arguments, decode them and assemble any completed deltas. If given
43 * a '-b' flag, pass any completed deltas to 'ctm' for application to
44 * the source tree. The '-d' flag is mandatory, but either of '-p' or
45 * '-b' can be omitted. If given the '-l' flag, notes and errors will
46 * be timestamped and written to the given file.
47 *
48 * Exit status is 0 for success or 1 for indigestible input. That is,
49 * 0 means the encode input pieces were decoded and stored, and 1 means
50 * some input was discarded. If a delta fails to apply, this won't be
51 * reflected in the exit status. In this case, the delta is left in
52 * 'deltadir'.
53 */
54int
55main(int argc, char **argv)
56 {
57 char *log_file = NULL;
58 int status = 0;
59 int fork_ctm = 0;
60
61 err_prog_name(argv[0]);
62
63 OPTIONS("[-Df] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
64 FLAG('D', delete_after)
65 FLAG('f', fork_ctm)
66 STRING('p', piece_dir)
67 STRING('d', delta_dir)
68 STRING('b', base_dir)
69 STRING('l', log_file)
70 ENDOPTS
71
72 if (delta_dir == NULL)
73 usage();
74
75 if (piece_dir == NULL && (base_dir == NULL || argc > 1))
76 usage();
77
78 if (log_file != NULL)
79 err_set_log(log_file);
80
81 /*
82 * Digest each file in turn, or just stdin if no files were given.
83 */
84 if (argc <= 1)
85 {
86 if (piece_dir != NULL)
87 status = read_piece(NULL);
88 }
89 else
90 {
91 while (*++argv != NULL)
92 status |= read_piece(*argv);
93 }
94
95 /*
96 * Maybe it's time to look for and apply completed deltas with ctm.
97 *
98 * Shall we report back to sendmail immediately, and let a child do
99 * the work? Sendmail will be waiting for us to complete, delaying
100 * other mail, and possibly some intermediate process (like MH slocal)
101 * will terminate us if we take too long!
102 *
103 * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
104 * Also, the child exit status is unimportant.
105 */
106 if (base_dir != NULL)
107 if (!fork_ctm || fork() == 0)
108 apply_complete();
109
110 return status;
111 }
112
113
114/*
115 * Construct the file name of a piece of a delta.
116 */
117#define mk_piece_name(fn,d,p,n) \
118 sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
119
120/*
121 * Construct the file name of an assembled delta.
122 */
123#define mk_delta_name(fn,d) \
124 sprintf((fn), "%s/%s", delta_dir, (d))
125
126/*
127 * If the next required delta is now present, let ctm lunch on it and any
128 * contiguous deltas.
129 */
130void
131apply_complete()
132 {
133 int i, dn;
134 int lfd;
135 FILE *fp, *ctm;
136 struct stat sb;
137 char class[20];
138 char delta[30];
139 char junk[2];
140 char fname[PATH_MAX];
141 char here[PATH_MAX];
142 char buf[PATH_MAX*2];
143
144 /*
145 * Grab a lock on the ctm mutex file so that we can be sure we are
146 * working alone, not fighting another ctm_rmail!
147 */
148 strcpy(fname, delta_dir);
149 strcat(fname, "/.mutex_apply");
150 if ((lfd = lock_file(fname)) < 0)
151 return;
152
153 /*
154 * Find out which delta ctm needs next.
155 */
156 sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
157 if ((fp = fopen(fname, "r")) == NULL)
158 {
159 close(lfd);
160 return;
161 }
162
163 i = fscanf(fp, "%s %d %c", class, &dn, junk);
164 fclose(fp);
165 if (i != 2)
166 {
167 close(lfd);
168 return;
169 }
170
171 /*
172 * We might need to convert the delta filename to an absolute pathname.
173 */
174 here[0] = '\0';
175 if (delta_dir[0] != '/')
176 {
177 getcwd(here, sizeof(here)-1);
178 i = strlen(here) - 1;
179 if (i >= 0 && here[i] != '/')
180 {
181 here[++i] = '/';
182 here[++i] = '\0';
183 }
184 }
185
186 /*
187 * Keep applying deltas until we run out or something bad happens.
188 */
189 for (;;)
190 {
191 sprintf(delta, "%s.%04d.gz", class, ++dn);
192 mk_delta_name(fname, delta);
193
194 if (stat(fname, &sb) < 0)
195 break;
196
197 sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname);
198 if ((ctm = popen(buf, "r")) == NULL)
199 {
200 err("ctm failed to apply %s", delta);
201 break;
202 }
203
204 while (fgets(buf, sizeof(buf), ctm) != NULL)
205 {
206 i = strlen(buf) - 1;
207 if (i >= 0 && buf[i] == '\n')
208 buf[i] = '\0';
209 err("ctm: %s", buf);
210 }
211
212 if (pclose(ctm) != 0)
213 {
214 err("ctm failed to apply %s", delta);
215 break;
216 }
217
218 if (delete_after)
219 unlink(fname);
220
221 err("%s applied%s", delta, delete_after ? " and deleted" : "");
222 }
223
224 /*
225 * Closing the lock file clears the lock.
226 */
227 close(lfd);
228 }
229
230
231/*
232 * This cheap plastic checksum effectively rotates our checksum-so-far
233 * left one, then adds the character. We only want 16 bits of it, and
234 * don't care what happens to the rest. It ain't much, but it's small.
235 */
236#define add_ck(sum,x) \
237 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
238
239
240/*
241 * Decode the data between BEGIN and END, and stash it in the staging area.
242 * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
243 * If we have all pieces of a delta, combine them. Returns 0 on success,
244 * and 1 for any sort of failure.
245 */
246int
247read_piece(char *input_file)
248 {
249 int status = 0;
250 FILE *ifp, *ofp = 0;
251 int decoding = 0;
252 int got_one = 0;
253 int line_no = 0;
254 int i, n;
255 int pce, npieces;
256 unsigned claimed_cksum;
257 unsigned short cksum = 0;
258 char out_buf[200];
259 char line[200];
260 char delta[30];
261 char pname[PATH_MAX];
262 char tname[PATH_MAX];
263 char junk[2];
264
265 ifp = stdin;
266 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
267 {
268 err("cannot open '%s' for reading", input_file);
269 return 1;
270 }
271
272 while (fgets(line, sizeof(line), ifp) != NULL)
273 {
274 line_no++;
275
276 /*
277 * Remove all trailing white space.
278 */
279 i = strlen(line) - 1;
280 while (i > 0 && isspace(line[i]))
281 line[--i] = '\0';
282
283 /*
284 * Look for the beginning of an encoded piece.
285 */
286 if (!decoding)
287 {
288 char *s;
289
290 if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c",
291 delta, &pce, &npieces, junk) != 3)
292 continue;
293
294 while ((s = strchr(delta, '/')) != NULL)
295 *s = '_';
296
297 got_one++;
298 strcpy(tname, piece_dir);
299 strcat(tname, "/p.XXXXXX");
300 if (mktemp(tname) == NULL)
301 {
302 err("*mktemp: '%s'", tname);
303 status++;
304 continue;
305 }
306 if ((ofp = fopen(tname, "w")) == NULL)
307 {
308 err("cannot open '%s' for writing", tname);
309 status++;
310 continue;
311 }
312
313 cksum = 0xffff;
314 decoding++;
315 continue;
316 }
317
318 /*
319 * We are decoding. Stop if we see the end flag.
320 */
321 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
322 {
323 int e;
324
325 decoding = 0;
326
327 fflush(ofp);
328 e = ferror(ofp);
329 fclose(ofp);
330
331 if (e)
332 err("error writing %s", tname);
333
334 if (cksum != claimed_cksum)
335 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
336
337 if (e || cksum != claimed_cksum)
338 {
339 err("%s %d/%d discarded", delta, pce, npieces);
340 unlink(tname);
341 status++;
342 continue;
343 }
344
345 mk_piece_name(pname, delta, pce, npieces);
346 if (rename(tname, pname) < 0)
347 {
348 err("*rename: '%s' to '%s'", tname, pname);
349 err("%s %d/%d lost!", delta, pce, npieces);
350 unlink(tname);
351 status++;
352 continue;
353 }
354
355 err("%s %d/%d stored", delta, pce, npieces);
356
357 if (!combine_if_complete(delta, pce, npieces))
358 status++;
359 continue;
360 }
361
362 /*
363 * Must be a line of encoded data. Decode it, sum it, and save it.
364 */
365 n = decode_line(line, out_buf);
366 if (n <= 0)
367 {
368 err("line %d: illegal character: '%c'", line_no, line[-n]);
369 err("%s %d/%d discarded", delta, pce, npieces);
370
371 fclose(ofp);
372 unlink(tname);
373
374 status++;
375 decoding = 0;
376 continue;
377 }
378
379 for (i = 0; i < n; i++)
380 add_ck(cksum, out_buf[i]);
381
382 fwrite(out_buf, sizeof(char), n, ofp);
383 }
384
385 if (decoding)
386 {
387 err("truncated file");
388 err("%s %d/%d discarded", delta, pce, npieces);
389
390 fclose(ofp);
391 unlink(tname);
392
393 status++;
394 }
395
396 if (ferror(ifp))
397 {
398 err("error reading %s", input_file == NULL ? "stdin" : input_file);
399 status++;
400 }
401
402 if (input_file != NULL)
403 fclose(ifp);
404
405 if (!got_one)
406 {
407 err("message contains no delta");
408 status++;
409 }
410
411 return (status != 0);
412 }
413
414
415/*
416 * Put the pieces together to form a delta, if they are all present.
417 * Returns 1 on success (even if we didn't do anything), and 0 on failure.
418 */
419int
420combine_if_complete(char *delta, int pce, int npieces)
421 {
422 int i, e;
423 int lfd;
424 struct stat sb;
425 char pname[PATH_MAX];
426 char dname[PATH_MAX];
427 char tname[PATH_MAX];
428
429 /*
430 * We can probably just rename() it into place if it is a small delta.
431 */
432 if (npieces == 1)
433 {
434 mk_delta_name(dname, delta);
435 mk_piece_name(pname, delta, 1, 1);
436 if (rename(pname, dname) == 0)
437 {
438 err("%s complete", delta);
439 return 1;
440 }
441 }
442
443 /*
444 * Grab a lock on the reassembly mutex file so that we can be sure we are
445 * working alone, not fighting another ctm_rmail!
446 */
447 strcpy(tname, delta_dir);
448 strcat(tname, "/.mutex_build");
449 if ((lfd = lock_file(tname)) < 0)
450 return 0;
451
452 /*
453 * Are all of the pieces present? Of course the current one is,
454 * unless all pieces are missing because another ctm_rmail has
455 * processed them already.
456 */
457 for (i = 1; i <= npieces; i++)
458 {
459 if (i == pce)
460 continue;
461 mk_piece_name(pname, delta, i, npieces);
462 if (stat(pname, &sb) < 0)
463 {
464 close(lfd);
465 return 1;
466 }
467 }
468
469 /*
470 * Stick them together. Let combine() use our file name buffers, since
471 * we're such good buddies. :-)
472 */
473 e = combine(delta, npieces, dname, pname, tname);
474 close(lfd);
475 return e;
476 }
477
478
479/*
480 * Put the pieces together to form a delta.
481 * Returns 1 on success, and 0 on failure.
482 * Note: dname, pname, and tname are room for some file names that just
483 * happened to by lying around in the calling routine. Waste not, want not!
484 */
485int
486combine(char *delta, int npieces, char *dname, char *pname, char *tname)
487 {
488 FILE *dfp, *pfp;
489 int i, n, e;
490 char buf[BUFSIZ];
491
492 strcpy(tname, delta_dir);
493 strcat(tname, "/d.XXXXXX");
494 if (mktemp(tname) == NULL)
495 {
496 err("*mktemp: '%s'", tname);
497 return 0;
498 }
499 if ((dfp = fopen(tname, "w")) == NULL)
500 {
501 err("cannot open '%s' for writing", tname);
502 return 0;
503 }
504
505 /*
506 * Reconstruct the delta by reading each piece in order.
507 */
508 for (i = 1; i <= npieces; i++)
509 {
510 mk_piece_name(pname, delta, i, npieces);
511 if ((pfp = fopen(pname, "r")) == NULL)
512 {
513 err("cannot open '%s' for reading", pname);
514 fclose(dfp);
515 unlink(tname);
516 return 0;
517 }
518 while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
519 fwrite(buf, sizeof(char), n, dfp);
520 e = ferror(pfp);
521 fclose(pfp);
522 if (e)
523 {
524 err("error reading '%s'", pname);
525 fclose(dfp);
526 unlink(tname);
527 return 0;
528 }
529 }
530 fflush(dfp);
531 e = ferror(dfp);
532 fclose(dfp);
533 if (e)
534 {
535 err("error writing '%s'", tname);
536 unlink(tname);
537 return 0;
538 }
539
540 mk_delta_name(dname, delta);
541 if (rename(tname, dname) < 0)
542 {
543 err("*rename: '%s' to '%s'", tname, dname);
544 unlink(tname);
545 return 0;
546 }
547
548 /*
549 * Throw the pieces away.
550 */
551 for (i = 1; i <= npieces; i++)
552 {
553 mk_piece_name(pname, delta, i, npieces);
554 if (unlink(pname) < 0)
555 err("*unlink: '%s'", pname);
556 }
557
558 err("%s complete", delta);
559 return 1;
560 }
561
562
563/*
564 * MIME BASE64 decode table.
565 */
566static unsigned char from_b64[0x80] =
567 {
568 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
569 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
570 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
571 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
572 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
573 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
574 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
575 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
576 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
577 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
578 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
579 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
580 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
581 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
582 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
583 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
584 };
585
586
587/*
588 * Decode a line of ASCII into binary. Returns the number of bytes in
589 * the output buffer, or < 0 on indigestable input. Error output is
590 * the negative of the index of the inedible character.
591 */
592int
593decode_line(char *line, char *out_buf)
594 {
595 unsigned char *ip = (unsigned char *)line;
596 unsigned char *op = (unsigned char *)out_buf;
597 unsigned long bits;
598 unsigned x;
599
600 for (;;)
601 {
602 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
603 break;
604 bits = x << 18;
605 ip++;
606 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
607 {
608 bits |= x << 12;
609 *op++ = bits >> 16;
610 ip++;
611 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
612 {
613 bits |= x << 6;
614 *op++ = bits >> 8;
615 ip++;
616 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
617 {
618 bits |= x;
619 *op++ = bits;
620 ip++;
621 }
622 }
623 }
624 }
625
626 if (*ip == '\0' || *ip == '\n')
627 return op - (unsigned char *)out_buf;
628 else
629 return -(ip - (unsigned char *)line);
630 }
631
632
633/*
634 * Create and lock the given file.
635 *
636 * Clearing the lock is as simple as closing the file descriptor we return.
637 */
638int
639lock_file(char *name)
640 {
641 int lfd;
642
643 if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
644 {
645 err("*open: '%s'", name);
646 return -1;
647 }
648 if (flock(lfd, LOCK_EX) < 0)
649 {
650 close(lfd);
651 err("*flock: '%s'", name);
652 return -1;
653 }
654 return lfd;
655 }