ctm_rmail.c revision 6457
16081Sphk/*
26081Sphk * Accept one (or more) ASCII encoded chunks that together make a compressed
36081Sphk * CTM delta.  Decode them and reconstruct the deltas.  Any completed
46081Sphk * deltas may be passed to ctm for unpacking.
56081Sphk *
66081Sphk * Author: Stephen McKay
76081Sphk *
86081Sphk * NOTICE: This is free software.  I hope you get some use from this program.
96081Sphk * In return you should think about all the nice people who give away software.
106081Sphk * Maybe you should write some free software too.
116081Sphk */
126081Sphk
136081Sphk#include <stdio.h>
146081Sphk#include <stdlib.h>
156081Sphk#include <string.h>
166081Sphk#include <errno.h>
176081Sphk#include <unistd.h>
186081Sphk#include <sys/types.h>
196081Sphk#include <sys/stat.h>
206081Sphk#include "error.h"
216081Sphk#include "options.h"
226081Sphk
236081Sphk#define CTM_STATUS	".ctm_status"
246081Sphk
256081Sphkchar *piece_dir = NULL;		/* Where to store pieces of deltas. */
266081Sphkchar *delta_dir = NULL;		/* Where to store completed deltas. */
276081Sphkchar *base_dir = NULL;		/* The tree to apply deltas to. */
286081Sphkint delete_after = 0;		/* Delete deltas after ctm applies them. */
296081Sphk
306081Sphkvoid apply_complete(void);
316081Sphkint read_piece(char *input_file);
326081Sphkint combine_if_complete(char *delta, int pce, int npieces);
336081Sphkint decode_line(char *line, char *out_buf);
346081Sphk
356081Sphk/*
366081Sphk * If given a '-p' flag, read encoded delta pieces from stdin or file
376081Sphk * arguments, decode them and assemble any completed deltas.  If given
386081Sphk * a '-b' flag, pass any completed deltas to 'ctm' for application to
396081Sphk * the source tree.  The '-d' flag is mandatory, but either of '-p' or
406081Sphk * '-b' can be omitted.  If given the '-l' flag, notes and errors will
416081Sphk * be timestamped and written to the given file.
426081Sphk *
436081Sphk * Exit status is 0 for success or 1 for indigestible input.  That is,
446081Sphk * 0 means the encode input pieces were decoded and stored, and 1 means
456081Sphk * some input was discarded.  If a delta fails to apply, this won't be
466081Sphk * reflected in the exit status.  In this case, the delta is left in
476081Sphk * 'deltadir'.
486081Sphk */
496290Sphkint
506081Sphkmain(int argc, char **argv)
516081Sphk    {
526081Sphk    char *log_file = NULL;
536081Sphk    int status = 0;
546081Sphk
556081Sphk    err_prog_name(argv[0]);
566081Sphk
576081Sphk    OPTIONS("[-D] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
586081Sphk	FLAG('D', delete_after)
596081Sphk	STRING('p', piece_dir)
606081Sphk	STRING('d', delta_dir)
616081Sphk	STRING('b', base_dir)
626081Sphk	STRING('l', log_file)
636081Sphk    ENDOPTS
646081Sphk
656290Sphk    if (delta_dir == NULL)
666081Sphk	usage();
676081Sphk
686457Sphk    if (piece_dir == NULL && (base_dir == NULL || argc > 1))
696290Sphk	usage();
706290Sphk
716081Sphk    if (log_file != NULL)
726081Sphk	err_set_log(log_file);
736081Sphk
746081Sphk    if (argc <= 1)
756081Sphk	{
766081Sphk	if (piece_dir != NULL)
776081Sphk	    status = read_piece(NULL);
786081Sphk	}
796081Sphk    else
806081Sphk	{
816081Sphk	while (*++argv != NULL)
826081Sphk	    status |= read_piece(*argv);
836081Sphk	}
846081Sphk
856081Sphk    if (base_dir != NULL)
866081Sphk	apply_complete();
876081Sphk
886081Sphk    return status;
896081Sphk    }
906081Sphk
916081Sphk
926081Sphk/*
936081Sphk * Construct the file name of a piece of a delta.
946081Sphk */
956081Sphk#define mk_piece_name(fn,d,p,n)	\
966081Sphk    sprintf((fn), "%s/%s+%d-%d", piece_dir, (d), (p), (n))
976081Sphk
986081Sphk/*
996081Sphk * Construct the file name of an assembled delta.
1006081Sphk */
1016081Sphk#define mk_delta_name(fn,d)	\
1026081Sphk    sprintf((fn), "%s/%s", delta_dir, (d))
1036081Sphk
1046081Sphk/*
1056081Sphk * If the next required delta is now present, let ctm lunch on it and any
1066081Sphk * contiguous deltas.
1076081Sphk */
1086081Sphkvoid
1096081Sphkapply_complete()
1106081Sphk    {
1116081Sphk    int i, dn;
1126081Sphk    FILE *fp, *ctm;
1136081Sphk    struct stat sb;
1146081Sphk    char class[20];
1156081Sphk    char delta[30];
1166081Sphk    char fname[1000];
1176081Sphk    char buf[2000];
1186081Sphk    char junk[2];
1196081Sphk    char here[1000];
1206081Sphk
1216081Sphk    sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
1226081Sphk    if ((fp = fopen(fname, "r")) == NULL)
1236081Sphk	return;
1246081Sphk
1256081Sphk    i = fscanf(fp, "%s %d %c", class, &dn, junk);
1266081Sphk    fclose(fp);
1276081Sphk    if (i != 2)
1286081Sphk	return;
1296081Sphk
1306081Sphk    /*
1316081Sphk     * We might need to convert the delta filename to an absolute pathname.
1326081Sphk     */
1336081Sphk    here[0] = '\0';
1346081Sphk    if (delta_dir[0] != '/')
1356081Sphk	{
1366081Sphk	getcwd(here, sizeof(here)-1);
1376081Sphk	i = strlen(here) - 1;
1386081Sphk	if (i >= 0 && here[i] != '/')
1396081Sphk	    {
1406081Sphk	    here[++i] = '/';
1416081Sphk	    here[++i] = '\0';
1426081Sphk	    }
1436081Sphk	}
1446081Sphk
1456081Sphk    /*
1466081Sphk     * Keep applying deltas until we run out or something bad happens.
1476081Sphk     */
1486081Sphk    for (;;)
1496081Sphk	{
1506081Sphk	sprintf(delta, "%s.%04d.gz", class, ++dn);
1516081Sphk	mk_delta_name(fname, delta);
1526081Sphk
1536081Sphk	if (stat(fname, &sb) < 0)
1546081Sphk	    return;
1556081Sphk
1566081Sphk	sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname);
1576081Sphk	if ((ctm = popen(buf, "r")) == NULL)
1586081Sphk	    {
1596081Sphk	    err("ctm failed to apply %s", delta);
1606081Sphk	    return;
1616081Sphk	    }
1626081Sphk
1636081Sphk	while (fgets(buf, sizeof(buf), ctm) != NULL)
1646081Sphk	    {
1656081Sphk	    i = strlen(buf) - 1;
1666081Sphk	    if (i >= 0 && buf[i] == '\n')
1676081Sphk		buf[i] = '\0';
1686081Sphk	    err("ctm: %s", buf);
1696081Sphk	    }
1706081Sphk
1716081Sphk	if (pclose(ctm) != 0)
1726081Sphk	    {
1736081Sphk	    err("ctm failed to apply %s", delta);
1746081Sphk	    return;
1756081Sphk	    }
1766081Sphk
1776081Sphk	if (delete_after)
1786081Sphk	    unlink(fname);
1796081Sphk
1806081Sphk	err("%s applied%s", delta, delete_after ? " and deleted" : "");
1816081Sphk	}
1826081Sphk    }
1836081Sphk
1846081Sphk
1856081Sphk/*
1866081Sphk * This cheap plastic checksum effectively rotates our checksum-so-far
1876081Sphk * left one, then adds the character.  We only want 16 bits of it, and
1886081Sphk * don't care what happens to the rest.  It ain't much, but it's small.
1896081Sphk */
1906081Sphk#define add_ck(sum,x)	\
1916081Sphk    ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
1926081Sphk
1936081Sphk
1946081Sphk/*
1956081Sphk * Decode the data between BEGIN and END, and stash it in the staging area.
1966081Sphk * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
1976081Sphk * If we have all pieces of a delta, combine them.  Returns 0 on success,
1986081Sphk * and 1 for any sort of failure.
1996081Sphk */
2006081Sphkint
2016081Sphkread_piece(char *input_file)
2026081Sphk    {
2036081Sphk    int status = 0;
2046290Sphk    FILE *ifp, *ofp = 0;
2056081Sphk    int decoding = 0;
2066081Sphk    int line_no = 0;
2076081Sphk    int i, n;
2086081Sphk    int pce, npieces;
2096081Sphk    unsigned claimed_cksum;
2106290Sphk    unsigned short cksum = 0;
2116081Sphk    char out_buf[200];
2126081Sphk    char line[200];
2136081Sphk    char delta[30];
2146081Sphk    char pname[1000];
2156081Sphk    char junk[2];
2166081Sphk
2176081Sphk    ifp = stdin;
2186081Sphk    if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
2196081Sphk	{
2206081Sphk	err("cannot open '%s' for reading", input_file);
2216081Sphk	return 1;
2226081Sphk	}
2236081Sphk
2246081Sphk    while (fgets(line, sizeof(line), ifp) != NULL)
2256081Sphk	{
2266081Sphk	line_no++;
2276081Sphk
2286081Sphk	/*
2296081Sphk	 * Look for the beginning of an encoded piece.
2306081Sphk	 */
2316081Sphk	if (!decoding)
2326081Sphk	    {
2336290Sphk	    char *s;
2346081Sphk
2356457Sphk	    if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c",
2366290Sphk		    delta, &pce, &npieces, junk) != 3)
2376290Sphk		continue;
2386081Sphk
2396290Sphk	    while ((s = strchr(delta, '/')) != NULL)
2406290Sphk		*s = '_';
2416081Sphk
2426290Sphk	    mk_piece_name(pname, delta, pce, npieces);
2436290Sphk	    if ((ofp = fopen(pname, "w")) == NULL)
2446290Sphk		{
2456290Sphk		err("cannot open '%s' for writing", pname);
2466290Sphk		status++;
2476290Sphk		continue;
2486081Sphk		}
2496290Sphk
2506290Sphk	    cksum = 0xffff;
2516290Sphk	    decoding++;
2526081Sphk	    continue;
2536081Sphk	    }
2546081Sphk
2556081Sphk	/*
2566081Sphk	 * We are decoding.  Stop if we see the end flag.
2576081Sphk	 */
2586081Sphk	if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
2596081Sphk	    {
2606081Sphk	    int e;
2616081Sphk
2626081Sphk	    decoding = 0;
2636081Sphk
2646081Sphk	    fflush(ofp);
2656081Sphk	    e = ferror(ofp);
2666081Sphk	    fclose(ofp);
2676081Sphk
2686081Sphk	    if (e)
2696081Sphk		err("error writing %s", pname);
2706081Sphk
2716081Sphk	    if (cksum != claimed_cksum)
2726081Sphk		err("checksum: read %d, calculated %d", claimed_cksum, cksum);
2736081Sphk
2746081Sphk	    if (e || cksum != claimed_cksum)
2756081Sphk		{
2766081Sphk		err("%s %d/%d discarded", delta, pce, npieces);
2776081Sphk		unlink(pname);
2786081Sphk		status++;
2796081Sphk		continue;
2806081Sphk		}
2816081Sphk
2826081Sphk	    err("%s %d/%d stored", delta, pce, npieces);
2836081Sphk
2846081Sphk	    if (!combine_if_complete(delta, pce, npieces))
2856081Sphk		status++;
2866081Sphk	    continue;
2876081Sphk	    }
2886081Sphk
2896081Sphk	/*
2906081Sphk	 * Must be a line of encoded data.  Decode it, sum it, and save it.
2916081Sphk	 */
2926081Sphk	n = decode_line(line, out_buf);
2936081Sphk	if (n < 0)
2946081Sphk	    {
2956081Sphk	    err("line %d: illegal character: '%c'", line_no, line[-n]);
2966081Sphk	    err("%s %d/%d discarded", delta, pce, npieces);
2976081Sphk
2986081Sphk	    fclose(ofp);
2996081Sphk	    unlink(pname);
3006081Sphk
3016081Sphk	    status++;
3026081Sphk	    decoding = 0;
3036081Sphk	    continue;
3046081Sphk	    }
3056081Sphk
3066081Sphk	for (i = 0; i < n; i++)
3076081Sphk	    add_ck(cksum, out_buf[i]);
3086081Sphk
3096081Sphk	fwrite(out_buf, sizeof(char), n, ofp);
3106081Sphk	}
3116081Sphk
3126081Sphk    if (decoding)
3136081Sphk	{
3146081Sphk	err("truncated file");
3156081Sphk	err("%s %d/%d discarded", delta, pce, npieces);
3166081Sphk
3176081Sphk	fclose(ofp);
3186081Sphk	unlink(pname);
3196081Sphk
3206081Sphk	status++;
3216081Sphk	}
3226081Sphk
3236081Sphk    if (ferror(ifp))
3246081Sphk	{
3256081Sphk	err("error reading %s", input_file == NULL ? "stdin" : input_file);
3266081Sphk	status++;
3276081Sphk	}
3286081Sphk
3296081Sphk    if (input_file != NULL)
3306081Sphk	fclose(ifp);
3316081Sphk
3326081Sphk    return (status != 0);
3336081Sphk    }
3346081Sphk
3356081Sphk
3366081Sphk/*
3376081Sphk * Put the pieces together to form a delta, if they are all present.
3386081Sphk * Returns 1 on success (even if we didn't do anything), and 0 on failure.
3396081Sphk */
3406081Sphkint
3416081Sphkcombine_if_complete(char *delta, int pce, int npieces)
3426081Sphk    {
3436081Sphk    int i;
3446081Sphk    FILE *dfp, *pfp;
3456081Sphk    int c;
3466081Sphk    struct stat sb;
3476081Sphk    char pname[1000];
3486081Sphk    char dname[1000];
3496081Sphk
3506081Sphk    /*
3516081Sphk     * All here?
3526081Sphk     */
3536081Sphk    for (i = 1; i <= npieces; i++)
3546081Sphk	{
3556081Sphk	if (i == pce)
3566081Sphk	    continue;
3576081Sphk	mk_piece_name(pname, delta, i, npieces);
3586081Sphk	if (stat(pname, &sb) < 0)
3596081Sphk	    return 1;
3606081Sphk	}
3616081Sphk
3626081Sphk    mk_delta_name(dname, delta);
3636081Sphk
3646081Sphk    /*
3656081Sphk     * We can probably just rename() it in to place if it is a small delta.
3666081Sphk     */
3676081Sphk    if (npieces == 1)
3686081Sphk	{
3696081Sphk	mk_piece_name(pname, delta, 1, 1);
3706081Sphk	if (rename(pname, dname) == 0)
3716081Sphk	    {
3726081Sphk	    err("%s complete", delta);
3736081Sphk	    return 1;
3746081Sphk	    }
3756081Sphk	}
3766081Sphk
3776081Sphk    if ((dfp = fopen(dname, "w")) == NULL)
3786081Sphk	{
3796081Sphk	err("cannot open '%s' for writing", dname);
3806081Sphk	return 0;
3816081Sphk	}
3826081Sphk
3836081Sphk    /*
3846081Sphk     * Ok, the hard way.  Reconstruct the delta by reading each piece in order.
3856081Sphk     */
3866081Sphk    for (i = 1; i <= npieces; i++)
3876081Sphk	{
3886081Sphk	mk_piece_name(pname, delta, i, npieces);
3896081Sphk	if ((pfp = fopen(pname, "r")) == NULL)
3906081Sphk	    {
3916081Sphk	    err("cannot open '%s' for reading", pname);
3926081Sphk	    fclose(dfp);
3936081Sphk	    unlink(dname);
3946081Sphk	    return 0;
3956081Sphk	    }
3966081Sphk	while ((c = getc(pfp)) != EOF)
3976081Sphk	    putc(c, dfp);
3986081Sphk	fclose(pfp);
3996081Sphk	}
4006081Sphk    fflush(dfp);
4016081Sphk    if (ferror(dfp))
4026081Sphk	{
4036081Sphk	err("error writing '%s'", dname);
4046081Sphk	fclose(dfp);
4056081Sphk	unlink(dname);
4066081Sphk	return 0;
4076081Sphk	}
4086081Sphk    fclose(dfp);
4096081Sphk
4106081Sphk    /*
4116081Sphk     * Throw the pieces away.
4126081Sphk     */
4136081Sphk    for (i = 1; i <= npieces; i++)
4146081Sphk	{
4156081Sphk	mk_piece_name(pname, delta, i, npieces);
4166081Sphk	unlink(pname);
4176081Sphk	}
4186081Sphk
4196081Sphk    err("%s complete", delta);
4206081Sphk    return 1;
4216081Sphk    }
4226081Sphk
4236081Sphk
4246081Sphk/*
4256081Sphk * MIME BASE64 decode table.
4266081Sphk */
4276081Sphkstatic unsigned char from_b64[0x80] =
4286081Sphk    {
4296081Sphk    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4306081Sphk    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4316081Sphk    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4326081Sphk    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4336081Sphk    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4346081Sphk    0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
4356081Sphk    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
4366081Sphk    0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4376081Sphk    0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
4386081Sphk    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
4396081Sphk    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
4406081Sphk    0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
4416081Sphk    0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
4426081Sphk    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
4436081Sphk    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
4446081Sphk    0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
4456081Sphk    };
4466081Sphk
4476081Sphk
4486081Sphk/*
4496081Sphk * Decode a line of ASCII into binary.  Returns the number of bytes in
4506081Sphk * the output buffer, or < 0 on indigestable input.  Error output is
4516081Sphk * the negative of the index of the inedible character.
4526081Sphk */
4536081Sphkint
4546081Sphkdecode_line(char *line, char *out_buf)
4556081Sphk    {
4566081Sphk    unsigned char *ip = (unsigned char *)line;
4576081Sphk    unsigned char *op = (unsigned char *)out_buf;
4586081Sphk    unsigned long bits;
4596081Sphk    unsigned x;
4606081Sphk
4616081Sphk    for (;;)
4626081Sphk	{
4636081Sphk	if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
4646081Sphk	    break;
4656081Sphk	bits = x << 18;
4666081Sphk	ip++;
4676081Sphk	if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
4686081Sphk	    {
4696081Sphk	    bits |= x << 12;
4706081Sphk	    *op++ = bits >> 16;
4716081Sphk	    ip++;
4726081Sphk	    if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
4736081Sphk		{
4746081Sphk		bits |= x << 6;
4756081Sphk		*op++ = bits >> 8;
4766081Sphk		ip++;
4776081Sphk		if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
4786081Sphk		    {
4796081Sphk		    bits |= x;
4806081Sphk		    *op++ = bits;
4816081Sphk		    ip++;
4826081Sphk		    }
4836081Sphk		}
4846081Sphk	    }
4856081Sphk	}
4866081Sphk
4876081Sphk    if (*ip == '\0' || *ip == '\n')
4886081Sphk	return op - (unsigned char *)out_buf;
4896081Sphk    else
4906081Sphk	return -(ip - (unsigned char *)line);
4916081Sphk    }
492