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