Logo Search packages:      
Sourcecode: ksh version File versions  Download package

cksum.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1992-2007 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                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-10-29 $\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.]"
"[B:scale?Block count scale (bytes per block) override for methods that"
"     include size in the output.  The default is method specific.]#[scale]"
"[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.]"
"[r:bsd?Equivalent to \b--method=bsd --scale=512\b for compatibility with"
"     other \bsum\b(1) implementations.]"

"\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 <modex.h>
#include <fts.h>
#include <error.h>

typedef struct State_s              /* program state        */
{
      int         all;        /* list all items       */
      Sfio_t*           check;            /* check previous output      */
      int         flags;            /* sumprint() SUM_* flags     */
      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   */
      size_t            scale;            /* scale override       */
      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, state->flags|SUM_SCALE, state->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;
      char*       method;
      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.flags = SUM_SIZE;
      state.warn = 1;
      method = 0;
      optinit(&optdisc, optinfo);
      for (;;)
      {
            switch (optget(argv, usage))
            {
            case 'a':
                  state.all = 1;
                  continue;
            case 'b':
                  state.text = 0;
                  continue;
            case 'B':
                  state.scale = opt_info.num;
                  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':
                  method = "bsd";
                  state.scale = 512;
                  state.flags |= SUM_LEGACY;
                  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':
                  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 (method && !(state.sum = sumopen(method)))
            error(3, "%s: unknown checksum method", 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 (!sh_checksig(context) && (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, state.flags|SUM_TOTAL|SUM_SCALE, state.scale);
            sfputc(sfstdout, '\n');
      }
      sumclose(state.sum);
      return error_info.errors != 0;
}

Generated by  Doxygen 1.6.0   Back to index