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$"; 3129449Scharnier#endif /* not lint */ 3229449Scharnier 3321918Sjkh#include <sys/types.h> 3421918Sjkh#include <sys/stat.h> 35112213Srobert 3629449Scharnier#include <err.h> 3729449Scharnier#include <errno.h> 3821918Sjkh#include <fcntl.h> 3921918Sjkh#include <fts.h> 4021918Sjkh#include <md5.h> 4129449Scharnier#include <stdio.h> 42112213Srobert#include <stdint.h> 4321918Sjkh#include <stdlib.h> 4421918Sjkh#include <string.h> 4529449Scharnier#include <unistd.h> 4621918Sjkh 47112213Srobertextern int crc(int fd, uint32_t *cval, off_t *clen); 4821918Sjkh 4921918Sjkh#define DISTMD5 1 /* MD5 format */ 5021918Sjkh#define DISTINF 2 /* .inf format */ 5121918Sjkh#define DISTTYPES 2 /* types supported */ 5221918Sjkh 5321918Sjkh#define E_UNKNOWN 1 /* Unknown format */ 5421918Sjkh#define E_BADMD5 2 /* Invalid MD5 format */ 5521918Sjkh#define E_BADINF 3 /* Invalid .inf format */ 5621918Sjkh#define E_NAME 4 /* Can't derive component name */ 5721918Sjkh#define E_LENGTH 5 /* Length mismatch */ 5821918Sjkh#define E_CHKSUM 6 /* Checksum mismatch */ 5921918Sjkh#define E_ERRNO 7 /* sys_errlist[errno] */ 6021918Sjkh 6121918Sjkh#define isfatal(err) ((err) && (err) <= E_NAME) 6221918Sjkh 6321918Sjkh#define NAMESIZE 256 /* filename buffer size */ 6421918Sjkh#define MDSUMLEN 32 /* length of MD5 message digest */ 6521918Sjkh 6621918Sjkh#define isstdin(path) ((path)[0] == '-' && !(path)[1]) 6721918Sjkh 6821918Sjkhstatic const char *opt_dir; /* where to look for components */ 6921918Sjkhstatic const char *opt_name; /* name for accessing components */ 7021918Sjkhstatic int opt_all; /* report on all components */ 7121918Sjkhstatic int opt_ignore; /* ignore missing components */ 7221918Sjkhstatic int opt_recurse; /* search directories recursively */ 7321918Sjkhstatic int opt_silent; /* silent about inaccessible files */ 7421918Sjkhstatic int opt_type; /* dist type: md5 or inf */ 7521918Sjkhstatic int opt_exist; /* just verify existence */ 7621918Sjkh 7721918Sjkhstatic int ckdist(const char *path, int type); 7821918Sjkhstatic int chkmd5(FILE * fp, const char *path); 7921918Sjkhstatic int chkinf(FILE * fp, const char *path); 8021918Sjkhstatic int report(const char *path, const char *name, int error); 8121918Sjkhstatic const char *distname(const char *path, const char *name, 8221918Sjkh const char *ext); 83185073Sdelphijstatic const char *stripath(const char *path); 8421918Sjkhstatic int distfile(const char *path); 8521918Sjkhstatic int disttype(const char *name); 8621918Sjkhstatic int fail(const char *path, const char *msg); 8721918Sjkhstatic void usage(void); 8821918Sjkh 8921918Sjkhint 9021918Sjkhmain(int argc, char *argv[]) 9121918Sjkh{ 9221918Sjkh static char *arg[2]; 9321918Sjkh struct stat sb; 9421918Sjkh FTS *ftsp; 9521918Sjkh FTSENT *f; 9621918Sjkh int rval, c, type; 9721918Sjkh 9821918Sjkh while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1) 9921918Sjkh switch (c) { 10021918Sjkh case 'a': 10121918Sjkh opt_all = 1; 10221918Sjkh break; 10321918Sjkh case 'd': 10421918Sjkh opt_dir = optarg; 10521918Sjkh break; 10621918Sjkh case 'i': 10721918Sjkh opt_ignore = 1; 10821918Sjkh break; 10921918Sjkh case 'n': 11021918Sjkh opt_name = optarg; 11121918Sjkh break; 11221918Sjkh case 'r': 11321918Sjkh opt_recurse = 1; 11421918Sjkh break; 11521918Sjkh case 's': 11621918Sjkh opt_silent = 1; 11721918Sjkh break; 11821918Sjkh case 't': 11921918Sjkh if ((opt_type = disttype(optarg)) == 0) { 12021918Sjkh warnx("illegal argument to -t option"); 12121918Sjkh usage(); 12221918Sjkh } 12321918Sjkh break; 12421918Sjkh case 'x': 12521918Sjkh opt_exist = 1; 12621918Sjkh break; 12721918Sjkh default: 12821918Sjkh usage(); 12921918Sjkh } 13021918Sjkh argc -= optind; 13121918Sjkh argv += optind; 13221918Sjkh if (argc < 1) 13321918Sjkh usage(); 13421918Sjkh if (opt_dir) { 13521918Sjkh if (stat(opt_dir, &sb)) 13661019Scharnier err(2, "%s", opt_dir); 13721918Sjkh if (!S_ISDIR(sb.st_mode)) 13829449Scharnier errx(2, "%s: not a directory", opt_dir); 13921918Sjkh } 14021918Sjkh rval = 0; 14121918Sjkh do { 14221918Sjkh if (isstdin(*argv)) 14321918Sjkh rval |= ckdist(*argv, opt_type); 14421918Sjkh else if (stat(*argv, &sb)) 14521918Sjkh rval |= fail(*argv, NULL); 14621918Sjkh else if (S_ISREG(sb.st_mode)) 14721918Sjkh rval |= ckdist(*argv, opt_type); 14821918Sjkh else { 14921918Sjkh arg[0] = *argv; 15021918Sjkh if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL) 15121918Sjkh err(2, "fts_open"); 15221918Sjkh while ((f = fts_read(ftsp)) != NULL) 15321918Sjkh switch (f->fts_info) { 15421918Sjkh case FTS_DC: 15521918Sjkh rval = fail(f->fts_path, "Directory causes a cycle"); 15621918Sjkh break; 15721918Sjkh case FTS_DNR: 15821918Sjkh case FTS_ERR: 15921918Sjkh case FTS_NS: 16021918Sjkh rval = fail(f->fts_path, sys_errlist[f->fts_errno]); 16121918Sjkh break; 16221918Sjkh case FTS_D: 16321918Sjkh if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL && 16421918Sjkh fts_set(ftsp, f, FTS_SKIP)) 16521918Sjkh err(2, "fts_set"); 16621918Sjkh break; 16721918Sjkh case FTS_F: 16821918Sjkh if ((type = distfile(f->fts_name)) != 0 && 16921918Sjkh (!opt_type || type == opt_type)) 17021918Sjkh rval |= ckdist(f->fts_path, type); 17121918Sjkh break; 17221918Sjkh default: ; 17321918Sjkh } 17421918Sjkh if (errno) 17521918Sjkh err(2, "fts_read"); 17621918Sjkh if (fts_close(ftsp)) 17721918Sjkh err(2, "fts_close"); 17821918Sjkh } 17921918Sjkh } while (*++argv); 18021918Sjkh return rval; 18121918Sjkh} 18221918Sjkh 18321918Sjkhstatic int 18421918Sjkhckdist(const char *path, int type) 18521918Sjkh{ 18621918Sjkh FILE *fp; 18721918Sjkh int rval, c; 18821918Sjkh 18921918Sjkh if (isstdin(path)) { 19021918Sjkh path = "(stdin)"; 19121918Sjkh fp = stdin; 19221918Sjkh } else if ((fp = fopen(path, "r")) == NULL) 19321918Sjkh return fail(path, NULL); 19421918Sjkh if (!type) { 19521918Sjkh if (fp != stdin) 19621918Sjkh type = distfile(path); 19721918Sjkh if (!type) 19821918Sjkh if ((c = fgetc(fp)) != EOF) { 19921918Sjkh type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0; 20021918Sjkh (void)ungetc(c, fp); 20121918Sjkh } 20221918Sjkh } 20321918Sjkh switch (type) { 20421918Sjkh case DISTMD5: 20521918Sjkh rval = chkmd5(fp, path); 20621918Sjkh break; 20721918Sjkh case DISTINF: 20821918Sjkh rval = chkinf(fp, path); 20921918Sjkh break; 21021918Sjkh default: 21121918Sjkh rval = report(path, NULL, E_UNKNOWN); 21221918Sjkh } 21321918Sjkh if (ferror(fp)) 21461019Scharnier warn("%s", path); 21521918Sjkh if (fp != stdin && fclose(fp)) 21661019Scharnier err(2, "%s", path); 21721918Sjkh return rval; 21821918Sjkh} 21921918Sjkh 22021918Sjkhstatic int 22121918Sjkhchkmd5(FILE * fp, const char *path) 22221918Sjkh{ 22321918Sjkh char buf[298]; /* "MD5 (NAMESIZE = MDSUMLEN" */ 22421918Sjkh char name[NAMESIZE + 1]; 22521918Sjkh char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1]; 22621918Sjkh const char *dname; 22721918Sjkh char *s; 22821918Sjkh int rval, error, c, fd; 22921918Sjkh char ch; 23021918Sjkh 23121918Sjkh rval = 0; 23221918Sjkh while (fgets(buf, sizeof(buf), fp)) { 23321918Sjkh dname = NULL; 23421918Sjkh error = 0; 23521918Sjkh if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum, 23621918Sjkh &ch)) != 3 && (!feof(fp) || c != 2)) || 23721918Sjkh (c == 3 && ch != '\n') || 23821918Sjkh (s = strrchr(name, ')')) == NULL || 23921918Sjkh strlen(sum) != MDSUMLEN) 24021918Sjkh error = E_BADMD5; 24121918Sjkh else { 24221918Sjkh *s = 0; 24321918Sjkh if ((dname = distname(path, name, NULL)) == NULL) 24421918Sjkh error = E_NAME; 24521918Sjkh else if (opt_exist) { 24621918Sjkh if ((fd = open(dname, O_RDONLY)) == -1) 24721918Sjkh error = E_ERRNO; 24821918Sjkh else if (close(fd)) 24961019Scharnier err(2, "%s", dname); 250185073Sdelphij } else if (!MD5File(dname, chk)) 25121918Sjkh error = E_ERRNO; 25221918Sjkh else if (strcmp(chk, sum)) 25321918Sjkh error = E_CHKSUM; 25421918Sjkh } 25521918Sjkh if (opt_ignore && error == E_ERRNO && errno == ENOENT) 25621918Sjkh continue; 25721918Sjkh if (error || opt_all) 25821918Sjkh rval |= report(path, dname, error); 25921918Sjkh if (isfatal(error)) 26021918Sjkh break; 26121918Sjkh } 26221918Sjkh return rval; 26321918Sjkh} 26421918Sjkh 26521918Sjkhstatic int 26621918Sjkhchkinf(FILE * fp, const char *path) 26721918Sjkh{ 26821918Sjkh char buf[30]; /* "cksum.2 = 10 6" */ 26921918Sjkh char ext[3]; 27021918Sjkh struct stat sb; 27121918Sjkh const char *dname; 272112213Srobert off_t len; 273112213Srobert u_long sum; 274112213Srobert intmax_t sumlen; 275112213Srobert uint32_t chk; 27621918Sjkh int rval, error, c, pieces, cnt, fd; 27721918Sjkh char ch; 27821918Sjkh 27921918Sjkh rval = 0; 28021918Sjkh for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) { 28121918Sjkh fd = -1; 28221918Sjkh dname = NULL; 28321918Sjkh error = 0; 28421918Sjkh if (cnt == -1) { 28521918Sjkh if ((c = sscanf(buf, "Pieces = %d%c", &pieces, &ch)) != 2 || 28621918Sjkh ch != '\n' || pieces < 1) 28721918Sjkh error = E_BADINF; 288112213Srobert } else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum, 289112213Srobert &sumlen, &ch)) != 4 && 29021918Sjkh (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') || 29121918Sjkh ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26) 29221918Sjkh error = E_BADINF; 29321918Sjkh else if ((dname = distname(fp == stdin ? NULL : path, NULL, 29421918Sjkh ext)) == NULL) 29521918Sjkh error = E_NAME; 29621918Sjkh else if ((fd = open(dname, O_RDONLY)) == -1) 29721918Sjkh error = E_ERRNO; 29821918Sjkh else if (fstat(fd, &sb)) 29921918Sjkh error = E_ERRNO; 300112213Srobert else if (sb.st_size != (off_t)sumlen) 30121918Sjkh error = E_LENGTH; 30221918Sjkh else if (!opt_exist) { 30321918Sjkh if (crc(fd, &chk, &len)) 30421918Sjkh error = E_ERRNO; 30521918Sjkh else if (chk != sum) 30621918Sjkh error = E_CHKSUM; 30721918Sjkh } 30821918Sjkh if (fd != -1 && close(fd)) 30961019Scharnier err(2, "%s", dname); 31021918Sjkh if (opt_ignore && error == E_ERRNO && errno == ENOENT) 31121918Sjkh continue; 31221918Sjkh if (error || (opt_all && cnt >= 0)) 31321918Sjkh rval |= report(path, dname, error); 31421918Sjkh if (isfatal(error)) 31521918Sjkh break; 31621918Sjkh } 31721918Sjkh return rval; 31821918Sjkh} 31921918Sjkh 32021918Sjkhstatic int 32121918Sjkhreport(const char *path, const char *name, int error) 32221918Sjkh{ 32321918Sjkh if (name) 32421918Sjkh name = stripath(name); 32521918Sjkh switch (error) { 32621918Sjkh case E_UNKNOWN: 32721918Sjkh printf("%s: Unknown format\n", path); 32821918Sjkh break; 32921918Sjkh case E_BADMD5: 33021918Sjkh printf("%s: Invalid MD5 format\n", path); 33121918Sjkh break; 33221918Sjkh case E_BADINF: 33321918Sjkh printf("%s: Invalid .inf format\n", path); 33421918Sjkh break; 33521918Sjkh case E_NAME: 33621918Sjkh printf("%s: Can't derive component name\n", path); 33721918Sjkh break; 33821918Sjkh case E_LENGTH: 33921918Sjkh printf("%s: %s: Size mismatch\n", path, name); 34021918Sjkh break; 34121918Sjkh case E_CHKSUM: 34221918Sjkh printf("%s: %s: Checksum mismatch\n", path, name); 34321918Sjkh break; 34421918Sjkh case E_ERRNO: 34521918Sjkh printf("%s: %s: %s\n", path, name, sys_errlist[errno]); 34621918Sjkh break; 34721918Sjkh default: 34821918Sjkh printf("%s: %s: OK\n", path, name); 34921918Sjkh } 35021918Sjkh return error != 0; 35121918Sjkh} 35221918Sjkh 35321918Sjkhstatic const char * 35421918Sjkhdistname(const char *path, const char *name, const char *ext) 35521918Sjkh{ 35621918Sjkh static char buf[NAMESIZE]; 35721918Sjkh size_t plen, nlen; 35821918Sjkh char *s; 35921918Sjkh 36021918Sjkh if (opt_name) 36121918Sjkh name = opt_name; 36221918Sjkh else if (!name) { 36321918Sjkh if (!path) 36421918Sjkh return NULL; 36521918Sjkh name = stripath(path); 36621918Sjkh } 36721918Sjkh nlen = strlen(name); 36821918Sjkh if (ext && nlen > 4 && name[nlen - 4] == '.' && 36921918Sjkh disttype(name + nlen - 3) == DISTINF) 37021918Sjkh nlen -= 4; 37121918Sjkh if (opt_dir) { 37221918Sjkh path = opt_dir; 37321918Sjkh plen = strlen(path); 37421918Sjkh } else 37521918Sjkh plen = path && (s = strrchr(path, '/')) != NULL ? 37621918Sjkh (size_t)(s - path) : 0; 37721918Sjkh if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf)) 37821918Sjkh return NULL; 37921918Sjkh s = buf; 38021918Sjkh if (plen) { 38121918Sjkh memcpy(s, path, plen); 38221918Sjkh s += plen; 38321918Sjkh *s++ = '/'; 38421918Sjkh } 38521918Sjkh memcpy(s, name, nlen); 38621918Sjkh s += nlen; 38721918Sjkh if (ext) { 38821918Sjkh *s++ = '.'; 38921918Sjkh memcpy(s, ext, 2); 39021918Sjkh s += 2; 39121918Sjkh } 39221918Sjkh *s = 0; 39321918Sjkh return buf; 39421918Sjkh} 39521918Sjkh 396185073Sdelphijstatic const char * 39721918Sjkhstripath(const char *path) 39821918Sjkh{ 39921918Sjkh const char *s; 40021918Sjkh 401185073Sdelphij return ((s = strrchr(path, '/')) != NULL && s[1] ? 40221918Sjkh s + 1 : path); 40321918Sjkh} 40421918Sjkh 40521918Sjkhstatic int 40621918Sjkhdistfile(const char *path) 40721918Sjkh{ 40821918Sjkh const char *s; 40921918Sjkh int type; 41021918Sjkh 41121918Sjkh if ((type = disttype(path)) == DISTMD5 || 41221918Sjkh ((s = strrchr(path, '.')) != NULL && s > path && 41321918Sjkh (type = disttype(s + 1)) != 0)) 41421918Sjkh return type; 41521918Sjkh return 0; 41621918Sjkh} 41721918Sjkh 41821918Sjkhstatic int 41921918Sjkhdisttype(const char *name) 42021918Sjkh{ 42121918Sjkh static const char dname[DISTTYPES][4] = {"md5", "inf"}; 42221918Sjkh int i; 42321918Sjkh 42421918Sjkh for (i = 0; i < DISTTYPES; i++) 42521918Sjkh if (!strcmp(dname[i], name)) 42621918Sjkh return 1 + i; 42721918Sjkh return 0; 42821918Sjkh} 42921918Sjkh 43021918Sjkhstatic int 43121918Sjkhfail(const char *path, const char *msg) 43221918Sjkh{ 43321918Sjkh if (opt_silent) 43421918Sjkh return 0; 43521918Sjkh warnx("%s: %s", path, msg ? msg : sys_errlist[errno]); 43621918Sjkh return 2; 43721918Sjkh} 43821918Sjkh 43921918Sjkhstatic void 44021918Sjkhusage(void) 44121918Sjkh{ 44221918Sjkh fprintf(stderr, 44329449Scharnier "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n"); 44421918Sjkh exit(2); 44521918Sjkh} 446