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.
6 * Author: Stephen McKay
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.
21 #include <sys/types.h>
28 #define CTM_STATUS ".ctm_status"
30 char *piece_dir = NULL; /* Where to store pieces of deltas. */
31 char *delta_dir = NULL; /* Where to store completed deltas. */
32 char *base_dir = NULL; /* The tree to apply deltas to. */
33 int delete_after = 0; /* Delete deltas after ctm applies them. */
34 int apply_verbose = 0; /* Run with '-v' */
35 int set_time = 0; /* Set the time of the files that is changed. */
36 int mask = 0; /* The current umask */
38 void apply_complete(void);
39 int read_piece(char *input_file);
40 int combine_if_complete(char *delta, int pce, int npieces);
41 int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
42 int decode_line(char *line, char *out_buf);
43 int lock_file(char *name);
46 * If given a '-p' flag, read encoded delta pieces from stdin or file
47 * arguments, decode them and assemble any completed deltas. If given
48 * a '-b' flag, pass any completed deltas to 'ctm' for application to
49 * the source tree. The '-d' flag is mandatory, but either of '-p' or
50 * '-b' can be omitted. If given the '-l' flag, notes and errors will
51 * be timestamped and written to the given file.
53 * Exit status is 0 for success or 1 for indigestible input. That is,
54 * 0 means the encode input pieces were decoded and stored, and 1 means
55 * some input was discarded. If a delta fails to apply, this won't be
56 * reflected in the exit status. In this case, the delta is left in
60 main(int argc, char **argv)
62 char *log_file = NULL;
69 err_prog_name(argv[0]);
71 OPTIONS("[-Dfuv] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
72 FLAG('D', delete_after)
75 FLAG('v', apply_verbose)
76 STRING('p', piece_dir)
77 STRING('d', delta_dir)
82 if (delta_dir == NULL)
85 if (piece_dir == NULL && (base_dir == NULL || argc > 1))
89 err_set_log(log_file);
92 * Digest each file in turn, or just stdin if no files were given.
96 if (piece_dir != NULL)
97 status = read_piece(NULL);
101 while (*++argv != NULL)
102 status |= read_piece(*argv);
106 * Maybe it's time to look for and apply completed deltas with ctm.
108 * Shall we report back to sendmail immediately, and let a child do
109 * the work? Sendmail will be waiting for us to complete, delaying
110 * other mail, and possibly some intermediate process (like MH slocal)
111 * will terminate us if we take too long!
113 * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
114 * Also, the child exit status is unimportant.
116 if (base_dir != NULL)
117 if (!fork_ctm || fork() == 0)
125 * Construct the file name of a piece of a delta.
127 #define mk_piece_name(fn,d,p,n) \
128 sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
131 * Construct the file name of an assembled delta.
133 #define mk_delta_name(fn,d) \
134 sprintf((fn), "%s/%s", delta_dir, (d))
137 * If the next required delta is now present, let ctm lunch on it and any
150 char fname[PATH_MAX];
152 char buf[PATH_MAX*2];
155 * Grab a lock on the ctm mutex file so that we can be sure we are
156 * working alone, not fighting another ctm_rmail!
158 strcpy(fname, delta_dir);
159 strcat(fname, "/.mutex_apply");
160 if ((lfd = lock_file(fname)) < 0)
164 * Find out which delta ctm needs next.
166 sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
167 if ((fp = fopen(fname, "r")) == NULL)
173 i = fscanf(fp, "%19s %d %c", class, &dn, junk);
182 * We might need to convert the delta filename to an absolute pathname.
185 if (delta_dir[0] != '/')
187 getcwd(here, sizeof(here)-1);
188 i = strlen(here) - 1;
189 if (i >= 0 && here[i] != '/')
197 * Keep applying deltas until we run out or something bad happens.
201 sprintf(delta, "%s.%04d.gz", class, ++dn);
202 mk_delta_name(fname, delta);
204 if (stat(fname, &sb) < 0)
207 sprintf(buf, "(cd %s && ctm %s%s%s%s) 2>&1", base_dir,
208 set_time ? "-u " : "",
209 apply_verbose ? "-v " : "", here, fname);
210 if ((ctm = popen(buf, "r")) == NULL)
212 err("ctm failed to apply %s", delta);
216 while (fgets(buf, sizeof(buf), ctm) != NULL)
219 if (i >= 0 && buf[i] == '\n')
224 if (pclose(ctm) != 0)
226 err("ctm failed to apply %s", delta);
233 err("%s applied%s", delta, delete_after ? " and deleted" : "");
237 * Closing the lock file clears the lock.
244 * This cheap plastic checksum effectively rotates our checksum-so-far
245 * left one, then adds the character. We only want 16 bits of it, and
246 * don't care what happens to the rest. It ain't much, but it's small.
248 #define add_ck(sum,x) \
249 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
253 * Decode the data between BEGIN and END, and stash it in the staging area.
254 * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
255 * If we have all pieces of a delta, combine them. Returns 0 on success,
256 * and 1 for any sort of failure.
259 read_piece(char *input_file)
268 unsigned claimed_cksum;
269 unsigned short cksum = 0;
273 char pname[PATH_MAX];
274 char tname[PATH_MAX];
278 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
280 err("cannot open '%s' for reading", input_file);
284 while (fgets(line, sizeof(line), ifp) != NULL)
289 * Remove all trailing white space.
291 i = strlen(line) - 1;
292 while (i > 0 && isspace(line[i]))
296 * Look for the beginning of an encoded piece.
303 if (sscanf(line, "CTM_MAIL BEGIN %29s %d %d %c",
304 delta, &pce, &npieces, junk) != 3)
307 while ((s = strchr(delta, '/')) != NULL)
311 strcpy(tname, piece_dir);
312 strcat(tname, "/p.XXXXXXXXXX");
313 if ((fd = mkstemp(tname)) == -1 ||
314 (ofp = fdopen(fd, "w")) == NULL)
317 err("cannot open '%s' for writing", tname);
321 err("*mkstemp: '%s'", tname);
332 * We are decoding. Stop if we see the end flag.
334 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
345 err("error writing %s", tname);
347 if (cksum != claimed_cksum)
348 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
350 if (e || cksum != claimed_cksum)
352 err("%s %d/%d discarded", delta, pce, npieces);
358 mk_piece_name(pname, delta, pce, npieces);
359 if (rename(tname, pname) < 0)
361 err("*rename: '%s' to '%s'", tname, pname);
362 err("%s %d/%d lost!", delta, pce, npieces);
368 err("%s %d/%d stored", delta, pce, npieces);
370 if (!combine_if_complete(delta, pce, npieces))
376 * Must be a line of encoded data. Decode it, sum it, and save it.
378 n = decode_line(line, out_buf);
381 err("line %d: illegal character: '%c'", line_no, line[-n]);
382 err("%s %d/%d discarded", delta, pce, npieces);
392 for (i = 0; i < n; i++)
393 add_ck(cksum, out_buf[i]);
395 fwrite(out_buf, sizeof(char), n, ofp);
400 err("truncated file");
401 err("%s %d/%d discarded", delta, pce, npieces);
411 err("error reading %s", input_file == NULL ? "stdin" : input_file);
415 if (input_file != NULL)
420 err("message contains no delta");
424 return (status != 0);
429 * Put the pieces together to form a delta, if they are all present.
430 * Returns 1 on success (even if we didn't do anything), and 0 on failure.
433 combine_if_complete(char *delta, int pce, int npieces)
438 char pname[PATH_MAX];
439 char dname[PATH_MAX];
440 char tname[PATH_MAX];
443 * We can probably just rename() it into place if it is a small delta.
447 mk_delta_name(dname, delta);
448 mk_piece_name(pname, delta, 1, 1);
449 if (rename(pname, dname) == 0)
451 chmod(dname, 0666 & ~mask);
452 err("%s complete", delta);
458 * Grab a lock on the reassembly mutex file so that we can be sure we are
459 * working alone, not fighting another ctm_rmail!
461 strcpy(tname, delta_dir);
462 strcat(tname, "/.mutex_build");
463 if ((lfd = lock_file(tname)) < 0)
467 * Are all of the pieces present? Of course the current one is,
468 * unless all pieces are missing because another ctm_rmail has
469 * processed them already.
471 for (i = 1; i <= npieces; i++)
475 mk_piece_name(pname, delta, i, npieces);
476 if (stat(pname, &sb) < 0)
484 * Stick them together. Let combine() use our file name buffers, since
485 * we're such good buddies. :-)
487 e = combine(delta, npieces, dname, pname, tname);
494 * Put the pieces together to form a delta.
495 * Returns 1 on success, and 0 on failure.
496 * Note: dname, pname, and tname are room for some file names that just
497 * happened to by lying around in the calling routine. Waste not, want not!
500 combine(char *delta, int npieces, char *dname, char *pname, char *tname)
507 strcpy(tname, delta_dir);
508 strcat(tname, "/d.XXXXXXXXXX");
509 if ((fd = mkstemp(tname)) == -1 ||
510 (dfp = fdopen(fd, "w")) == NULL)
514 err("cannot open '%s' for writing", tname);
517 err("*mkstemp: '%s'", tname);
522 * Reconstruct the delta by reading each piece in order.
524 for (i = 1; i <= npieces; i++)
526 mk_piece_name(pname, delta, i, npieces);
527 if ((pfp = fopen(pname, "r")) == NULL)
529 err("cannot open '%s' for reading", pname);
534 while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
535 fwrite(buf, sizeof(char), n, dfp);
540 err("error reading '%s'", pname);
551 err("error writing '%s'", tname);
556 mk_delta_name(dname, delta);
557 if (rename(tname, dname) < 0)
559 err("*rename: '%s' to '%s'", tname, dname);
563 chmod(dname, 0666 & ~mask);
566 * Throw the pieces away.
568 for (i = 1; i <= npieces; i++)
570 mk_piece_name(pname, delta, i, npieces);
571 if (unlink(pname) < 0)
572 err("*unlink: '%s'", pname);
575 err("%s complete", delta);
581 * MIME BASE64 decode table.
583 static unsigned char from_b64[0x80] =
585 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
586 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
587 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
588 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
589 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
590 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
591 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
592 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
593 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
594 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
595 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
596 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
597 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
598 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
599 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
600 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
605 * Decode a line of ASCII into binary. Returns the number of bytes in
606 * the output buffer, or < 0 on indigestable input. Error output is
607 * the negative of the index of the inedible character.
610 decode_line(char *line, char *out_buf)
612 unsigned char *ip = (unsigned char *)line;
613 unsigned char *op = (unsigned char *)out_buf;
619 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
623 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
628 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
633 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
643 if (*ip == '\0' || *ip == '\n')
644 return op - (unsigned char *)out_buf;
646 return -(ip - (unsigned char *)line);
651 * Create and lock the given file.
653 * Clearing the lock is as simple as closing the file descriptor we return.
656 lock_file(char *name)
660 if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
662 err("*open: '%s'", name);
665 if (flock(lfd, LOCK_EX) < 0)
668 err("*flock: '%s'", name);