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