74f298cc4aaec77654879ad72b50fe22bc54fecc
[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  * $FreeBSD: src/usr.sbin/ckdist/ckdist.c,v 1.3.2.1 2000/07/01 10:37:21 ps Exp $
28  * $DragonFly: src/usr.sbin/ckdist/ckdist.c,v 1.2 2003/06/17 04:29:52 dillon Exp $
29  */
30
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <err.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <fts.h>
37 #include <md5.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 extern 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
64 static const char *opt_dir;     /* where to look for components */
65 static const char *opt_name;    /* name for accessing components */
66 static int opt_all;             /* report on all components */
67 static int opt_ignore;          /* ignore missing components */
68 static int opt_recurse;         /* search directories recursively */
69 static int opt_silent;          /* silent about inaccessible files */
70 static int opt_type;            /* dist type: md5 or inf */
71 static int opt_exist;           /* just verify existence */
72
73 static int ckdist(const char *path, int type);
74 static int chkmd5(FILE * fp, const char *path);
75 static int chkinf(FILE * fp, const char *path);
76 static int report(const char *path, const char *name, int error);
77 static const char *distname(const char *path, const char *name,
78                             const char *ext);
79 static char *stripath(const char *path);
80 static int distfile(const char *path);
81 static int disttype(const char *name);
82 static int fail(const char *path, const char *msg);
83 static void usage(void);
84
85 int
86 main(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, "%s", 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
179 static int
180 ckdist(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("%s", path);
211     if (fp != stdin && fclose(fp))
212         err(2, "%s", path);
213     return rval;
214 }
215
216 static int
217 chkmd5(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, "%s", 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
261 static int
262 chkinf(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, "%s", 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
313 static int
314 report(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
346 static const char *
347 distname(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
389 static char *
390 stripath(const char *path)
391 {
392     const char *s;
393
394     return (char *)((s = strrchr(path, '/')) != NULL && s[1] ? 
395                     s + 1 : path);
396 }
397
398 static int
399 distfile(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
411 static int
412 disttype(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
423 static int
424 fail(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
432 static void
433 usage(void)
434 {
435     fprintf(stderr,
436             "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
437     exit(2);
438 }