The SPDX folks have obsoleted the BSD-2-Clause-FreeBSD identifier. Catch up to that fact and revert to their recommended match of BSD-2-Clause. Discussed with: pfg MFC After: 3 days Sponsored by: Netflix
448 lines
11 KiB
C
448 lines
11 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 1997 Robert Nordier
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef lint
|
|
static const char rcsid[] =
|
|
"$FreeBSD$";
|
|
#endif /* not lint */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <fts.h>
|
|
#include <md5.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
extern int crc(int fd, uint32_t *cval, off_t *clen);
|
|
|
|
#define DISTMD5 1 /* MD5 format */
|
|
#define DISTINF 2 /* .inf format */
|
|
#define DISTTYPES 2 /* types supported */
|
|
|
|
#define E_UNKNOWN 1 /* Unknown format */
|
|
#define E_BADMD5 2 /* Invalid MD5 format */
|
|
#define E_BADINF 3 /* Invalid .inf format */
|
|
#define E_NAME 4 /* Can't derive component name */
|
|
#define E_LENGTH 5 /* Length mismatch */
|
|
#define E_CHKSUM 6 /* Checksum mismatch */
|
|
#define E_ERRNO 7 /* sys_errlist[errno] */
|
|
|
|
#define isfatal(err) ((err) && (err) <= E_NAME)
|
|
|
|
#define NAMESIZE 256 /* filename buffer size */
|
|
#define MDSUMLEN 32 /* length of MD5 message digest */
|
|
|
|
#define isstdin(path) ((path)[0] == '-' && !(path)[1])
|
|
|
|
static const char *opt_dir; /* where to look for components */
|
|
static const char *opt_name; /* name for accessing components */
|
|
static int opt_all; /* report on all components */
|
|
static int opt_ignore; /* ignore missing components */
|
|
static int opt_recurse; /* search directories recursively */
|
|
static int opt_silent; /* silent about inaccessible files */
|
|
static int opt_type; /* dist type: md5 or inf */
|
|
static int opt_exist; /* just verify existence */
|
|
|
|
static int ckdist(const char *path, int type);
|
|
static int chkmd5(FILE * fp, const char *path);
|
|
static int chkinf(FILE * fp, const char *path);
|
|
static int report(const char *path, const char *name, int error);
|
|
static const char *distname(const char *path, const char *name,
|
|
const char *ext);
|
|
static const char *stripath(const char *path);
|
|
static int distfile(const char *path);
|
|
static int disttype(const char *name);
|
|
static int fail(const char *path, const char *msg);
|
|
static void usage(void);
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
static char *arg[2];
|
|
struct stat sb;
|
|
FTS *ftsp;
|
|
FTSENT *f;
|
|
int rval, c, type;
|
|
|
|
while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1)
|
|
switch (c) {
|
|
case 'a':
|
|
opt_all = 1;
|
|
break;
|
|
case 'd':
|
|
opt_dir = optarg;
|
|
break;
|
|
case 'i':
|
|
opt_ignore = 1;
|
|
break;
|
|
case 'n':
|
|
opt_name = optarg;
|
|
break;
|
|
case 'r':
|
|
opt_recurse = 1;
|
|
break;
|
|
case 's':
|
|
opt_silent = 1;
|
|
break;
|
|
case 't':
|
|
if ((opt_type = disttype(optarg)) == 0) {
|
|
warnx("illegal argument to -t option");
|
|
usage();
|
|
}
|
|
break;
|
|
case 'x':
|
|
opt_exist = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc < 1)
|
|
usage();
|
|
if (opt_dir) {
|
|
if (stat(opt_dir, &sb))
|
|
err(2, "%s", opt_dir);
|
|
if (!S_ISDIR(sb.st_mode))
|
|
errx(2, "%s: not a directory", opt_dir);
|
|
}
|
|
rval = 0;
|
|
do {
|
|
if (isstdin(*argv))
|
|
rval |= ckdist(*argv, opt_type);
|
|
else if (stat(*argv, &sb))
|
|
rval |= fail(*argv, NULL);
|
|
else if (S_ISREG(sb.st_mode))
|
|
rval |= ckdist(*argv, opt_type);
|
|
else {
|
|
arg[0] = *argv;
|
|
if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL)
|
|
err(2, "fts_open");
|
|
while (errno = 0, (f = fts_read(ftsp)) != NULL)
|
|
switch (f->fts_info) {
|
|
case FTS_DC:
|
|
rval = fail(f->fts_path, "Directory causes a cycle");
|
|
break;
|
|
case FTS_DNR:
|
|
case FTS_ERR:
|
|
case FTS_NS:
|
|
rval = fail(f->fts_path, sys_errlist[f->fts_errno]);
|
|
break;
|
|
case FTS_D:
|
|
if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL &&
|
|
fts_set(ftsp, f, FTS_SKIP))
|
|
err(2, "fts_set");
|
|
break;
|
|
case FTS_F:
|
|
if ((type = distfile(f->fts_name)) != 0 &&
|
|
(!opt_type || type == opt_type))
|
|
rval |= ckdist(f->fts_path, type);
|
|
break;
|
|
default: ;
|
|
}
|
|
if (errno)
|
|
err(2, "fts_read");
|
|
if (fts_close(ftsp))
|
|
err(2, "fts_close");
|
|
}
|
|
} while (*++argv);
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
ckdist(const char *path, int type)
|
|
{
|
|
FILE *fp;
|
|
int rval, c;
|
|
|
|
if (isstdin(path)) {
|
|
path = "(stdin)";
|
|
fp = stdin;
|
|
} else if ((fp = fopen(path, "r")) == NULL)
|
|
return fail(path, NULL);
|
|
if (!type) {
|
|
if (fp != stdin)
|
|
type = distfile(path);
|
|
if (!type)
|
|
if ((c = fgetc(fp)) != EOF) {
|
|
type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0;
|
|
(void)ungetc(c, fp);
|
|
}
|
|
}
|
|
switch (type) {
|
|
case DISTMD5:
|
|
rval = chkmd5(fp, path);
|
|
break;
|
|
case DISTINF:
|
|
rval = chkinf(fp, path);
|
|
break;
|
|
default:
|
|
rval = report(path, NULL, E_UNKNOWN);
|
|
}
|
|
if (ferror(fp))
|
|
warn("%s", path);
|
|
if (fp != stdin && fclose(fp))
|
|
err(2, "%s", path);
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
chkmd5(FILE * fp, const char *path)
|
|
{
|
|
char buf[298]; /* "MD5 (NAMESIZE = MDSUMLEN" */
|
|
char name[NAMESIZE + 1];
|
|
char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
|
|
const char *dname;
|
|
char *s;
|
|
int rval, error, c, fd;
|
|
char ch;
|
|
|
|
rval = 0;
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
dname = NULL;
|
|
error = 0;
|
|
if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
|
|
&ch)) != 3 && (!feof(fp) || c != 2)) ||
|
|
(c == 3 && ch != '\n') ||
|
|
(s = strrchr(name, ')')) == NULL ||
|
|
strlen(sum) != MDSUMLEN)
|
|
error = E_BADMD5;
|
|
else {
|
|
*s = 0;
|
|
if ((dname = distname(path, name, NULL)) == NULL)
|
|
error = E_NAME;
|
|
else if (opt_exist) {
|
|
if ((fd = open(dname, O_RDONLY)) == -1)
|
|
error = E_ERRNO;
|
|
else if (close(fd))
|
|
err(2, "%s", dname);
|
|
} else if (!MD5File(dname, chk))
|
|
error = E_ERRNO;
|
|
else if (strcmp(chk, sum))
|
|
error = E_CHKSUM;
|
|
}
|
|
if (opt_ignore && error == E_ERRNO && errno == ENOENT)
|
|
continue;
|
|
if (error || opt_all)
|
|
rval |= report(path, dname, error);
|
|
if (isfatal(error))
|
|
break;
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
chkinf(FILE * fp, const char *path)
|
|
{
|
|
char buf[30]; /* "cksum.2 = 10 6" */
|
|
char ext[3];
|
|
struct stat sb;
|
|
const char *dname;
|
|
off_t len;
|
|
u_long sum;
|
|
intmax_t sumlen;
|
|
uint32_t chk;
|
|
int rval, error, c, pieces, cnt, fd;
|
|
char ch;
|
|
|
|
rval = 0;
|
|
for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
|
|
fd = -1;
|
|
dname = NULL;
|
|
error = 0;
|
|
if (cnt == -1) {
|
|
if ((c = sscanf(buf, "Pieces = %d%c", &pieces, &ch)) != 2 ||
|
|
ch != '\n' || pieces < 1)
|
|
error = E_BADINF;
|
|
} else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum,
|
|
&sumlen, &ch)) != 4 &&
|
|
(!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
|
|
ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
|
|
error = E_BADINF;
|
|
else if ((dname = distname(fp == stdin ? NULL : path, NULL,
|
|
ext)) == NULL)
|
|
error = E_NAME;
|
|
else if ((fd = open(dname, O_RDONLY)) == -1)
|
|
error = E_ERRNO;
|
|
else if (fstat(fd, &sb))
|
|
error = E_ERRNO;
|
|
else if (sb.st_size != (off_t)sumlen)
|
|
error = E_LENGTH;
|
|
else if (!opt_exist) {
|
|
if (crc(fd, &chk, &len))
|
|
error = E_ERRNO;
|
|
else if (chk != sum)
|
|
error = E_CHKSUM;
|
|
}
|
|
if (fd != -1 && close(fd))
|
|
err(2, "%s", dname);
|
|
if (opt_ignore && error == E_ERRNO && errno == ENOENT)
|
|
continue;
|
|
if (error || (opt_all && cnt >= 0))
|
|
rval |= report(path, dname, error);
|
|
if (isfatal(error))
|
|
break;
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
report(const char *path, const char *name, int error)
|
|
{
|
|
if (name)
|
|
name = stripath(name);
|
|
switch (error) {
|
|
case E_UNKNOWN:
|
|
printf("%s: Unknown format\n", path);
|
|
break;
|
|
case E_BADMD5:
|
|
printf("%s: Invalid MD5 format\n", path);
|
|
break;
|
|
case E_BADINF:
|
|
printf("%s: Invalid .inf format\n", path);
|
|
break;
|
|
case E_NAME:
|
|
printf("%s: Can't derive component name\n", path);
|
|
break;
|
|
case E_LENGTH:
|
|
printf("%s: %s: Size mismatch\n", path, name);
|
|
break;
|
|
case E_CHKSUM:
|
|
printf("%s: %s: Checksum mismatch\n", path, name);
|
|
break;
|
|
case E_ERRNO:
|
|
printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
|
|
break;
|
|
default:
|
|
printf("%s: %s: OK\n", path, name);
|
|
}
|
|
return error != 0;
|
|
}
|
|
|
|
static const char *
|
|
distname(const char *path, const char *name, const char *ext)
|
|
{
|
|
static char buf[NAMESIZE];
|
|
size_t plen, nlen;
|
|
char *s;
|
|
|
|
if (opt_name)
|
|
name = opt_name;
|
|
else if (!name) {
|
|
if (!path)
|
|
return NULL;
|
|
name = stripath(path);
|
|
}
|
|
nlen = strlen(name);
|
|
if (ext && nlen > 4 && name[nlen - 4] == '.' &&
|
|
disttype(name + nlen - 3) == DISTINF)
|
|
nlen -= 4;
|
|
if (opt_dir) {
|
|
path = opt_dir;
|
|
plen = strlen(path);
|
|
} else
|
|
plen = path && (s = strrchr(path, '/')) != NULL ?
|
|
(size_t)(s - path) : 0;
|
|
if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
|
|
return NULL;
|
|
s = buf;
|
|
if (plen) {
|
|
memcpy(s, path, plen);
|
|
s += plen;
|
|
*s++ = '/';
|
|
}
|
|
memcpy(s, name, nlen);
|
|
s += nlen;
|
|
if (ext) {
|
|
*s++ = '.';
|
|
memcpy(s, ext, 2);
|
|
s += 2;
|
|
}
|
|
*s = 0;
|
|
return buf;
|
|
}
|
|
|
|
static const char *
|
|
stripath(const char *path)
|
|
{
|
|
const char *s;
|
|
|
|
return ((s = strrchr(path, '/')) != NULL && s[1] ?
|
|
s + 1 : path);
|
|
}
|
|
|
|
static int
|
|
distfile(const char *path)
|
|
{
|
|
const char *s;
|
|
int type;
|
|
|
|
if ((type = disttype(path)) == DISTMD5 ||
|
|
((s = strrchr(path, '.')) != NULL && s > path &&
|
|
(type = disttype(s + 1)) != 0))
|
|
return type;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
disttype(const char *name)
|
|
{
|
|
static const char dname[DISTTYPES][4] = {"md5", "inf"};
|
|
int i;
|
|
|
|
for (i = 0; i < DISTTYPES; i++)
|
|
if (!strcmp(dname[i], name))
|
|
return 1 + i;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
fail(const char *path, const char *msg)
|
|
{
|
|
if (opt_silent)
|
|
return 0;
|
|
warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
|
|
return 2;
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr,
|
|
"usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
|
|
exit(2);
|
|
}
|