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.
12 * $FreeBSD: src/usr.sbin/ctm/ctm_rmail/ctm_rmail.c,v 1.15.2.2 2002/08/26 18:58:53 iedowse Exp $
13 * $DragonFly: src/usr.sbin/ctm/ctm_rmail/Attic/ctm_rmail.c,v 1.2 2003/06/17 04:29:53 dillon Exp $
22 #include <sys/types.h>
29 #define CTM_STATUS ".ctm_status"
31 char *piece_dir = NULL; /* Where to store pieces of deltas. */
32 char *delta_dir = NULL; /* Where to store completed deltas. */
33 char *base_dir = NULL; /* The tree to apply deltas to. */
34 int delete_after = 0; /* Delete deltas after ctm applies them. */
35 int apply_verbose = 0; /* Run with '-v' */
36 int set_time = 0; /* Set the time of the files that is changed. */
37 int mask = 0; /* The current umask */
39 void apply_complete(void);
40 int read_piece(char *input_file);
41 int combine_if_complete(char *delta, int pce, int npieces);
42 int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
43 int decode_line(char *line, char *out_buf);
44 int lock_file(char *name);
47 * If given a '-p' flag, read encoded delta pieces from stdin or file
48 * arguments, decode them and assemble any completed deltas. If given
49 * a '-b' flag, pass any completed deltas to 'ctm' for application to
50 * the source tree. The '-d' flag is mandatory, but either of '-p' or
51 * '-b' can be omitted. If given the '-l' flag, notes and errors will
52 * be timestamped and written to the given file.
54 * Exit status is 0 for success or 1 for indigestible input. That is,
55 * 0 means the encode input pieces were decoded and stored, and 1 means
56 * some input was discarded. If a delta fails to apply, this won't be
57 * reflected in the exit status. In this case, the delta is left in
61 main(int argc, char **argv)
63 char *log_file = NULL;
70 err_prog_name(argv[0]);
72 OPTIONS("[-Dfuv] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
73 FLAG('D', delete_after)
76 FLAG('v', apply_verbose)
77 STRING('p', piece_dir)
78 STRING('d', delta_dir)
83 if (delta_dir == NULL)
86 if (piece_dir == NULL && (base_dir == NULL || argc > 1))
90 err_set_log(log_file);
93 * Digest each file in turn, or just stdin if no files were given.
97 if (piece_dir != NULL)
98 status = read_piece(NULL);
102 while (*++argv != NULL)
103 status |= read_piece(*argv);
107 * Maybe it's time to look for and apply completed deltas with ctm.
109 * Shall we report back to sendmail immediately, and let a child do
110 * the work? Sendmail will be waiting for us to complete, delaying
111 * other mail, and possibly some intermediate process (like MH slocal)
112 * will terminate us if we take too long!
114 * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
115 * Also, the child exit status is unimportant.
117 if (base_dir != NULL)
118 if (!fork_ctm || fork() == 0)
126 * Construct the file name of a piece of a delta.
128 #define mk_piece_name(fn,d,p,n) \
129 sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
132 * Construct the file name of an assembled delta.
134 #define mk_delta_name(fn,d) \
135 sprintf((fn), "%s/%s", delta_dir, (d))
138 * If the next required delta is now present, let ctm lunch on it and any
151 char fname[PATH_MAX];
153 char buf[PATH_MAX*2];
156 * Grab a lock on the ctm mutex file so that we can be sure we are
157 * working alone, not fighting another ctm_rmail!
159 strcpy(fname, delta_dir);
160 strcat(fname, "/.mutex_apply");
161 if ((lfd = lock_file(fname)) < 0)
165 * Find out which delta ctm needs next.
167 sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
168 if ((fp = fopen(fname, "r")) == NULL)
174 i = fscanf(fp, "%19s %d %c", class, &dn, junk);
183 * We might need to convert the delta filename to an absolute pathname.
186 if (delta_dir[0] != '/')
188 getcwd(here, sizeof(here)-1);
189 i = strlen(here) - 1;
190 if (i >= 0 && here[i] != '/')
198 * Keep applying deltas until we run out or something bad happens.
202 sprintf(delta, "%s.%04d.gz", class, ++dn);
203 mk_delta_name(fname, delta);
205 if (stat(fname, &sb) < 0)
208 sprintf(buf, "(cd %s && ctm %s%s%s%s) 2>&1", base_dir,
209 set_time ? "-u " : "",
210 apply_verbose ? "-v " : "", here, fname);
211 if ((ctm = popen(buf, "r")) == NULL)
213 err("ctm failed to apply %s", delta);
217 while (fgets(buf, sizeof(buf), ctm) != NULL)
220 if (i >= 0 && buf[i] == '\n')
225 if (pclose(ctm) != 0)
227 err("ctm failed to apply %s", delta);
234 err("%s applied%s", delta, delete_after ? " and deleted" : "");
238 * Closing the lock file clears the lock.
245 * This cheap plastic checksum effectively rotates our checksum-so-far
246 * left one, then adds the character. We only want 16 bits of it, and
247 * don't care what happens to the rest. It ain't much, but it's small.
249 #define add_ck(sum,x) \
250 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
254 * Decode the data between BEGIN and END, and stash it in the staging area.
255 * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
256 * If we have all pieces of a delta, combine them. Returns 0 on success,
257 * and 1 for any sort of failure.
260 read_piece(char *input_file)
269 unsigned claimed_cksum;
270 unsigned short cksum = 0;
274 char pname[PATH_MAX];
275 char tname[PATH_MAX];
279 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
281 err("cannot open '%s' for reading", input_file);
285 while (fgets(line, sizeof(line), ifp) != NULL)
290 * Remove all trailing white space.
292 i = strlen(line) - 1;
293 while (i > 0 && isspace(line[i]))
297 * Look for the beginning of an encoded piece.
304 if (sscanf(line, "CTM_MAIL BEGIN %29s %d %d %c",
305 delta, &pce, &npieces, junk) != 3)
308 while ((s = strchr(delta, '/')) != NULL)
312 strcpy(tname, piece_dir);
313 strcat(tname, "/p.XXXXXXXXXX");
314 if ((fd = mkstemp(tname)) == -1 ||
315 (ofp = fdopen(fd, "w")) == NULL)
318 err("cannot open '%s' for writing", tname);
322 err("*mkstemp: '%s'", tname);
333 * We are decoding. Stop if we see the end flag.
335 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
346 err("error writing %s", tname);
348 if (cksum != claimed_cksum)
349 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
351 if (e || cksum != claimed_cksum)
353 err("%s %d/%d discarded", delta, pce, npieces);
359 mk_piece_name(pname, delta, pce, npieces);
360 if (rename(tname, pname) < 0)
362 err("*rename: '%s' to '%s'", tname, pname);
363 err("%s %d/%d lost!", delta, pce, npieces);
369 err("%s %d/%d stored", delta, pce, npieces);
371 if (!combine_if_complete(delta, pce, npieces))
377 * Must be a line of encoded data. Decode it, sum it, and save it.
379 n = decode_line(line, out_buf);
382 err("line %d: illegal character: '%c'", line_no, line[-n]);
383 err("%s %d/%d discarded", delta, pce, npieces);
393 for (i = 0; i < n; i++)
394 add_ck(cksum, out_buf[i]);
396 fwrite(out_buf, sizeof(char), n, ofp);
401 err("truncated file");
402 err("%s %d/%d discarded", delta, pce, npieces);
412 err("error reading %s", input_file == NULL ? "stdin" : input_file);
416 if (input_file != NULL)
421 err("message contains no delta");
425 return (status != 0);
430 * Put the pieces together to form a delta, if they are all present.
431 * Returns 1 on success (even if we didn't do anything), and 0 on failure.
434 combine_if_complete(char *delta, int pce, int npieces)
439 char pname[PATH_MAX];
440 char dname[PATH_MAX];
441 char tname[PATH_MAX];
444 * We can probably just rename() it into place if it is a small delta.
448 mk_delta_name(dname, delta);
449 mk_piece_name(pname, delta, 1, 1);
450 if (rename(pname, dname) == 0)
452 chmod(dname, 0666 & ~mask);
453 err("%s complete", delta);
459 * Grab a lock on the reassembly mutex file so that we can be sure we are
460 * working alone, not fighting another ctm_rmail!
462 strcpy(tname, delta_dir);
463 strcat(tname, "/.mutex_build");
464 if ((lfd = lock_file(tname)) < 0)
468 * Are all of the pieces present? Of course the current one is,
469 * unless all pieces are missing because another ctm_rmail has
470 * processed them already.
472 for (i = 1; i <= npieces; i++)
476 mk_piece_name(pname, delta, i, npieces);
477 if (stat(pname, &sb) < 0)
485 * Stick them together. Let combine() use our file name buffers, since
486 * we're such good buddies. :-)
488 e = combine(delta, npieces, dname, pname, tname);
495 * Put the pieces together to form a delta.
496 * Returns 1 on success, and 0 on failure.
497 * Note: dname, pname, and tname are room for some file names that just
498 * happened to by lying around in the calling routine. Waste not, want not!
501 combine(char *delta, int npieces, char *dname, char *pname, char *tname)
508 strcpy(tname, delta_dir);
509 strcat(tname, "/d.XXXXXXXXXX");
510 if ((fd = mkstemp(tname)) == -1 ||
511 (dfp = fdopen(fd, "w")) == NULL)
515 err("cannot open '%s' for writing", tname);
518 err("*mkstemp: '%s'", tname);
523 * Reconstruct the delta by reading each piece in order.
525 for (i = 1; i <= npieces; i++)
527 mk_piece_name(pname, delta, i, npieces);
528 if ((pfp = fopen(pname, "r")) == NULL)
530 err("cannot open '%s' for reading", pname);
535 while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
536 fwrite(buf, sizeof(char), n, dfp);
541 err("error reading '%s'", pname);
552 err("error writing '%s'", tname);
557 mk_delta_name(dname, delta);
558 if (rename(tname, dname) < 0)
560 err("*rename: '%s' to '%s'", tname, dname);
564 chmod(dname, 0666 & ~mask);
567 * Throw the pieces away.
569 for (i = 1; i <= npieces; i++)
571 mk_piece_name(pname, delta, i, npieces);
572 if (unlink(pname) < 0)
573 err("*unlink: '%s'", pname);
576 err("%s complete", delta);
582 * MIME BASE64 decode table.
584 static unsigned char from_b64[0x80] =
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, 0xff, 0xff, 0xff, 0xff, 0xff,
591 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
592 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
593 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
594 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
595 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
596 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
597 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
598 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
599 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
600 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
601 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
606 * Decode a line of ASCII into binary. Returns the number of bytes in
607 * the output buffer, or < 0 on indigestable input. Error output is
608 * the negative of the index of the inedible character.
611 decode_line(char *line, char *out_buf)
613 unsigned char *ip = (unsigned char *)line;
614 unsigned char *op = (unsigned char *)out_buf;
620 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
624 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
629 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
634 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
644 if (*ip == '\0' || *ip == '\n')
645 return op - (unsigned char *)out_buf;
647 return -(ip - (unsigned char *)line);
652 * Create and lock the given file.
654 * Clearing the lock is as simple as closing the file descriptor we return.
657 lock_file(char *name)
661 if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
663 err("*open: '%s'", name);
666 if (flock(lfd, LOCK_EX) < 0)
669 err("*flock: '%s'", name);