1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23 * Glenn Fowler
24 * AT&T Research
25 *
26 * sum -- list file checksum and size
27 */
28
29 static const char usage[] =
30 "[-?\n@(#)$Id: sum (AT&T Research) 2009-11-28 $\n]"
31 USAGE_LICENSE
32 "[+NAME?cksum,md5sum,sum - print file checksum and block count]"
33 "[+DESCRIPTION?\bsum\b lists the checksum, and for most methods the block"
34 " count, for each file argument. The standard input is read if there are"
35 " no \afile\a arguments. \bgetconf UNIVERSE\b determines the default"
36 " \bsum\b method: \batt\b for the \batt\b universe, \bbsd\b otherwise."
37 " The default for the other commands is the command name itself. The"
38 " \batt\b method is a true sum, all others are order dependent.]"
39 "[+?Method names consist of a leading identifier and 0 or more options"
40 " separated by -.]"
41 "[+?\bgetconf PATH_RESOLVE\b determines how symbolic links are handled. This"
42 " can be explicitly overridden by the \b--logical\b, \b--metaphysical\b,"
43 " and \b--physical\b options below. \bPATH_RESOLVE\b can be one of:]{"
44 " [+logical?Follow all symbolic links.]"
45 " [+metaphysical?Follow command argument symbolic links,"
46 " otherwise don't follow.]"
47 " [+physical?Don't follow symbolic links.]"
48 "}"
49
50 "[a:all?List the checksum for all files. Use with \b--total\b to list both"
51 " individual and total checksums and block counts.]"
52 "[b:binary?Read files in binary mode. This is the default.]"
53 "[B:scale?Block count scale (bytes per block) override for methods that"
54 " include size in the output. The default is method specific.]#[scale]"
55 "[c:check?Each \afile\a is interpreted as the output from a previous \bsum\b."
56 " If \b--header\b or \b--permissions\b was specified in the previous"
57 " \bsum\b then the checksum method is automatically determined,"
58 " otherwise \b--method\b must be specified. The listed checksum is"
59 " compared with the current value and a warning is issued for each file"
60 " that does not match. If \afile\a was generated by \b--permissions\b"
61 " then the file mode, user and group are also checked. Empty lines,"
62 " lines starting with \b#<space>\b, or the line \b#\b are ignored. Lines"
63 " containing no blanks are interpreted as [no]]\aname\a[=\avalue\a]]"
64 " options:]{"
65 " [+method=name?Checksum method to apply to subsequent lines.]"
66 " [+permissions?Subsequent lines were generated with"
67 " \b--permissions\b.]"
68 "}"
69 "[h:header?Print the checksum method as the first output line. Used with"
70 " \b--check\b and \b--permissions\b.]"
71 "[l:list?Each \afile\a is interpreted as a list of files, one per line,"
72 " that is checksummed.]"
73 "[p:permissions?If \b--check\b is not specified then list the file"
74 " mode, user and group between the checksum and path. User and group"
75 " matching the caller are output as \b-\b. If \b--check\b is"
76 " specified then the mode, user and group for each path in \afile\a"
77 " are updated if necessary to match those in \afile\a. A warning is"
78 " printed on the standard error for each changed file.]"
79 "[R:recursive?Recursively checksum the contents of directories.]"
80 "[S:silent|status?No output for \b--check\b; 0 exit status means all sums"
81 " matched, non-0 means at least one sum failed to match. Ignored for"
82 " \b--permissions\b.]"
83 "[t:total?List only the total checksum and block count of all files."
84 " \b--all\b \b--total\b lists each checksum and the total. The"
85 " total checksum and block count may be different from the checksum"
86 " and block count of the catenation of all files due to partial"
87 " blocks that may occur when the files are treated separately.]"
88 "[T:text?Read files in text mode (i.e., treat \b\\r\\n\b as \b\\n\b).]"
89 "[w!:warn?Warn about invalid \b--check\b lines.]"
90 "[x:method|algorithm?Specifies the checksum \amethod\a to"
91 " apply. Parenthesized method options are readonly implementation"
92 " details.]:[method]{\fmethods\f}"
93 "[L:logical|follow?Follow symbolic links when traversing directories. The"
94 " default is determined by \bgetconf PATH_RESOLVE\b.]"
95 "[H:metaphysical?Follow command argument symbolic links, otherwise don't"
96 " follow symbolic links when traversing directories. The default is"
97 " determined by \bgetconf PATH_RESOLVE\b.]"
98 "[P:physical?Don't follow symbolic links when traversing directories. The"
99 " default is determined by \bgetconf PATH_RESOLVE\b.]"
100 "[r:bsd?Equivalent to \b--method=bsd --scale=512\b for compatibility with"
101 " other \bsum\b(1) implementations.]"
102 "[s:sysv?Equivalent to \b--method=sys5\b for compatibility with other"
103 " \bsum\b(1) implementations.]"
104
105 "\n"
106 "\n[ file ... ]\n"
107 "\n"
108
109 "[+SEE ALSO?\bgetconf\b(1), \btw\b(1), \buuencode\b(1)]"
110 ;
111
112 #include <cmd.h>
113 #include <sum.h>
114 #include <ls.h>
115 #include <modex.h>
116 #include <fts_fix.h>
117 #include <error.h>
118
119 typedef struct State_s /* program state */
120 {
121 int all; /* list all items */
122 Sfio_t* check; /* check previous output */
123 int flags; /* sumprint() SUM_* flags */
124 gid_t gid; /* caller gid */
125 int header; /* list method on output */
126 int list; /* list file name too */
127 Sum_t* oldsum; /* previous sum method */
128 int permissions; /* include mode,uer,group */
129 int haveperm; /* permissions in the input */
130 int recursive; /* recursively descend dirs */
131 size_t scale; /* scale override */
132 unsigned long size; /* combined size of all files */
133 int silent; /* silent check, 0 exit if ok */
134 int (*sort)(FTSENT* const*, FTSENT* const*);
135 Sum_t* sum; /* sum method */
136 int text; /* \r\n == \n */
137 int total; /* list totals only */
138 uid_t uid; /* caller uid */
139 int warn; /* invalid check line warnings */
140 } State_t;
141
142 static void verify(State_t*, char*, char*, Sfio_t*);
143
144 /*
145 * open path for read mode
146 */
147
148 static Sfio_t*
openfile(const char * path,const char * mode)149 openfile(const char* path, const char* mode)
150 {
151 Sfio_t* sp;
152
153 if (!path || streq(path, "-") || streq(path, "/dev/stdin") || streq(path, "/dev/fd/0"))
154 {
155 sp = sfstdin;
156 sfopen(sp, NiL, mode);
157 }
158 else if (!(sp = sfopen(NiL, path, mode)))
159 error(ERROR_SYSTEM|2, "%s: cannot read", path);
160 return sp;
161 }
162
163 /*
164 * close an openfile() stream
165 */
166
167 static int
closefile(Sfio_t * sp)168 closefile(Sfio_t* sp)
169 {
170 return sp == sfstdin ? 0 : sfclose(sp);
171 }
172
173 /*
174 * compute and print sum on an open file
175 */
176
177 static void
pr(State_t * state,Sfio_t * op,Sfio_t * ip,char * file,int perm,struct stat * st,Sfio_t * check)178 pr(State_t* state, Sfio_t* op, Sfio_t* ip, char* file, int perm, struct stat* st, Sfio_t* check)
179 {
180 register char* p;
181 register char* r;
182 register char* e;
183 register int peek;
184 struct stat ss;
185
186 if (check)
187 {
188 state->oldsum = state->sum;
189 while (p = sfgetr(ip, '\n', 1))
190 verify(state, p, file, check);
191 state->sum = state->oldsum;
192 if (state->warn && !sfeof(ip))
193 error(2, "%s: last line incomplete", file);
194 return;
195 }
196 suminit(state->sum);
197 if (state->text)
198 {
199 peek = 0;
200 while (p = sfreserve(ip, SF_UNBOUND, 0))
201 {
202 e = p + sfvalue(ip);
203 if (peek)
204 {
205 peek = 0;
206 if (*p != '\n')
207 sumblock(state->sum, "\r", 1);
208 }
209 while (r = memchr(p, '\r', e - p))
210 {
211 if (++r >= e)
212 {
213 e--;
214 peek = 1;
215 break;
216 }
217 sumblock(state->sum, p, r - p - (*r == '\n'));
218 p = r;
219 }
220 sumblock(state->sum, p, e - p);
221 }
222 if (peek)
223 sumblock(state->sum, "\r", 1);
224 }
225 else
226 while (p = sfreserve(ip, SF_UNBOUND, 0))
227 sumblock(state->sum, p, sfvalue(ip));
228 if (sfvalue(ip))
229 error(ERROR_SYSTEM|2, "%s: read error", file);
230 sumdone(state->sum);
231 if (!state->total || state->all)
232 {
233 sumprint(state->sum, op, state->flags|SUM_SCALE, state->scale);
234 if (perm >= 0)
235 {
236 if (perm)
237 {
238 if (!st && fstat(sffileno(ip), st = &ss))
239 error(ERROR_SYSTEM|2, "%s: cannot stat", file);
240 else
241 sfprintf(sfstdout, " %04o %s %s",
242 modex(st->st_mode & S_IPERM),
243 (st->st_uid != state->uid && ((st->st_mode & S_ISUID) || (st->st_mode & S_IRUSR) && !(st->st_mode & (S_IRGRP|S_IROTH)) || (st->st_mode & S_IXUSR) && !(st->st_mode & (S_IXGRP|S_IXOTH)))) ? fmtuid(st->st_uid) : "-",
244 (st->st_gid != state->gid && ((st->st_mode & S_ISGID) || (st->st_mode & S_IRGRP) && !(st->st_mode & S_IROTH) || (st->st_mode & S_IXGRP) && !(st->st_mode & S_IXOTH))) ? fmtgid(st->st_gid) : "-");
245 }
246 if (ip != sfstdin)
247 sfprintf(op, " %s", file);
248 sfputc(op, '\n');
249 }
250 }
251 }
252
253 /*
254 * verify previous sum output
255 */
256
257 static void
verify(State_t * state,register char * s,char * check,Sfio_t * rp)258 verify(State_t* state, register char* s, char* check, Sfio_t* rp)
259 {
260 register char* t;
261 char* e;
262 char* file;
263 int attr;
264 int mode;
265 int uid;
266 int gid;
267 Sfio_t* sp;
268 struct stat st;
269
270 if (!*s || *s == '#' && (!*(s + 1) || *(s + 1) == ' ' || *(s + 1) == '\t'))
271 return;
272 if (t = strchr(s, ' '))
273 {
274 if ((t - s) > 10 || !(file = strchr(t + 1, ' ')))
275 file = t;
276 *file++ = 0;
277 attr = 0;
278 if ((mode = strtol(file, &e, 8)) && *e == ' ' && (e - file) == 4)
279 {
280 mode = modei(mode);
281 if (t = strchr(++e, ' '))
282 {
283 if (*e == '-' && (t - e) == 1)
284 uid = -1;
285 else
286 {
287 *t = 0;
288 uid = struid(e);
289 *t = ' ';
290 }
291 if (e = strchr(++t, ' '))
292 {
293 if (*t == '-' && (e - t) == 1)
294 gid = -1;
295 else
296 {
297 *e = 0;
298 gid = struid(t);
299 *e = ' ';
300 }
301 file = e + 1;
302 attr = 1;
303 }
304 }
305 }
306 if (sp = openfile(file, "rb"))
307 {
308 pr(state, rp, sp, file, -1, NiL, NiL);
309 if (!(t = sfstruse(rp)))
310 error(ERROR_SYSTEM|3, "out of space");
311 if (!streq(s, t))
312 {
313 if (state->silent)
314 error_info.errors++;
315 else
316 error(2, "%s: checksum changed", file);
317 }
318 else if (attr)
319 {
320 if (fstat(sffileno(sp), &st))
321 {
322 if (state->silent)
323 error_info.errors++;
324 else
325 error(ERROR_SYSTEM|2, "%s: cannot stat", file);
326 }
327 else
328 {
329 if (uid < 0 || uid == st.st_uid)
330 uid = -1;
331 else if (!state->permissions)
332 {
333 if (state->silent)
334 error_info.errors++;
335 else
336 error(2, "%s: uid should be %s", file, fmtuid(uid));
337 }
338 if (gid < 0 || gid == st.st_gid)
339 gid = -1;
340 else if (!state->permissions)
341 {
342 if (state->silent)
343 error_info.errors++;
344 else
345 error(2, "%s: gid should be %s", file, fmtgid(gid));
346 }
347 if (state->permissions && (uid >= 0 || gid >= 0))
348 {
349 if (chown(file, uid, gid) < 0)
350 {
351 if (uid < 0)
352 error(ERROR_SYSTEM|2, "%s: cannot change group to %s", file, fmtgid(gid));
353 else if (gid < 0)
354 error(ERROR_SYSTEM|2, "%s: cannot change user to %s", file, fmtuid(uid));
355 else
356 error(ERROR_SYSTEM|2, "%s: cannot change user to %s and group to %s", file, fmtuid(uid), fmtgid(gid));
357 }
358 else
359 {
360 if (uid < 0)
361 error(1, "%s: changed group to %s", file, fmtgid(gid));
362 else if (gid < 0)
363 error(1, "%s: changed user to %s", file, fmtuid(uid));
364 else
365 error(1, "%s: changed user to %s and group to %s", file, fmtuid(uid), fmtgid(gid));
366 }
367 }
368 if ((st.st_mode & S_IPERM) ^ mode)
369 {
370 if (state->permissions)
371 {
372 if (chmod(file, mode) < 0)
373 error(ERROR_SYSTEM|2, "%s: cannot change mode to %s", file, fmtmode(mode, 0));
374 else
375 error(ERROR_SYSTEM|1, "%s: changed mode to %s", file, fmtmode(mode, 0));
376 }
377 else if (state->silent)
378 error_info.errors++;
379 else
380 error(2, "%s: mode should be %s", file, fmtmode(mode, 0));
381 }
382 }
383 }
384 closefile(sp);
385 }
386 }
387 else if (strneq(s, "method=", 7))
388 {
389 s += 7;
390 if (state->sum != state->oldsum)
391 sumclose(state->sum);
392 if (!(state->sum = sumopen(s)))
393 error(3, "%s: %s: unknown checksum method", check, s);
394 }
395 else if (streq(s, "permissions"))
396 state->haveperm = 1;
397 else
398 error(1, "%s: %s: unknown option", check, s);
399 }
400
401 /*
402 * sum the list of files in lp
403 */
404
405 static void
list(State_t * state,register Sfio_t * lp)406 list(State_t* state, register Sfio_t* lp)
407 {
408 register char* file;
409 register Sfio_t* sp;
410
411 while (file = sfgetr(lp, '\n', 1))
412 if (sp = openfile(file, state->check ? "rt" : "rb"))
413 {
414 pr(state, sfstdout, sp, file, state->permissions, NiL, state->check);
415 closefile(sp);
416 }
417 }
418
419 /*
420 * order child entries
421 */
422
423 static int
order(FTSENT * const * f1,FTSENT * const * f2)424 order(FTSENT* const* f1, FTSENT* const* f2)
425 {
426 return strcoll((*f1)->fts_name, (*f2)->fts_name);
427 }
428
429 /*
430 * optget() info discipline function
431 */
432
433 static int
optinfo(Opt_t * op,Sfio_t * sp,const char * s,Optdisc_t * dp)434 optinfo(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp)
435 {
436 if (streq(s, "methods"))
437 return sumusage(sp);
438 return 0;
439 }
440
441 int
b_cksum(int argc,register char ** argv,void * context)442 b_cksum(int argc, register char** argv, void* context)
443 {
444 register int flags;
445 char* file;
446 char* method;
447 Sfio_t* sp;
448 FTS* fts;
449 FTSENT* ent;
450 int logical;
451 Optdisc_t optdisc;
452 State_t state;
453
454 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
455 memset(&state, 0, sizeof(state));
456 flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER;
457 state.flags = SUM_SIZE;
458 state.warn = 1;
459 logical = 1;
460 method = 0;
461 optinit(&optdisc, optinfo);
462 for (;;)
463 {
464 switch (optget(argv, usage))
465 {
466 case 'a':
467 state.all = 1;
468 continue;
469 case 'b':
470 state.text = 0;
471 continue;
472 case 'B':
473 state.scale = opt_info.num;
474 continue;
475 case 'c':
476 if (!(state.check = sfstropen()))
477 error(3, "out of space [check]");
478 continue;
479 case 'h':
480 state.header = 1;
481 continue;
482 case 'l':
483 state.list = 1;
484 continue;
485 case 'p':
486 state.permissions = 1;
487 continue;
488 case 'r':
489 method = "bsd";
490 state.scale = 512;
491 state.flags |= SUM_LEGACY;
492 continue;
493 case 'R':
494 flags &= ~FTS_TOP;
495 state.recursive = 1;
496 state.sort = order;
497 logical = 0;
498 continue;
499 case 's':
500 method = "sys5";
501 continue;
502 case 'S':
503 state.silent = opt_info.num;
504 continue;
505 case 't':
506 state.total = 1;
507 continue;
508 case 'w':
509 state.warn = opt_info.num;
510 continue;
511 case 'x':
512 method = opt_info.arg;
513 continue;
514 case 'H':
515 flags |= FTS_META|FTS_PHYSICAL;
516 logical = 0;
517 continue;
518 case 'L':
519 flags &= ~(FTS_META|FTS_PHYSICAL);
520 logical = 0;
521 continue;
522 case 'P':
523 flags &= ~FTS_META;
524 flags |= FTS_PHYSICAL;
525 logical = 0;
526 continue;
527 case 'T':
528 state.text = 1;
529 continue;
530 case '?':
531 error(ERROR_USAGE|4, "%s", opt_info.arg);
532 break;
533 case ':':
534 error(2, "%s", opt_info.arg);
535 break;
536 }
537 break;
538 }
539 argv += opt_info.index;
540 if (error_info.errors)
541 error(ERROR_USAGE|4, "%s", optusage(NiL));
542
543 /*
544 * check the method
545 */
546
547 if (method && !(state.sum = sumopen(method)))
548 error(3, "%s: unknown checksum method", method);
549 if (!state.sum && !(state.sum = sumopen(error_info.id)) && !(state.sum = sumopen(astconf("UNIVERSE", NiL, NiL))))
550 state.sum = sumopen(NiL);
551
552 /*
553 * do it
554 */
555
556 if (logical)
557 {
558 flags &= ~(FTS_META|FTS_PHYSICAL);
559 flags |= FTS_SEEDOTDIR;
560 }
561 if (state.permissions)
562 {
563 state.uid = geteuid();
564 state.gid = getegid();
565 state.silent = 0;
566 }
567 if (!state.check && (state.header || state.permissions))
568 {
569 sfprintf(sfstdout, "method=%s\n", state.sum->name);
570 if (state.permissions)
571 sfprintf(sfstdout, "permissions\n");
572 }
573 if (state.list)
574 {
575 if (*argv)
576 {
577 while (file = *argv++)
578 if (sp = openfile(file, "rt"))
579 {
580 list(&state, sp);
581 closefile(sp);
582 }
583 }
584 else if (sp = openfile(NiL, "rt"))
585 {
586 list(&state, sp);
587 closefile(sp);
588 }
589 }
590 else if (!*argv && !state.recursive)
591 pr(&state, sfstdout, sfstdin, "/dev/stdin", state.permissions, NiL, state.check);
592 else if (!(fts = fts_open(argv, flags, state.sort)))
593 error(ERROR_system(1), "%s: not found", *argv);
594 else
595 {
596 while (!sh_checksig(context) && (ent = fts_read(fts)))
597 switch (ent->fts_info)
598 {
599 case FTS_SL:
600 if (!(flags & FTS_PHYSICAL) || (flags & FTS_META) && ent->fts_level == 1)
601 fts_set(NiL, ent, FTS_FOLLOW);
602 break;
603 case FTS_F:
604 if (sp = openfile(ent->fts_accpath, "rb"))
605 {
606 pr(&state, sfstdout, sp, ent->fts_path, state.permissions, ent->fts_statp, state.check);
607 closefile(sp);
608 }
609 break;
610 case FTS_DC:
611 error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_accpath);
612 break;
613 case FTS_DNR:
614 error(ERROR_system(0), "%s: cannot read directory", ent->fts_accpath);
615 break;
616 case FTS_DNX:
617 error(ERROR_system(0), "%s: cannot search directory", ent->fts_accpath);
618 break;
619 case FTS_NS:
620 error(ERROR_system(0), "%s: not found", ent->fts_accpath);
621 break;
622 }
623 fts_close(fts);
624 }
625 if (state.total)
626 {
627 sumprint(state.sum, sfstdout, state.flags|SUM_TOTAL|SUM_SCALE, state.scale);
628 sfputc(sfstdout, '\n');
629 }
630 sumclose(state.sum);
631 return error_info.errors != 0;
632 }
633