diff options
Diffstat (limited to 'usr/src/lib/libcmd/common/cksum.c')
-rw-r--r-- | usr/src/lib/libcmd/common/cksum.c | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/usr/src/lib/libcmd/common/cksum.c b/usr/src/lib/libcmd/common/cksum.c new file mode 100644 index 0000000000..25d9ee0c0f --- /dev/null +++ b/usr/src/lib/libcmd/common/cksum.c @@ -0,0 +1,599 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1992-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* Glenn Fowler <gsf@research.att.com> * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Glenn Fowler + * AT&T Research + * + * sum -- list file checksum and size + */ + +static const char usage[] = +"[-?\n@(#)$Id: sum (AT&T Research) 2007-02-07 $\n]" +USAGE_LICENSE +"[+NAME?cksum,md5sum,sum - print file checksum and block count]" +"[+DESCRIPTION?\bsum\b lists the checksum, and for most methods the block" +" count, for each file argument. The standard input is read if there are" +" no \afile\a arguments. \bgetconf UNIVERSE\b determines the default" +" \bsum\b method: \batt\b for the \batt\b universe, \bbsd\b otherwise." +" The default for the other commands is the command name itself. The" +" \batt\b method is a true sum, all others are order dependent.]" +"[+?Method names consist of a leading identifier and 0 or more options" +" separated by -.]" +"[+?\bgetconf PATH_RESOLVE\b determines how symbolic links are handled. This" +" can be explicitly overridden by the \b--logical\b, \b--metaphysical\b," +" and \b--physical\b options below. \bPATH_RESOLVE\b can be one of:]{" +" [+logical?Follow all symbolic links.]" +" [+metaphysical?Follow command argument symbolic links," +" otherwise don't follow.]" +" [+physical?Don't follow symbolic links.]" +"}" + +"[a:all?List the checksum for all files. Use with \b--total\b to list both" +" individual and total checksums and block counts.]" +"[b:binary?Read files in binary mode. This is the default.]" +"[c:check?Each \afile\a is interpreted as the output from a previous \bsum\b." +" If \b--header\b or \b--permissions\b was specified in the previous" +" \bsum\b then the checksum method is automatically determined," +" otherwise \b--method\b must be specified. The listed checksum is" +" compared with the current value and a warning is issued for each file" +" that does not match. If \afile\a was generated by \b--permissions\b" +" then the file mode, user and group are also checked. Empty lines," +" lines starting with \b#<space>\b, or the line \b#\b are ignored. Lines" +" containing no blanks are interpreted as [no]]\aname\a[=\avalue\a]]" +" options:]{" +" [+method=name?Checksum method to apply to subsequent lines.]" +" [+permissions?Subsequent lines were generated with" +" \b--permissions\b.]" +"}" +"[h:header?Print the checksum method as the first output line. Used with" +" \b--check\b and \b--permissions\b.]" +"[l:list?Each \afile\a is interpreted as a list of files, one per line," +" that is checksummed.]" +"[p:permissions?If \b--check\b is not specified then list the file" +" mode, user and group between the checksum and path. User and group" +" matching the caller are output as \b-\b. If \b--check\b is" +" specified then the mode, user and group for each path in \afile\a" +" are updated if necessary to match those in \afile\a. A warning is" +" printed on the standard error for each changed file.]" +"[r:recursive?Recursively checksum the contents of directories.]" +"[s:silent|status?No output for \b--check\b; 0 exit status means all sums" +" matched, non-0 means at least one sum failed to match. Ignored for" +" \b--permissions\b.]" +"[t:total?List only the total checksum and block count of all files." +" \b--all\b \b--total\b lists each checksum and the total. The" +" total checksum and block count may be different from the checksum" +" and block count of the catenation of all files due to partial" +" blocks that may occur when the files are treated separately.]" +"[T:text?Read files in text mode (i.e., treat \b\\r\\n\b as \b\\n\b).]" +"[w!:warn?Warn about invalid \b--check\b lines.]" +"[x:method|algorithm?Specifies the checksum \amethod\a to" +" apply. Parenthesized method options are readonly implementation" +" details.]:[method]{\fmethods\f}" +"[L:logical|follow?Follow symbolic links when traversing directories. The" +" default is determined by \bgetconf PATH_RESOLVE\b.]" +"[H:metaphysical?Follow command argument symbolic links, otherwise don't" +" follow symbolic links when traversing directories. The default is" +" determined by \bgetconf PATH_RESOLVE\b.]" +"[P:physical?Don't follow symbolic links when traversing directories. The" +" default is determined by \bgetconf PATH_RESOLVE\b.]" + +"\n" +"\n[ file ... ]\n" +"\n" + +"[+SEE ALSO?\bgetconf\b(1), \btw\b(1), \buuencode\b(1)]" +; + +#include <cmd.h> +#include <sum.h> +#include <ls.h> +#include <fts.h> +#include <error.h> + +typedef struct State_s /* program state */ +{ + int all; /* list all items */ + Sfio_t* check; /* check previous output */ + gid_t gid; /* caller gid */ + int header; /* list method on output */ + int list; /* list file name too */ + Sum_t* oldsum; /* previous sum method */ + int permissions; /* include mode,uer,group */ + int haveperm; /* permissions in the input */ + int recursive; /* recursively descend dirs */ + unsigned long size; /* combined size of all files */ + int silent; /* silent check, 0 exit if ok */ + int (*sort)(FTSENT* const*, FTSENT* const*); + Sum_t* sum; /* sum method */ + int text; /* \r\n == \n */ + int total; /* list totals only */ + uid_t uid; /* caller uid */ + int warn; /* invalid check line warnings */ +} State_t; + +static void verify(State_t*, char*, char*, Sfio_t*); + +/* + * open path for read mode + */ + +static Sfio_t* +openfile(const char* path, const char* mode) +{ + Sfio_t* sp; + + if (!path || streq(path, "-") || streq(path, "/dev/stdin") || streq(path, "/dev/fd/0")) + { + sp = sfstdin; + sfopen(sp, NiL, mode); + } + else if (!(sp = sfopen(NiL, path, mode))) + error(ERROR_SYSTEM|2, "%s: cannot read", path); + return sp; +} + +/* + * close an openfile() stream + */ + +static int +closefile(Sfio_t* sp) +{ + return sp == sfstdin ? 0 : sfclose(sp); +} + +/* + * compute and print sum on an open file + */ + +static void +pr(State_t* state, Sfio_t* op, Sfio_t* ip, char* file, int perm, struct stat* st, Sfio_t* check) +{ + register char* p; + register char* r; + register char* e; + register int peek; + struct stat ss; + + if (check) + { + state->oldsum = state->sum; + while (p = sfgetr(ip, '\n', 1)) + verify(state, p, file, check); + state->sum = state->oldsum; + if (state->warn && !sfeof(ip)) + error(2, "%s: last line incomplete", file); + return; + } + suminit(state->sum); + if (state->text) + { + peek = 0; + while (p = sfreserve(ip, SF_UNBOUND, 0)) + { + e = p + sfvalue(ip); + if (peek) + { + peek = 0; + if (*p != '\n') + sumblock(state->sum, "\r", 1); + } + while (r = memchr(p, '\r', e - p)) + { + if (++r >= e) + { + e--; + peek = 1; + break; + } + sumblock(state->sum, p, r - p - (*r == '\n')); + p = r; + } + sumblock(state->sum, p, e - p); + } + if (peek) + sumblock(state->sum, "\r", 1); + } + else + while (p = sfreserve(ip, SF_UNBOUND, 0)) + sumblock(state->sum, p, sfvalue(ip)); + if (sfvalue(ip)) + error(ERROR_SYSTEM|2, "%s: read error", file); + sumdone(state->sum); + if (!state->total || state->all) + { + sumprint(state->sum, op, SUM_SIZE|SUM_SCALE); + if (perm >= 0) + { + if (perm) + { + if (!st && fstat(sffileno(ip), st = &ss)) + error(ERROR_SYSTEM|2, "%s: cannot stat", file); + else + sfprintf(sfstdout, " %04o %s %s", + modex(st->st_mode & S_IPERM), + (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) : "-", + (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) : "-"); + } + if (ip != sfstdin) + sfprintf(op, " %s", file); + sfputc(op, '\n'); + } + } +} + +/* + * verify previous sum output + */ + +static void +verify(State_t* state, register char* s, char* check, Sfio_t* rp) +{ + register char* t; + char* e; + char* file; + int attr; + int mode; + int uid; + int gid; + Sfio_t* sp; + struct stat st; + + if (!*s || *s == '#' && (!*(s + 1) || *(s + 1) == ' ' || *(s + 1) == '\t')) + return; + if (t = strchr(s, ' ')) + { + if ((t - s) > 10 || !(file = strchr(t + 1, ' '))) + file = t; + *file++ = 0; + attr = 0; + if ((mode = strtol(file, &e, 8)) && *e == ' ' && (e - file) == 4) + { + mode = modei(mode); + if (t = strchr(++e, ' ')) + { + if (*e == '-' && (t - e) == 1) + uid = -1; + else + { + *t = 0; + uid = struid(e); + *t = ' '; + } + if (e = strchr(++t, ' ')) + { + if (*t == '-' && (e - t) == 1) + gid = -1; + else + { + *e = 0; + gid = struid(t); + *e = ' '; + } + file = e + 1; + attr = 1; + } + } + } + if (sp = openfile(file, "rb")) + { + pr(state, rp, sp, file, -1, NiL, NiL); + if (!(t = sfstruse(rp))) + error(ERROR_SYSTEM|3, "out of space"); + if (!streq(s, t)) + { + if (state->silent) + error_info.errors++; + else + error(2, "%s: checksum changed", file); + } + else if (attr) + { + if (fstat(sffileno(sp), &st)) + { + if (state->silent) + error_info.errors++; + else + error(ERROR_SYSTEM|2, "%s: cannot stat", file); + } + else + { + if (uid < 0 || uid == st.st_uid) + uid = -1; + else if (!state->permissions) + { + if (state->silent) + error_info.errors++; + else + error(2, "%s: uid should be %s", file, fmtuid(uid)); + } + if (gid < 0 || gid == st.st_gid) + gid = -1; + else if (!state->permissions) + { + if (state->silent) + error_info.errors++; + else + error(2, "%s: gid should be %s", file, fmtgid(gid)); + } + if (state->permissions && (uid >= 0 || gid >= 0)) + { + if (chown(file, uid, gid) < 0) + { + if (uid < 0) + error(ERROR_SYSTEM|2, "%s: cannot change group to %s", file, fmtgid(gid)); + else if (gid < 0) + error(ERROR_SYSTEM|2, "%s: cannot change user to %s", file, fmtuid(uid)); + else + error(ERROR_SYSTEM|2, "%s: cannot change user to %s and group to %s", file, fmtuid(uid), fmtgid(gid)); + } + else + { + if (uid < 0) + error(1, "%s: changed group to %s", file, fmtgid(gid)); + else if (gid < 0) + error(1, "%s: changed user to %s", file, fmtuid(uid)); + else + error(1, "%s: changed user to %s and group to %s", file, fmtuid(uid), fmtgid(gid)); + } + } + if ((st.st_mode & S_IPERM) ^ mode) + { + if (state->permissions) + { + if (chmod(file, mode) < 0) + error(ERROR_SYSTEM|2, "%s: cannot change mode to %s", file, fmtmode(mode, 0)); + else + error(ERROR_SYSTEM|1, "%s: changed mode to %s", file, fmtmode(mode, 0)); + } + else if (state->silent) + error_info.errors++; + else + error(2, "%s: mode should be %s", file, fmtmode(mode, 0)); + } + } + } + closefile(sp); + } + } + else if (strneq(s, "method=", 7)) + { + s += 7; + if (state->sum != state->oldsum) + sumclose(state->sum); + if (!(state->sum = sumopen(s))) + error(3, "%s: %s: unknown checksum method", check, s); + } + else if (streq(s, "permissions")) + state->haveperm = 1; + else + error(1, "%s: %s: unknown option", check, s); +} + +/* + * sum the list of files in lp + */ + +static void +list(State_t* state, register Sfio_t* lp) +{ + register char* file; + register Sfio_t* sp; + + while (file = sfgetr(lp, '\n', 1)) + if (sp = openfile(file, state->check ? "rt" : "rb")) + { + pr(state, sfstdout, sp, file, state->permissions, NiL, state->check); + closefile(sp); + } +} + +/* + * order child entries + */ + +static int +order(FTSENT* const* f1, FTSENT* const* f2) +{ + return strcoll((*f1)->fts_name, (*f2)->fts_name); +} + +/* + * optget() info discipline function + */ + +static int +optinfo(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp) +{ + if (streq(s, "methods")) + return sumusage(sp); + return 0; +} + +int +b_cksum(int argc, register char** argv, void* context) +{ + register int flags; + register char* s; + char* file; + Sfio_t* sp; + FTS* fts; + FTSENT* ent; + Optdisc_t optdisc; + State_t state; + + cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY); + memset(&state, 0, sizeof(state)); + setlocale(LC_ALL, ""); + flags = fts_flags() | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR; + state.warn = 1; + optinit(&optdisc, optinfo); + for (;;) + { + switch (optget(argv, usage)) + { + case 'a': + state.all = 1; + continue; + case 'b': + state.text = 0; + continue; + case 'c': + if (!(state.check = sfstropen())) + error(3, "out of space [check]"); + continue; + case 'h': + state.header = 1; + continue; + case 'l': + state.list = 1; + continue; + case 'p': + state.permissions = 1; + continue; + case 'r': + flags &= ~FTS_TOP; + state.recursive = 1; + state.sort = order; + continue; + case 's': + state.silent = opt_info.num; + continue; + case 't': + state.total = 1; + continue; + case 'w': + state.warn = opt_info.num; + continue; + case 'x': + if (!(state.sum = sumopen(opt_info.arg))) + error(3, "%s: unknown checksum method", opt_info.arg); + continue; + case 'H': + flags |= FTS_META|FTS_PHYSICAL; + continue; + case 'L': + flags &= ~(FTS_META|FTS_PHYSICAL); + continue; + case 'P': + flags &= ~FTS_META; + flags |= FTS_PHYSICAL; + continue; + case 'T': + state.text = 1; + continue; + case '?': + error(ERROR_USAGE|4, "%s", opt_info.arg); + break; + case ':': + error(2, "%s", opt_info.arg); + break; + } + break; + } + argv += opt_info.index; + if (error_info.errors) + error(ERROR_USAGE|4, "%s", optusage(NiL)); + + /* + * check the method + */ + + if (!state.sum && !(state.sum = sumopen(error_info.id)) && !(state.sum = sumopen(astconf("UNIVERSE", NiL, NiL)))) + state.sum = sumopen(NiL); + + /* + * do it + */ + + if (state.permissions) + { + state.uid = geteuid(); + state.gid = getegid(); + state.silent = 0; + } + if (!state.check && (state.header || state.permissions)) + { + sfprintf(sfstdout, "method=%s\n", state.sum->name); + if (state.permissions) + sfprintf(sfstdout, "permissions\n"); + } + if (state.list) + { + if (*argv) + { + while (file = *argv++) + if (sp = openfile(file, "rt")) + { + list(&state, sp); + closefile(sp); + } + } + else if (sp = openfile(NiL, "rt")) + { + list(&state, sp); + closefile(sp); + } + } + else if (!*argv && !state.recursive) + pr(&state, sfstdout, sfstdin, "/dev/stdin", state.permissions, NiL, state.check); + else if (!(fts = fts_open(argv, flags, state.sort))) + error(ERROR_system(1), "%s: not found", *argv); + else + { + while (!cmdquit() && (ent = fts_read(fts))) + switch (ent->fts_info) + { + case FTS_SL: + if (!(flags & FTS_PHYSICAL) || (flags & FTS_META) && ent->fts_level == 1) + fts_set(NiL, ent, FTS_FOLLOW); + break; + case FTS_F: + if (sp = openfile(ent->fts_accpath, "rb")) + { + pr(&state, sfstdout, sp, ent->fts_path, state.permissions, ent->fts_statp, state.check); + closefile(sp); + } + break; + case FTS_DC: + error(ERROR_warn(0), "%s: directory causes cycle", ent->fts_accpath); + break; + case FTS_DNR: + error(ERROR_system(0), "%s: cannot read directory", ent->fts_accpath); + break; + case FTS_DNX: + error(ERROR_system(0), "%s: cannot search directory", ent->fts_accpath); + break; + case FTS_NS: + error(ERROR_system(0), "%s: not found", ent->fts_accpath); + break; + } + fts_close(fts); + } + if (state.total) + { + sumprint(state.sum, sfstdout, SUM_TOTAL|SUM_SIZE|SUM_SCALE); + sfputc(sfstdout, '\n'); + } + sumclose(state.sum); + return error_info.errors != 0; +} |