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