ckdist.c revision 61019
121918Sjkh/*
221918Sjkh * Copyright (c) 1997 Robert Nordier
321918Sjkh * All rights reserved.
421918Sjkh *
521918Sjkh * Redistribution and use in source and binary forms, with or without
621918Sjkh * modification, are permitted provided that the following conditions
721918Sjkh * are met:
821918Sjkh * 1. Redistributions of source code must retain the above copyright
921918Sjkh *    notice, this list of conditions and the following disclaimer.
1021918Sjkh * 2. Redistributions in binary form must reproduce the above copyright
1121918Sjkh *    notice, this list of conditions and the following disclaimer in
1221918Sjkh *    the documentation and/or other materials provided with the
1321918Sjkh *    distribution.
1421918Sjkh *
1521918Sjkh * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
1621918Sjkh * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1721918Sjkh * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1821918Sjkh * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
1921918Sjkh * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2021918Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
2121918Sjkh * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2221918Sjkh * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
2321918Sjkh * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
2421918Sjkh * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
2521918Sjkh * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2621918Sjkh */
2721918Sjkh
2829449Scharnier#ifndef lint
2929449Scharnierstatic const char rcsid[] =
3050479Speter  "$FreeBSD: head/usr.sbin/ckdist/ckdist.c 61019 2000-05-28 14:57:49Z charnier $";
3129449Scharnier#endif /* not lint */
3229449Scharnier
3321918Sjkh#include <sys/types.h>
3421918Sjkh#include <sys/stat.h>
3529449Scharnier#include <err.h>
3629449Scharnier#include <errno.h>
3721918Sjkh#include <fcntl.h>
3821918Sjkh#include <fts.h>
3921918Sjkh#include <md5.h>
4029449Scharnier#include <stdio.h>
4121918Sjkh#include <stdlib.h>
4221918Sjkh#include <string.h>
4329449Scharnier#include <unistd.h>
4421918Sjkh
4521918Sjkhextern int crc(int fd, u_long * cval, u_long * clen);
4621918Sjkh
4721918Sjkh#define DISTMD5     1		/* MD5 format */
4821918Sjkh#define DISTINF     2		/* .inf format */
4921918Sjkh#define DISTTYPES   2		/* types supported */
5021918Sjkh
5121918Sjkh#define E_UNKNOWN   1		/* Unknown format */
5221918Sjkh#define E_BADMD5    2		/* Invalid MD5 format */
5321918Sjkh#define E_BADINF    3		/* Invalid .inf format */
5421918Sjkh#define E_NAME      4		/* Can't derive component name */
5521918Sjkh#define E_LENGTH    5		/* Length mismatch */
5621918Sjkh#define E_CHKSUM    6		/* Checksum mismatch */
5721918Sjkh#define E_ERRNO     7		/* sys_errlist[errno] */
5821918Sjkh
5921918Sjkh#define isfatal(err)   ((err) && (err) <= E_NAME)
6021918Sjkh
6121918Sjkh#define NAMESIZE  256           /* filename buffer size */
6221918Sjkh#define MDSUMLEN   32           /* length of MD5 message digest */
6321918Sjkh
6421918Sjkh#define isstdin(path)  ((path)[0] == '-' && !(path)[1])
6521918Sjkh
6621918Sjkhstatic const char *opt_dir;	/* where to look for components */
6721918Sjkhstatic const char *opt_name;	/* name for accessing components */
6821918Sjkhstatic int opt_all;		/* report on all components */
6921918Sjkhstatic int opt_ignore;		/* ignore missing components */
7021918Sjkhstatic int opt_recurse;		/* search directories recursively */
7121918Sjkhstatic int opt_silent;		/* silent about inaccessible files */
7221918Sjkhstatic int opt_type;		/* dist type: md5 or inf */
7321918Sjkhstatic int opt_exist;		/* just verify existence */
7421918Sjkh
7521918Sjkhstatic int ckdist(const char *path, int type);
7621918Sjkhstatic int chkmd5(FILE * fp, const char *path);
7721918Sjkhstatic int chkinf(FILE * fp, const char *path);
7821918Sjkhstatic int report(const char *path, const char *name, int error);
7921918Sjkhstatic const char *distname(const char *path, const char *name,
8021918Sjkh	                    const char *ext);
8121918Sjkhstatic char *stripath(const char *path);
8221918Sjkhstatic int distfile(const char *path);
8321918Sjkhstatic int disttype(const char *name);
8421918Sjkhstatic int fail(const char *path, const char *msg);
8521918Sjkhstatic void usage(void);
8621918Sjkh
8721918Sjkhint
8821918Sjkhmain(int argc, char *argv[])
8921918Sjkh{
9021918Sjkh    static char *arg[2];
9121918Sjkh    struct stat sb;
9221918Sjkh    FTS *ftsp;
9321918Sjkh    FTSENT *f;
9421918Sjkh    int rval, c, type;
9521918Sjkh
9621918Sjkh    while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1)
9721918Sjkh	switch (c) {
9821918Sjkh	case 'a':
9921918Sjkh	    opt_all = 1;
10021918Sjkh	    break;
10121918Sjkh	case 'd':
10221918Sjkh	    opt_dir = optarg;
10321918Sjkh	    break;
10421918Sjkh	case 'i':
10521918Sjkh	    opt_ignore = 1;
10621918Sjkh	    break;
10721918Sjkh	case 'n':
10821918Sjkh	    opt_name = optarg;
10921918Sjkh	    break;
11021918Sjkh	case 'r':
11121918Sjkh	    opt_recurse = 1;
11221918Sjkh	    break;
11321918Sjkh	case 's':
11421918Sjkh	    opt_silent = 1;
11521918Sjkh	    break;
11621918Sjkh	case 't':
11721918Sjkh	    if ((opt_type = disttype(optarg)) == 0) {
11821918Sjkh		warnx("illegal argument to -t option");
11921918Sjkh		usage();
12021918Sjkh	    }
12121918Sjkh	    break;
12221918Sjkh	case 'x':
12321918Sjkh	    opt_exist = 1;
12421918Sjkh	    break;
12521918Sjkh	default:
12621918Sjkh	    usage();
12721918Sjkh	}
12821918Sjkh    argc -= optind;
12921918Sjkh    argv += optind;
13021918Sjkh    if (argc < 1)
13121918Sjkh	usage();
13221918Sjkh    if (opt_dir) {
13321918Sjkh	if (stat(opt_dir, &sb))
13461019Scharnier	    err(2, "%s", opt_dir);
13521918Sjkh	if (!S_ISDIR(sb.st_mode))
13629449Scharnier	    errx(2, "%s: not a directory", opt_dir);
13721918Sjkh    }
13821918Sjkh    rval = 0;
13921918Sjkh    do {
14021918Sjkh	if (isstdin(*argv))
14121918Sjkh	    rval |= ckdist(*argv, opt_type);
14221918Sjkh	else if (stat(*argv, &sb))
14321918Sjkh	    rval |= fail(*argv, NULL);
14421918Sjkh	else if (S_ISREG(sb.st_mode))
14521918Sjkh	    rval |= ckdist(*argv, opt_type);
14621918Sjkh	else {
14721918Sjkh	    arg[0] = *argv;
14821918Sjkh	    if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL)
14921918Sjkh		err(2, "fts_open");
15021918Sjkh	    while ((f = fts_read(ftsp)) != NULL)
15121918Sjkh		switch (f->fts_info) {
15221918Sjkh		case FTS_DC:
15321918Sjkh		    rval = fail(f->fts_path, "Directory causes a cycle");
15421918Sjkh		    break;
15521918Sjkh		case FTS_DNR:
15621918Sjkh		case FTS_ERR:
15721918Sjkh		case FTS_NS:
15821918Sjkh		    rval = fail(f->fts_path, sys_errlist[f->fts_errno]);
15921918Sjkh		    break;
16021918Sjkh		case FTS_D:
16121918Sjkh		    if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL &&
16221918Sjkh			fts_set(ftsp, f, FTS_SKIP))
16321918Sjkh			err(2, "fts_set");
16421918Sjkh		    break;
16521918Sjkh		case FTS_F:
16621918Sjkh		    if ((type = distfile(f->fts_name)) != 0 &&
16721918Sjkh			(!opt_type || type == opt_type))
16821918Sjkh			rval |= ckdist(f->fts_path, type);
16921918Sjkh		    break;
17021918Sjkh                default: ;
17121918Sjkh		}
17221918Sjkh	    if (errno)
17321918Sjkh		err(2, "fts_read");
17421918Sjkh	    if (fts_close(ftsp))
17521918Sjkh		err(2, "fts_close");
17621918Sjkh	}
17721918Sjkh    } while (*++argv);
17821918Sjkh    return rval;
17921918Sjkh}
18021918Sjkh
18121918Sjkhstatic int
18221918Sjkhckdist(const char *path, int type)
18321918Sjkh{
18421918Sjkh    FILE *fp;
18521918Sjkh    int rval, c;
18621918Sjkh
18721918Sjkh    if (isstdin(path)) {
18821918Sjkh	path = "(stdin)";
18921918Sjkh	fp = stdin;
19021918Sjkh    } else if ((fp = fopen(path, "r")) == NULL)
19121918Sjkh	return fail(path, NULL);
19221918Sjkh    if (!type) {
19321918Sjkh	if (fp != stdin)
19421918Sjkh	    type = distfile(path);
19521918Sjkh	if (!type)
19621918Sjkh	    if ((c = fgetc(fp)) != EOF) {
19721918Sjkh		type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0;
19821918Sjkh		(void)ungetc(c, fp);
19921918Sjkh	    }
20021918Sjkh    }
20121918Sjkh    switch (type) {
20221918Sjkh    case DISTMD5:
20321918Sjkh	rval = chkmd5(fp, path);
20421918Sjkh	break;
20521918Sjkh    case DISTINF:
20621918Sjkh	rval = chkinf(fp, path);
20721918Sjkh	break;
20821918Sjkh    default:
20921918Sjkh	rval = report(path, NULL, E_UNKNOWN);
21021918Sjkh    }
21121918Sjkh    if (ferror(fp))
21261019Scharnier	warn("%s", path);
21321918Sjkh    if (fp != stdin && fclose(fp))
21461019Scharnier	err(2, "%s", path);
21521918Sjkh    return rval;
21621918Sjkh}
21721918Sjkh
21821918Sjkhstatic int
21921918Sjkhchkmd5(FILE * fp, const char *path)
22021918Sjkh{
22121918Sjkh    char buf[298];              /* "MD5 (NAMESIZE = MDSUMLEN" */
22221918Sjkh    char name[NAMESIZE + 1];
22321918Sjkh    char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
22421918Sjkh    const char *dname;
22521918Sjkh    char *s;
22621918Sjkh    int rval, error, c, fd;
22721918Sjkh    char ch;
22821918Sjkh
22921918Sjkh    rval = 0;
23021918Sjkh    while (fgets(buf, sizeof(buf), fp)) {
23121918Sjkh	dname = NULL;
23221918Sjkh	error = 0;
23321918Sjkh	if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
23421918Sjkh			 &ch)) != 3 && (!feof(fp) || c != 2)) ||
23521918Sjkh	    (c == 3 && ch != '\n') ||
23621918Sjkh	    (s = strrchr(name, ')')) == NULL ||
23721918Sjkh	    strlen(sum) != MDSUMLEN)
23821918Sjkh	    error = E_BADMD5;
23921918Sjkh	else {
24021918Sjkh	    *s = 0;
24121918Sjkh	    if ((dname = distname(path, name, NULL)) == NULL)
24221918Sjkh		error = E_NAME;
24321918Sjkh	    else if (opt_exist) {
24421918Sjkh		if ((fd = open(dname, O_RDONLY)) == -1)
24521918Sjkh		    error = E_ERRNO;
24621918Sjkh		else if (close(fd))
24761019Scharnier		    err(2, "%s", dname);
24821918Sjkh	    } else if (!MD5File((char *)dname, chk))
24921918Sjkh		error = E_ERRNO;
25021918Sjkh	    else if (strcmp(chk, sum))
25121918Sjkh		error = E_CHKSUM;
25221918Sjkh	}
25321918Sjkh	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
25421918Sjkh	    continue;
25521918Sjkh	if (error || opt_all)
25621918Sjkh	    rval |= report(path, dname, error);
25721918Sjkh	if (isfatal(error))
25821918Sjkh	    break;
25921918Sjkh    }
26021918Sjkh    return rval;
26121918Sjkh}
26221918Sjkh
26321918Sjkhstatic int
26421918Sjkhchkinf(FILE * fp, const char *path)
26521918Sjkh{
26621918Sjkh    char buf[30];               /* "cksum.2 = 10 6" */
26721918Sjkh    char ext[3];
26821918Sjkh    struct stat sb;
26921918Sjkh    const char *dname;
27021918Sjkh    u_long sum, len, chk;
27121918Sjkh    int rval, error, c, pieces, cnt, fd;
27221918Sjkh    char ch;
27321918Sjkh
27421918Sjkh    rval = 0;
27521918Sjkh    for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
27621918Sjkh	fd = -1;
27721918Sjkh	dname = NULL;
27821918Sjkh	error = 0;
27921918Sjkh	if (cnt == -1) {
28021918Sjkh	    if ((c = sscanf(buf, "Pieces =  %d%c", &pieces, &ch)) != 2 ||
28121918Sjkh		ch != '\n' || pieces < 1)
28221918Sjkh		error = E_BADINF;
28321918Sjkh	} else if (((c = sscanf(buf, "cksum.%2s = %lu %lu%c", ext, &sum,
28421918Sjkh			        &len, &ch)) != 4 &&
28521918Sjkh		    (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
28621918Sjkh		   ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
28721918Sjkh	    error = E_BADINF;
28821918Sjkh	else if ((dname = distname(fp == stdin ? NULL : path, NULL,
28921918Sjkh				    ext)) == NULL)
29021918Sjkh	    error = E_NAME;
29121918Sjkh	else if ((fd = open(dname, O_RDONLY)) == -1)
29221918Sjkh	    error = E_ERRNO;
29321918Sjkh	else if (fstat(fd, &sb))
29421918Sjkh	    error = E_ERRNO;
29521918Sjkh	else if (sb.st_size != (off_t)len)
29621918Sjkh	    error = E_LENGTH;
29721918Sjkh	else if (!opt_exist) {
29821918Sjkh	    if (crc(fd, &chk, &len))
29921918Sjkh		error = E_ERRNO;
30021918Sjkh	    else if (chk != sum)
30121918Sjkh		error = E_CHKSUM;
30221918Sjkh	}
30321918Sjkh	if (fd != -1 && close(fd))
30461019Scharnier	    err(2, "%s", dname);
30521918Sjkh	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
30621918Sjkh	    continue;
30721918Sjkh	if (error || (opt_all && cnt >= 0))
30821918Sjkh	    rval |= report(path, dname, error);
30921918Sjkh	if (isfatal(error))
31021918Sjkh	    break;
31121918Sjkh    }
31221918Sjkh    return rval;
31321918Sjkh}
31421918Sjkh
31521918Sjkhstatic int
31621918Sjkhreport(const char *path, const char *name, int error)
31721918Sjkh{
31821918Sjkh    if (name)
31921918Sjkh	name = stripath(name);
32021918Sjkh    switch (error) {
32121918Sjkh    case E_UNKNOWN:
32221918Sjkh	printf("%s: Unknown format\n", path);
32321918Sjkh	break;
32421918Sjkh    case E_BADMD5:
32521918Sjkh	printf("%s: Invalid MD5 format\n", path);
32621918Sjkh	break;
32721918Sjkh    case E_BADINF:
32821918Sjkh	printf("%s: Invalid .inf format\n", path);
32921918Sjkh	break;
33021918Sjkh    case E_NAME:
33121918Sjkh	printf("%s: Can't derive component name\n", path);
33221918Sjkh	break;
33321918Sjkh    case E_LENGTH:
33421918Sjkh	printf("%s: %s: Size mismatch\n", path, name);
33521918Sjkh	break;
33621918Sjkh    case E_CHKSUM:
33721918Sjkh	printf("%s: %s: Checksum mismatch\n", path, name);
33821918Sjkh	break;
33921918Sjkh    case E_ERRNO:
34021918Sjkh	printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
34121918Sjkh	break;
34221918Sjkh    default:
34321918Sjkh	printf("%s: %s: OK\n", path, name);
34421918Sjkh    }
34521918Sjkh    return error != 0;
34621918Sjkh}
34721918Sjkh
34821918Sjkhstatic const char *
34921918Sjkhdistname(const char *path, const char *name, const char *ext)
35021918Sjkh{
35121918Sjkh    static char buf[NAMESIZE];
35221918Sjkh    size_t plen, nlen;
35321918Sjkh    char *s;
35421918Sjkh
35521918Sjkh    if (opt_name)
35621918Sjkh	name = opt_name;
35721918Sjkh    else if (!name) {
35821918Sjkh	if (!path)
35921918Sjkh	    return NULL;
36021918Sjkh	name = stripath(path);
36121918Sjkh    }
36221918Sjkh    nlen = strlen(name);
36321918Sjkh    if (ext && nlen > 4 && name[nlen - 4] == '.' &&
36421918Sjkh	disttype(name + nlen - 3) == DISTINF)
36521918Sjkh	nlen -= 4;
36621918Sjkh    if (opt_dir) {
36721918Sjkh	path = opt_dir;
36821918Sjkh	plen = strlen(path);
36921918Sjkh    } else
37021918Sjkh	plen = path && (s = strrchr(path, '/')) != NULL ?
37121918Sjkh            (size_t)(s - path) : 0;
37221918Sjkh    if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
37321918Sjkh	return NULL;
37421918Sjkh    s = buf;
37521918Sjkh    if (plen) {
37621918Sjkh	memcpy(s, path, plen);
37721918Sjkh	s += plen;
37821918Sjkh	*s++ = '/';
37921918Sjkh    }
38021918Sjkh    memcpy(s, name, nlen);
38121918Sjkh    s += nlen;
38221918Sjkh    if (ext) {
38321918Sjkh	*s++ = '.';
38421918Sjkh	memcpy(s, ext, 2);
38521918Sjkh	s += 2;
38621918Sjkh    }
38721918Sjkh    *s = 0;
38821918Sjkh    return buf;
38921918Sjkh}
39021918Sjkh
39121918Sjkhstatic char *
39221918Sjkhstripath(const char *path)
39321918Sjkh{
39421918Sjkh    const char *s;
39521918Sjkh
39621918Sjkh    return (char *)((s = strrchr(path, '/')) != NULL && s[1] ?
39721918Sjkh		    s + 1 : path);
39821918Sjkh}
39921918Sjkh
40021918Sjkhstatic int
40121918Sjkhdistfile(const char *path)
40221918Sjkh{
40321918Sjkh    const char *s;
40421918Sjkh    int type;
40521918Sjkh
40621918Sjkh    if ((type = disttype(path)) == DISTMD5 ||
40721918Sjkh	((s = strrchr(path, '.')) != NULL && s > path &&
40821918Sjkh	 (type = disttype(s + 1)) != 0))
40921918Sjkh	return type;
41021918Sjkh    return 0;
41121918Sjkh}
41221918Sjkh
41321918Sjkhstatic int
41421918Sjkhdisttype(const char *name)
41521918Sjkh{
41621918Sjkh    static const char dname[DISTTYPES][4] = {"md5", "inf"};
41721918Sjkh    int i;
41821918Sjkh
41921918Sjkh    for (i = 0; i < DISTTYPES; i++)
42021918Sjkh	if (!strcmp(dname[i], name))
42121918Sjkh	    return 1 + i;
42221918Sjkh    return 0;
42321918Sjkh}
42421918Sjkh
42521918Sjkhstatic int
42621918Sjkhfail(const char *path, const char *msg)
42721918Sjkh{
42821918Sjkh    if (opt_silent)
42921918Sjkh	return 0;
43021918Sjkh    warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
43121918Sjkh    return 2;
43221918Sjkh}
43321918Sjkh
43421918Sjkhstatic void
43521918Sjkhusage(void)
43621918Sjkh{
43721918Sjkh    fprintf(stderr,
43829449Scharnier	    "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
43921918Sjkh    exit(2);
44021918Sjkh}
441