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