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