ctm_rmail.c revision 6457
180016Sobrien/* 280016Sobrien * Accept one (or more) ASCII encoded chunks that together make a compressed 380016Sobrien * CTM delta. Decode them and reconstruct the deltas. Any completed 480016Sobrien * deltas may be passed to ctm for unpacking. 580016Sobrien * 680016Sobrien * Author: Stephen McKay 7218822Sdim * 8218822Sdim * NOTICE: This is free software. I hope you get some use from this program. 9218822Sdim * In return you should think about all the nice people who give away software. 10218822Sdim * Maybe you should write some free software too. 11218822Sdim */ 12218822Sdim 1380016Sobrien#include <stdio.h> 1480016Sobrien#include <stdlib.h> 1580016Sobrien#include <string.h> 16130561Sobrien#include <errno.h> 1780016Sobrien#include <unistd.h> 1880016Sobrien#include <sys/types.h> 1980016Sobrien#include <sys/stat.h> 2080016Sobrien#include "error.h" 2180016Sobrien#include "options.h" 2280016Sobrien 2380016Sobrien#define CTM_STATUS ".ctm_status" 2480016Sobrien 2580016Sobrienchar *piece_dir = NULL; /* Where to store pieces of deltas. */ 2680016Sobrienchar *delta_dir = NULL; /* Where to store completed deltas. */ 2780016Sobrienchar *base_dir = NULL; /* The tree to apply deltas to. */ 28218822Sdimint delete_after = 0; /* Delete deltas after ctm applies them. */ 2989857Sobrien 3080016Sobrienvoid apply_complete(void); 3180016Sobrienint read_piece(char *input_file); 3280016Sobrienint combine_if_complete(char *delta, int pce, int npieces); 33130561Sobrienint decode_line(char *line, char *out_buf); 34130561Sobrien 3589857Sobrien/* 3689857Sobrien * If given a '-p' flag, read encoded delta pieces from stdin or file 3789857Sobrien * arguments, decode them and assemble any completed deltas. If given 3880016Sobrien * a '-b' flag, pass any completed deltas to 'ctm' for application to 39218822Sdim * the source tree. The '-d' flag is mandatory, but either of '-p' or 40218822Sdim * '-b' can be omitted. If given the '-l' flag, notes and errors will 4180016Sobrien * be timestamped and written to the given file. 42218822Sdim * 43218822Sdim * Exit status is 0 for success or 1 for indigestible input. That is, 4480016Sobrien * 0 means the encode input pieces were decoded and stored, and 1 means 45218822Sdim * some input was discarded. If a delta fails to apply, this won't be 4680016Sobrien * reflected in the exit status. In this case, the delta is left in 4780016Sobrien * 'deltadir'. 4880016Sobrien */ 4980016Sobrienint 50218822Sdimmain(int argc, char **argv) 5180016Sobrien { 5280016Sobrien char *log_file = NULL; 5380016Sobrien int status = 0; 54218822Sdim 55218822Sdim err_prog_name(argv[0]); 56218822Sdim 57218822Sdim OPTIONS("[-D] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]") 58218822Sdim FLAG('D', delete_after) 5980016Sobrien STRING('p', piece_dir) 60218822Sdim STRING('d', delta_dir) 61218822Sdim STRING('b', base_dir) 6280016Sobrien STRING('l', log_file) 6380016Sobrien ENDOPTS 6480016Sobrien 6580016Sobrien if (delta_dir == NULL) 6689857Sobrien usage(); 6780016Sobrien 6880016Sobrien if (piece_dir == NULL && (base_dir == NULL || argc > 1)) 6980016Sobrien usage(); 7080016Sobrien 7180016Sobrien if (log_file != NULL) 7280016Sobrien err_set_log(log_file); 7389857Sobrien 7489857Sobrien if (argc <= 1) 7589857Sobrien { 7689857Sobrien if (piece_dir != NULL) 7780016Sobrien status = read_piece(NULL); 7880016Sobrien } 7980016Sobrien else 8080016Sobrien { 81218822Sdim while (*++argv != NULL) 8280016Sobrien status |= read_piece(*argv); 8380016Sobrien } 8480016Sobrien 8580016Sobrien if (base_dir != NULL) 8680016Sobrien apply_complete(); 8780016Sobrien 8880016Sobrien return status; 8980016Sobrien } 9080016Sobrien 9180016Sobrien 9280016Sobrien/* 9380016Sobrien * Construct the file name of a piece of a delta. 9480016Sobrien */ 9580016Sobrien#define mk_piece_name(fn,d,p,n) \ 9680016Sobrien sprintf((fn), "%s/%s+%d-%d", piece_dir, (d), (p), (n)) 9780016Sobrien 9880016Sobrien/* 9980016Sobrien * Construct the file name of an assembled delta. 10080016Sobrien */ 101218822Sdim#define mk_delta_name(fn,d) \ 102218822Sdim sprintf((fn), "%s/%s", delta_dir, (d)) 103218822Sdim 104218822Sdim/* 10580016Sobrien * If the next required delta is now present, let ctm lunch on it and any 106218822Sdim * contiguous deltas. 107218822Sdim */ 108218822Sdimvoid 109218822Sdimapply_complete() 110218822Sdim { 111218822Sdim int i, dn; 112218822Sdim FILE *fp, *ctm; 11380016Sobrien struct stat sb; 11480016Sobrien char class[20]; 11580016Sobrien char delta[30]; 11680016Sobrien char fname[1000]; 11780016Sobrien char buf[2000]; 11880016Sobrien char junk[2]; 11980016Sobrien char here[1000]; 12080016Sobrien 12180016Sobrien sprintf(fname, "%s/%s", base_dir, CTM_STATUS); 12280016Sobrien 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