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