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
29static const char usage[] =
30"[-?\n@(#)$Id: sum (AT&T Research) 2009-11-28 $\n]"
31USAGE_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
119typedef 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
142static void	verify(State_t*, char*, char*, Sfio_t*);
143
144/*
145 * open path for read mode
146 */
147
148static Sfio_t*
149openfile(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
167static int
168closefile(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
177static void
178pr(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
257static void
258verify(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
405static void
406list(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
423static int
424order(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
433static int
434optinfo(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
441int
442b_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