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