1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2009 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-07-02 $\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.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*
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
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
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
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
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
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
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
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 	setlocale(LC_ALL, "");
457 	flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
458 	state.flags = SUM_SIZE;
459 	state.warn = 1;
460 	logical = 1;
461 	method = 0;
462 	optinit(&optdisc, optinfo);
463 	for (;;)
464 	{
465 		switch (optget(argv, usage))
466 		{
467 		case 'a':
468 			state.all = 1;
469 			continue;
470 		case 'b':
471 			state.text = 0;
472 			continue;
473 		case 'B':
474 			state.scale = opt_info.num;
475 			continue;
476 		case 'c':
477 			if (!(state.check = sfstropen()))
478 				error(3, "out of space [check]");
479 			continue;
480 		case 'h':
481 			state.header = 1;
482 			continue;
483 		case 'l':
484 			state.list = 1;
485 			continue;
486 		case 'p':
487 			state.permissions = 1;
488 			continue;
489 		case 'r':
490 			method = "bsd";
491 			state.scale = 512;
492 			state.flags |= SUM_LEGACY;
493 			continue;
494 		case 'R':
495 			flags &= ~FTS_TOP;
496 			state.recursive = 1;
497 			state.sort = order;
498 			logical = 0;
499 			continue;
500 		case 's':
501 			method = "sys5";
502 			continue;
503 		case 'S':
504 			state.silent = opt_info.num;
505 			continue;
506 		case 't':
507 			state.total = 1;
508 			continue;
509 		case 'w':
510 			state.warn = opt_info.num;
511 			continue;
512 		case 'x':
513 			method = opt_info.arg;
514 			continue;
515 		case 'H':
516 			flags |= FTS_META|FTS_PHYSICAL;
517 			logical = 0;
518 			continue;
519 		case 'L':
520 			flags &= ~(FTS_META|FTS_PHYSICAL);
521 			logical = 0;
522 			continue;
523 		case 'P':
524 			flags &= ~FTS_META;
525 			flags |= FTS_PHYSICAL;
526 			logical = 0;
527 			continue;
528 		case 'T':
529 			state.text = 1;
530 			continue;
531 		case '?':
532 			error(ERROR_USAGE|4, "%s", opt_info.arg);
533 			break;
534 		case ':':
535 			error(2, "%s", opt_info.arg);
536 			break;
537 		}
538 		break;
539 	}
540 	argv += opt_info.index;
541 	if (error_info.errors)
542 		error(ERROR_USAGE|4, "%s", optusage(NiL));
543 
544 	/*
545 	 * check the method
546 	 */
547 
548 	if (method && !(state.sum = sumopen(method)))
549 		error(3, "%s: unknown checksum method", method);
550 	if (!state.sum && !(state.sum = sumopen(error_info.id)) && !(state.sum = sumopen(astconf("UNIVERSE", NiL, NiL))))
551 		state.sum = sumopen(NiL);
552 
553 	/*
554 	 * do it
555 	 */
556 
557 	if (logical)
558 		flags &= ~(FTS_META|FTS_PHYSICAL);
559 	if (state.permissions)
560 	{
561 		state.uid = geteuid();
562 		state.gid = getegid();
563 		state.silent = 0;
564 	}
565 	if (!state.check && (state.header || state.permissions))
566 	{
567 		sfprintf(sfstdout, "method=%s\n", state.sum->name);
568 		if (state.permissions)
569 			sfprintf(sfstdout, "permissions\n");
570 	}
571 	if (state.list)
572 	{
573 		if (*argv)
574 		{
575 			while (file = *argv++)
576 				if (sp = openfile(file, "rt"))
577 				{
578 					list(&state, sp);
579 					closefile(sp);
580 				}
581 		}
582 		else if (sp = openfile(NiL, "rt"))
583 		{
584 			list(&state, sp);
585 			closefile(sp);
586 		}
587 	}
588 	else if (!*argv && !state.recursive)
589 		pr(&state, sfstdout, sfstdin, "/dev/stdin", state.permissions, NiL, state.check);
590 	else if (!(fts = fts_open(argv, flags, state.sort)))
591 		error(ERROR_system(1), "%s: not found", *argv);
592 	else
593 	{
594 		while (!sh_checksig(context) && (ent = fts_read(fts)))
595 			switch (ent->fts_info)
596 			{
597 			case FTS_SL:
598 				if (!(flags & FTS_PHYSICAL) || (flags & FTS_META) && ent->fts_level == 1)
599 					fts_set(NiL, ent, FTS_FOLLOW);
600 				break;
601 			case FTS_F:
602 				if (sp = openfile(ent->fts_accpath, "rb"))
603 				{
604 					pr(&state, sfstdout, sp, ent->fts_path, state.permissions, ent->fts_statp, state.check);
605 					closefile(sp);
606 				}
607 				break;
608 			case FTS_DC:
609 				error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_accpath);
610 				break;
611 			case FTS_DNR:
612 				error(ERROR_system(0), "%s: cannot read directory", ent->fts_accpath);
613 				break;
614 			case FTS_DNX:
615 				error(ERROR_system(0), "%s: cannot search directory", ent->fts_accpath);
616 				break;
617 			case FTS_NS:
618 				error(ERROR_system(0), "%s: not found", ent->fts_accpath);
619 				break;
620 			}
621 		fts_close(fts);
622 	}
623 	if (state.total)
624 	{
625 		sumprint(state.sum, sfstdout, state.flags|SUM_TOTAL|SUM_SCALE, state.scale);
626 		sfputc(sfstdout, '\n');
627 	}
628 	sumclose(state.sum);
629 	return error_info.errors != 0;
630 }
631