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