ctm_rmail.c revision 6457
1/* 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. 5 * 6 * Author: Stephen McKay 7 * 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. 11 */ 12 13#include <stdio.h> 14#include <stdlib.h> 15#include <string.h> 16#include <errno.h> 17#include <unistd.h> 18#include <sys/types.h> 19#include <sys/stat.h> 20#include "error.h" 21#include "options.h" 22 23#define CTM_STATUS ".ctm_status" 24 25char *piece_dir = NULL; /* Where to store pieces of deltas. */ 26char *delta_dir = NULL; /* Where to store completed deltas. */ 27char *base_dir = NULL; /* The tree to apply deltas to. */ 28int delete_after = 0; /* Delete deltas after ctm applies them. */ 29 30void apply_complete(void); 31int read_piece(char *input_file); 32int combine_if_complete(char *delta, int pce, int npieces); 33int decode_line(char *line, char *out_buf); 34 35/* 36 * If given a '-p' flag, read encoded delta pieces from stdin or file 37 * arguments, decode them and assemble any completed deltas. If given 38 * a '-b' flag, pass any completed deltas to 'ctm' for application to 39 * the source tree. The '-d' flag is mandatory, but either of '-p' or 40 * '-b' can be omitted. If given the '-l' flag, notes and errors will 41 * be timestamped and written to the given file. 42 * 43 * Exit status is 0 for success or 1 for indigestible input. That is, 44 * 0 means the encode input pieces were decoded and stored, and 1 means 45 * some input was discarded. If a delta fails to apply, this won't be 46 * reflected in the exit status. In this case, the delta is left in 47 * 'deltadir'. 48 */ 49int 50main(int argc, char **argv) 51 { 52 char *log_file = NULL; 53 int status = 0; 54 55 err_prog_name(argv[0]); 56 57 OPTIONS("[-D] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]") 58 FLAG('D', delete_after) 59 STRING('p', piece_dir) 60 STRING('d', delta_dir) 61 STRING('b', base_dir) 62 STRING('l', log_file) 63 ENDOPTS 64 65 if (delta_dir == NULL) 66 usage(); 67 68 if (piece_dir == NULL && (base_dir == NULL || argc > 1)) 69 usage(); 70 71 if (log_file != NULL) 72 err_set_log(log_file); 73 74 if (argc <= 1) 75 { 76 if (piece_dir != NULL) 77 status = read_piece(NULL); 78 } 79 else 80 { 81 while (*++argv != NULL) 82 status |= read_piece(*argv); 83 } 84 85 if (base_dir != NULL) 86 apply_complete(); 87 88 return status; 89 } 90 91 92/* 93 * Construct the file name of a piece of a delta. 94 */ 95#define mk_piece_name(fn,d,p,n) \ 96 sprintf((fn), "%s/%s+%d-%d", piece_dir, (d), (p), (n)) 97 98/* 99 * Construct the file name of an assembled delta. 100 */ 101#define mk_delta_name(fn,d) \ 102 sprintf((fn), "%s/%s", delta_dir, (d)) 103 104/* 105 * If the next required delta is now present, let ctm lunch on it and any 106 * contiguous deltas. 107 */ 108void 109apply_complete() 110 { 111 int i, dn; 112 FILE *fp, *ctm; 113 struct stat sb; 114 char class[20]; 115 char delta[30]; 116 char fname[1000]; 117 char buf[2000]; 118 char junk[2]; 119 char here[1000]; 120 121 sprintf(fname, "%s/%s", base_dir, CTM_STATUS); 122 if ((fp = fopen(fname, "r")) == NULL) 123 return; 124 125 i = fscanf(fp, "%s %d %c", class, &dn, junk); 126 fclose(fp); 127 if (i != 2) 128 return; 129 130 /* 131 * We might need to convert the delta filename to an absolute pathname. 132 */ 133 here[0] = '\0'; 134 if (delta_dir[0] != '/') 135 { 136 getcwd(here, sizeof(here)-1); 137 i = strlen(here) - 1; 138 if (i >= 0 && here[i] != '/') 139 { 140 here[++i] = '/'; 141 here[++i] = '\0'; 142 } 143 } 144 145 /* 146 * Keep applying deltas until we run out or something bad happens. 147 */ 148 for (;;) 149 { 150 sprintf(delta, "%s.%04d.gz", class, ++dn); 151 mk_delta_name(fname, delta); 152 153 if (stat(fname, &sb) < 0) 154 return; 155 156 sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname); 157 if ((ctm = popen(buf, "r")) == NULL) 158 { 159 err("ctm failed to apply %s", delta); 160 return; 161 } 162 163 while (fgets(buf, sizeof(buf), ctm) != NULL) 164 { 165 i = strlen(buf) - 1; 166 if (i >= 0 && buf[i] == '\n') 167 buf[i] = '\0'; 168 err("ctm: %s", buf); 169 } 170 171 if (pclose(ctm) != 0) 172 { 173 err("ctm failed to apply %s", delta); 174 return; 175 } 176 177 if (delete_after) 178 unlink(fname); 179 180 err("%s applied%s", delta, delete_after ? " and deleted" : ""); 181 } 182 } 183 184 185/* 186 * This cheap plastic checksum effectively rotates our checksum-so-far 187 * left one, then adds the character. We only want 16 bits of it, and 188 * don't care what happens to the rest. It ain't much, but it's small. 189 */ 190#define add_ck(sum,x) \ 191 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 192 193 194/* 195 * Decode the data between BEGIN and END, and stash it in the staging area. 196 * Multiple pieces can be present in a single file, bracketed by BEGIN/END. 197 * If we have all pieces of a delta, combine them. Returns 0 on success, 198 * and 1 for any sort of failure. 199 */ 200int 201read_piece(char *input_file) 202 { 203 int status = 0; 204 FILE *ifp, *ofp = 0; 205 int decoding = 0; 206 int line_no = 0; 207 int i, n; 208 int pce, npieces; 209 unsigned claimed_cksum; 210 unsigned short cksum = 0; 211 char out_buf[200]; 212 char line[200]; 213 char delta[30]; 214 char pname[1000]; 215 char junk[2]; 216 217 ifp = stdin; 218 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL) 219 { 220 err("cannot open '%s' for reading", input_file); 221 return 1; 222 } 223 224 while (fgets(line, sizeof(line), ifp) != NULL) 225 { 226 line_no++; 227 228 /* 229 * Look for the beginning of an encoded piece. 230 */ 231 if (!decoding) 232 { 233 char *s; 234 235 if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c", 236 delta, &pce, &npieces, junk) != 3) 237 continue; 238 239 while ((s = strchr(delta, '/')) != NULL) 240 *s = '_'; 241 242 mk_piece_name(pname, delta, pce, npieces); 243 if ((ofp = fopen(pname, "w")) == NULL) 244 { 245 err("cannot open '%s' for writing", pname); 246 status++; 247 continue; 248 } 249 250 cksum = 0xffff; 251 decoding++; 252 continue; 253 } 254 255 /* 256 * We are decoding. Stop if we see the end flag. 257 */ 258 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1) 259 { 260 int e; 261 262 decoding = 0; 263 264 fflush(ofp); 265 e = ferror(ofp); 266 fclose(ofp); 267 268 if (e) 269 err("error writing %s", pname); 270 271 if (cksum != claimed_cksum) 272 err("checksum: read %d, calculated %d", claimed_cksum, cksum); 273 274 if (e || cksum != claimed_cksum) 275 { 276 err("%s %d/%d discarded", delta, pce, npieces); 277 unlink(pname); 278 status++; 279 continue; 280 } 281 282 err("%s %d/%d stored", delta, pce, npieces); 283 284 if (!combine_if_complete(delta, pce, npieces)) 285 status++; 286 continue; 287 } 288 289 /* 290 * Must be a line of encoded data. Decode it, sum it, and save it. 291 */ 292 n = decode_line(line, out_buf); 293 if (n < 0) 294 { 295 err("line %d: illegal character: '%c'", line_no, line[-n]); 296 err("%s %d/%d discarded", delta, pce, npieces); 297 298 fclose(ofp); 299 unlink(pname); 300 301 status++; 302 decoding = 0; 303 continue; 304 } 305 306 for (i = 0; i < n; i++) 307 add_ck(cksum, out_buf[i]); 308 309 fwrite(out_buf, sizeof(char), n, ofp); 310 } 311 312 if (decoding) 313 { 314 err("truncated file"); 315 err("%s %d/%d discarded", delta, pce, npieces); 316 317 fclose(ofp); 318 unlink(pname); 319 320 status++; 321 } 322 323 if (ferror(ifp)) 324 { 325 err("error reading %s", input_file == NULL ? "stdin" : input_file); 326 status++; 327 } 328 329 if (input_file != NULL) 330 fclose(ifp); 331 332 return (status != 0); 333 } 334 335 336/* 337 * Put the pieces together to form a delta, if they are all present. 338 * Returns 1 on success (even if we didn't do anything), and 0 on failure. 339 */ 340int 341combine_if_complete(char *delta, int pce, int npieces) 342 { 343 int i; 344 FILE *dfp, *pfp; 345 int c; 346 struct stat sb; 347 char pname[1000]; 348 char dname[1000]; 349 350 /* 351 * All here? 352 */ 353 for (i = 1; i <= npieces; i++) 354 { 355 if (i == pce) 356 continue; 357 mk_piece_name(pname, delta, i, npieces); 358 if (stat(pname, &sb) < 0) 359 return 1; 360 } 361 362 mk_delta_name(dname, delta); 363 364 /* 365 * We can probably just rename() it in to place if it is a small delta. 366 */ 367 if (npieces == 1) 368 { 369 mk_piece_name(pname, delta, 1, 1); 370 if (rename(pname, dname) == 0) 371 { 372 err("%s complete", delta); 373 return 1; 374 } 375 } 376 377 if ((dfp = fopen(dname, "w")) == NULL) 378 { 379 err("cannot open '%s' for writing", dname); 380 return 0; 381 } 382 383 /* 384 * Ok, the hard way. Reconstruct the delta by reading each piece in order. 385 */ 386 for (i = 1; i <= npieces; i++) 387 { 388 mk_piece_name(pname, delta, i, npieces); 389 if ((pfp = fopen(pname, "r")) == NULL) 390 { 391 err("cannot open '%s' for reading", pname); 392 fclose(dfp); 393 unlink(dname); 394 return 0; 395 } 396 while ((c = getc(pfp)) != EOF) 397 putc(c, dfp); 398 fclose(pfp); 399 } 400 fflush(dfp); 401 if (ferror(dfp)) 402 { 403 err("error writing '%s'", dname); 404 fclose(dfp); 405 unlink(dname); 406 return 0; 407 } 408 fclose(dfp); 409 410 /* 411 * Throw the pieces away. 412 */ 413 for (i = 1; i <= npieces; i++) 414 { 415 mk_piece_name(pname, delta, i, npieces); 416 unlink(pname); 417 } 418 419 err("%s complete", delta); 420 return 1; 421 } 422 423 424/* 425 * MIME BASE64 decode table. 426 */ 427static unsigned char from_b64[0x80] = 428 { 429 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 430 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 431 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 432 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 433 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 434 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, 435 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 436 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 437 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 438 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 439 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 440 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 441 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 442 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 443 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 444 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff 445 }; 446 447 448/* 449 * Decode a line of ASCII into binary. Returns the number of bytes in 450 * the output buffer, or < 0 on indigestable input. Error output is 451 * the negative of the index of the inedible character. 452 */ 453int 454decode_line(char *line, char *out_buf) 455 { 456 unsigned char *ip = (unsigned char *)line; 457 unsigned char *op = (unsigned char *)out_buf; 458 unsigned long bits; 459 unsigned x; 460 461 for (;;) 462 { 463 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40) 464 break; 465 bits = x << 18; 466 ip++; 467 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40) 468 { 469 bits |= x << 12; 470 *op++ = bits >> 16; 471 ip++; 472 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40) 473 { 474 bits |= x << 6; 475 *op++ = bits >> 8; 476 ip++; 477 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40) 478 { 479 bits |= x; 480 *op++ = bits; 481 ip++; 482 } 483 } 484 } 485 } 486 487 if (*ip == '\0' || *ip == '\n') 488 return op - (unsigned char *)out_buf; 489 else 490 return -(ip - (unsigned char *)line); 491 } 492