ckdist.c revision 21918
1/*
2 * Copyright (c) 1997 Robert Nordier
3 * All rights reserved.
4 *
5 * $Id: ckdist.c,v 1.6 1997/01/20 16:47:06 rnordier Exp $
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in
14 *    the documentation and/or other materials provided with the
15 *    distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <fcntl.h>
33#include <unistd.h>
34#include <fts.h>
35#include <err.h>
36#include <md5.h>
37
38#include <errno.h>
39#include <stdlib.h>
40#include <string.h>
41#include <stdio.h>
42
43extern int crc(int fd, u_long * cval, u_long * clen);
44
45#define DISTMD5     1		/* MD5 format */
46#define DISTINF     2		/* .inf format */
47#define DISTTYPES   2		/* types supported */
48
49#define E_UNKNOWN   1		/* Unknown format */
50#define E_BADMD5    2		/* Invalid MD5 format */
51#define E_BADINF    3		/* Invalid .inf format */
52#define E_NAME      4		/* Can't derive component name */
53#define E_LENGTH    5		/* Length mismatch */
54#define E_CHKSUM    6		/* Checksum mismatch */
55#define E_ERRNO     7		/* sys_errlist[errno] */
56
57#define isfatal(err)   ((err) && (err) <= E_NAME)
58
59#define NAMESIZE  256           /* filename buffer size */
60#define MDSUMLEN   32           /* length of MD5 message digest */
61
62#define isstdin(path)  ((path)[0] == '-' && !(path)[1])
63
64static const char *opt_dir;	/* where to look for components */
65static const char *opt_name;	/* name for accessing components */
66static int opt_all;		/* report on all components */
67static int opt_ignore;		/* ignore missing components */
68static int opt_recurse;		/* search directories recursively */
69static int opt_silent;		/* silent about inaccessible files */
70static int opt_type;		/* dist type: md5 or inf */
71static int opt_exist;		/* just verify existence */
72
73static int ckdist(const char *path, int type);
74static int chkmd5(FILE * fp, const char *path);
75static int chkinf(FILE * fp, const char *path);
76static int report(const char *path, const char *name, int error);
77static const char *distname(const char *path, const char *name,
78	                    const char *ext);
79static char *stripath(const char *path);
80static int distfile(const char *path);
81static int disttype(const char *name);
82static int fail(const char *path, const char *msg);
83static void usage(void);
84
85int
86main(int argc, char *argv[])
87{
88    static char *arg[2];
89    struct stat sb;
90    FTS *ftsp;
91    FTSENT *f;
92    int rval, c, type;
93
94    while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1)
95	switch (c) {
96	case 'a':
97	    opt_all = 1;
98	    break;
99	case 'd':
100	    opt_dir = optarg;
101	    break;
102	case 'i':
103	    opt_ignore = 1;
104	    break;
105	case 'n':
106	    opt_name = optarg;
107	    break;
108	case 'r':
109	    opt_recurse = 1;
110	    break;
111	case 's':
112	    opt_silent = 1;
113	    break;
114	case 't':
115	    if ((opt_type = disttype(optarg)) == 0) {
116		warnx("illegal argument to -t option");
117		usage();
118	    }
119	    break;
120	case 'x':
121	    opt_exist = 1;
122	    break;
123	default:
124	    usage();
125	}
126    argc -= optind;
127    argv += optind;
128    if (argc < 1)
129	usage();
130    if (opt_dir) {
131	if (stat(opt_dir, &sb))
132	    err(2, opt_dir);
133	if (!S_ISDIR(sb.st_mode))
134	    errx(2, "%s: Not a directory", opt_dir);
135    }
136    rval = 0;
137    do {
138	if (isstdin(*argv))
139	    rval |= ckdist(*argv, opt_type);
140	else if (stat(*argv, &sb))
141	    rval |= fail(*argv, NULL);
142	else if (S_ISREG(sb.st_mode))
143	    rval |= ckdist(*argv, opt_type);
144	else {
145	    arg[0] = *argv;
146	    if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL)
147		err(2, "fts_open");
148	    while ((f = fts_read(ftsp)) != NULL)
149		switch (f->fts_info) {
150		case FTS_DC:
151		    rval = fail(f->fts_path, "Directory causes a cycle");
152		    break;
153		case FTS_DNR:
154		case FTS_ERR:
155		case FTS_NS:
156		    rval = fail(f->fts_path, sys_errlist[f->fts_errno]);
157		    break;
158		case FTS_D:
159		    if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL &&
160			fts_set(ftsp, f, FTS_SKIP))
161			err(2, "fts_set");
162		    break;
163		case FTS_F:
164		    if ((type = distfile(f->fts_name)) != 0 &&
165			(!opt_type || type == opt_type))
166			rval |= ckdist(f->fts_path, type);
167		    break;
168                default: ;
169		}
170	    if (errno)
171		err(2, "fts_read");
172	    if (fts_close(ftsp))
173		err(2, "fts_close");
174	}
175    } while (*++argv);
176    return rval;
177}
178
179static int
180ckdist(const char *path, int type)
181{
182    FILE *fp;
183    int rval, c;
184
185    if (isstdin(path)) {
186	path = "(stdin)";
187	fp = stdin;
188    } else if ((fp = fopen(path, "r")) == NULL)
189	return fail(path, NULL);
190    if (!type) {
191	if (fp != stdin)
192	    type = distfile(path);
193	if (!type)
194	    if ((c = fgetc(fp)) != EOF) {
195		type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0;
196		(void)ungetc(c, fp);
197	    }
198    }
199    switch (type) {
200    case DISTMD5:
201	rval = chkmd5(fp, path);
202	break;
203    case DISTINF:
204	rval = chkinf(fp, path);
205	break;
206    default:
207	rval = report(path, NULL, E_UNKNOWN);
208    }
209    if (ferror(fp))
210	warn(path);
211    if (fp != stdin && fclose(fp))
212	err(2, path);
213    return rval;
214}
215
216static int
217chkmd5(FILE * fp, const char *path)
218{
219    char buf[298];              /* "MD5 (NAMESIZE = MDSUMLEN" */
220    char name[NAMESIZE + 1];
221    char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
222    const char *dname;
223    char *s;
224    int rval, error, c, fd;
225    char ch;
226
227    rval = 0;
228    while (fgets(buf, sizeof(buf), fp)) {
229	dname = NULL;
230	error = 0;
231	if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
232			 &ch)) != 3 && (!feof(fp) || c != 2)) ||
233	    (c == 3 && ch != '\n') ||
234	    (s = strrchr(name, ')')) == NULL ||
235	    strlen(sum) != MDSUMLEN)
236	    error = E_BADMD5;
237	else {
238	    *s = 0;
239	    if ((dname = distname(path, name, NULL)) == NULL)
240		error = E_NAME;
241	    else if (opt_exist) {
242		if ((fd = open(dname, O_RDONLY)) == -1)
243		    error = E_ERRNO;
244		else if (close(fd))
245		    err(2, dname);
246	    } else if (!MD5File((char *)dname, chk))
247		error = E_ERRNO;
248	    else if (strcmp(chk, sum))
249		error = E_CHKSUM;
250	}
251	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
252	    continue;
253	if (error || opt_all)
254	    rval |= report(path, dname, error);
255	if (isfatal(error))
256	    break;
257    }
258    return rval;
259}
260
261static int
262chkinf(FILE * fp, const char *path)
263{
264    char buf[30];               /* "cksum.2 = 10 6" */
265    char ext[3];
266    struct stat sb;
267    const char *dname;
268    u_long sum, len, chk;
269    int rval, error, c, pieces, cnt, fd;
270    char ch;
271
272    rval = 0;
273    for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
274	fd = -1;
275	dname = NULL;
276	error = 0;
277	if (cnt == -1) {
278	    if ((c = sscanf(buf, "Pieces =  %d%c", &pieces, &ch)) != 2 ||
279		ch != '\n' || pieces < 1)
280		error = E_BADINF;
281	} else if (((c = sscanf(buf, "cksum.%2s = %lu %lu%c", ext, &sum,
282			        &len, &ch)) != 4 &&
283		    (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
284		   ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
285	    error = E_BADINF;
286	else if ((dname = distname(fp == stdin ? NULL : path, NULL,
287				    ext)) == NULL)
288	    error = E_NAME;
289	else if ((fd = open(dname, O_RDONLY)) == -1)
290	    error = E_ERRNO;
291	else if (fstat(fd, &sb))
292	    error = E_ERRNO;
293	else if (sb.st_size != (off_t)len)
294	    error = E_LENGTH;
295	else if (!opt_exist) {
296	    if (crc(fd, &chk, &len))
297		error = E_ERRNO;
298	    else if (chk != sum)
299		error = E_CHKSUM;
300	}
301	if (fd != -1 && close(fd))
302	    err(2, dname);
303	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
304	    continue;
305	if (error || (opt_all && cnt >= 0))
306	    rval |= report(path, dname, error);
307	if (isfatal(error))
308	    break;
309    }
310    return rval;
311}
312
313static int
314report(const char *path, const char *name, int error)
315{
316    if (name)
317	name = stripath(name);
318    switch (error) {
319    case E_UNKNOWN:
320	printf("%s: Unknown format\n", path);
321	break;
322    case E_BADMD5:
323	printf("%s: Invalid MD5 format\n", path);
324	break;
325    case E_BADINF:
326	printf("%s: Invalid .inf format\n", path);
327	break;
328    case E_NAME:
329	printf("%s: Can't derive component name\n", path);
330	break;
331    case E_LENGTH:
332	printf("%s: %s: Size mismatch\n", path, name);
333	break;
334    case E_CHKSUM:
335	printf("%s: %s: Checksum mismatch\n", path, name);
336	break;
337    case E_ERRNO:
338	printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
339	break;
340    default:
341	printf("%s: %s: OK\n", path, name);
342    }
343    return error != 0;
344}
345
346static const char *
347distname(const char *path, const char *name, const char *ext)
348{
349    static char buf[NAMESIZE];
350    size_t plen, nlen;
351    char *s;
352
353    if (opt_name)
354	name = opt_name;
355    else if (!name) {
356	if (!path)
357	    return NULL;
358	name = stripath(path);
359    }
360    nlen = strlen(name);
361    if (ext && nlen > 4 && name[nlen - 4] == '.' &&
362	disttype(name + nlen - 3) == DISTINF)
363	nlen -= 4;
364    if (opt_dir) {
365	path = opt_dir;
366	plen = strlen(path);
367    } else
368	plen = path && (s = strrchr(path, '/')) != NULL ?
369            (size_t)(s - path) : 0;
370    if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
371	return NULL;
372    s = buf;
373    if (plen) {
374	memcpy(s, path, plen);
375	s += plen;
376	*s++ = '/';
377    }
378    memcpy(s, name, nlen);
379    s += nlen;
380    if (ext) {
381	*s++ = '.';
382	memcpy(s, ext, 2);
383	s += 2;
384    }
385    *s = 0;
386    return buf;
387}
388
389static char *
390stripath(const char *path)
391{
392    const char *s;
393
394    return (char *)((s = strrchr(path, '/')) != NULL && s[1] ?
395		    s + 1 : path);
396}
397
398static int
399distfile(const char *path)
400{
401    const char *s;
402    int type;
403
404    if ((type = disttype(path)) == DISTMD5 ||
405	((s = strrchr(path, '.')) != NULL && s > path &&
406	 (type = disttype(s + 1)) != 0))
407	return type;
408    return 0;
409}
410
411static int
412disttype(const char *name)
413{
414    static const char dname[DISTTYPES][4] = {"md5", "inf"};
415    int i;
416
417    for (i = 0; i < DISTTYPES; i++)
418	if (!strcmp(dname[i], name))
419	    return 1 + i;
420    return 0;
421}
422
423static int
424fail(const char *path, const char *msg)
425{
426    if (opt_silent)
427	return 0;
428    warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
429    return 2;
430}
431
432static void
433usage(void)
434{
435    fprintf(stderr,
436	    "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file"
437	    " ...\n");
438    exit(2);
439}
440