1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2003 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <unistd.h> 29 #include "bart.h" 30 31 static int compare_manifests(FILE *rulesfile, char *control, char *test, 32 boolean_t prog_fmt, uint_t flags); 33 static void extract_fname_ftype(char *line, char *fname, char *type); 34 static int report_add(char *fname, char *type); 35 static int report_delete(char *fname, char *type); 36 static int evaluate_differences(char *control_line, char *test_line, 37 boolean_t prog_fmt, int flags); 38 static void report_error(char *fname, char *type, char *ctrl_val, 39 char *test_val, boolean_t prog_fmt); 40 static int read_manifest_line(FILE *fd, char *buf, int buf_size, int start_pos, 41 char **line, char *fname); 42 static void parse_line(char *line, char *fname, char *type, char *size, 43 char *mode, char *acl, char *mtime, char *uid, char *gid, char *contents, 44 char *devnode, char *dest); 45 static void init_default_flags(uint_t *flags); 46 static void get_token(char *line, int *curr_pos, int line_len, char *buf, 47 int buf_size); 48 49 int 50 bart_compare(int argc, char **argv) 51 { 52 char *control_fname, *test_fname; 53 int c; 54 FILE *rules_fd = NULL; 55 uint_t glob_flags; 56 boolean_t prog_fmt = B_FALSE; 57 58 init_default_flags(&glob_flags); 59 60 while ((c = getopt(argc, argv, "pr:i:")) != EOF) { 61 switch (c) { 62 case 'p': 63 prog_fmt = B_TRUE; 64 break; 65 66 case 'r': 67 if (optarg == NULL) 68 usage(); 69 70 if (strcmp(optarg, "-") == 0) 71 rules_fd = stdin; 72 else 73 rules_fd = fopen(optarg, "r"); 74 if (rules_fd == NULL) { 75 perror(optarg); 76 usage(); 77 } 78 break; 79 80 case 'i': 81 process_glob_ignores(optarg, &glob_flags); 82 break; 83 84 case '?': 85 default: 86 usage(); 87 } 88 } 89 90 /* Make sure we have the right number of args */ 91 if ((optind + 2) != argc) 92 usage(); 93 argv += optind; 94 control_fname = argv[0]; 95 test_fname = argv[1]; 96 /* At this point, the filenames are sane, so do the comparison */ 97 return (compare_manifests(rules_fd, control_fname, test_fname, 98 prog_fmt, glob_flags)); 99 } 100 101 static int 102 compare_manifests(FILE *rulesfile, char *control, char *test, 103 boolean_t prog_fmt, uint_t flags) 104 { 105 FILE *control_fd, *test_fd; 106 char *control_line, *test_line, control_buf[BUF_SIZE], 107 test_buf[BUF_SIZE], control_fname[PATH_MAX], 108 control_type[TYPE_SIZE], test_fname[PATH_MAX], 109 test_type[TYPE_SIZE]; 110 int control_pos, test_pos, ret, fname_cmp, return_status; 111 112 return_status = EXIT; 113 114 return_status = read_rules(rulesfile, "", flags, 0); 115 116 control_fd = fopen(control, "r"); 117 if (control_fd == NULL) { 118 perror(control); 119 return (FATAL_EXIT); 120 } 121 122 test_fd = fopen(test, "r"); 123 if (test_fd == NULL) { 124 perror(test); 125 return (FATAL_EXIT); 126 } 127 128 control_pos = read_manifest_line(control_fd, control_buf, 129 BUF_SIZE, 0, &control_line, control); 130 test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, 0, 131 &test_line, test); 132 133 while ((control_pos != -1) && (test_pos != -1)) { 134 ret = strcmp(control_line, test_line); 135 if (ret == 0) { 136 /* Lines compare OK, just read the next lines.... */ 137 control_pos = read_manifest_line(control_fd, 138 control_buf, BUF_SIZE, control_pos, &control_line, 139 control); 140 test_pos = read_manifest_line(test_fd, test_buf, 141 BUF_SIZE, test_pos, &test_line, test); 142 continue; 143 } 144 145 /* 146 * Something didn't compare properly. 147 */ 148 extract_fname_ftype(control_line, control_fname, control_type); 149 extract_fname_ftype(test_line, test_fname, test_type); 150 fname_cmp = strcmp(control_fname, test_fname); 151 152 if (fname_cmp == 0) { 153 /* 154 * Filenames were the same, see what was 155 * different and continue. 156 */ 157 if (evaluate_differences(control_line, test_line, 158 prog_fmt, flags) != 0) 159 return_status = WARNING_EXIT; 160 161 control_pos = read_manifest_line(control_fd, 162 control_buf, BUF_SIZE, control_pos, &control_line, 163 control); 164 test_pos = read_manifest_line(test_fd, test_buf, 165 BUF_SIZE, test_pos, &test_line, test); 166 } else if (fname_cmp > 0) { 167 /* Filenames were different, a files was ADDED */ 168 if (report_add(test_fname, test_type)) { 169 report_error(test_fname, ADD_KEYWORD, NULL, 170 NULL, prog_fmt); 171 return_status = WARNING_EXIT; 172 } 173 test_pos = read_manifest_line(test_fd, test_buf, 174 BUF_SIZE, test_pos, &test_line, test); 175 } else if (fname_cmp < 0) { 176 /* Filenames were different, a files was DELETED */ 177 if (report_delete(control_fname, control_type)) { 178 report_error(control_fname, DELETE_KEYWORD, 179 NULL, NULL, prog_fmt); 180 return_status = WARNING_EXIT; 181 } 182 control_pos = read_manifest_line(control_fd, 183 control_buf, BUF_SIZE, control_pos, &control_line, 184 control); 185 } 186 } 187 188 /* 189 * Entering this while loop means files were DELETED from the test 190 * manifest. 191 */ 192 while (control_pos != -1) { 193 (void) sscanf(control_line, "%1023s", control_fname); 194 if (report_delete(control_fname, control_type)) { 195 report_error(control_fname, DELETE_KEYWORD, NULL, 196 NULL, prog_fmt); 197 return_status = WARNING_EXIT; 198 } 199 control_pos = read_manifest_line(control_fd, control_buf, 200 BUF_SIZE, control_pos, &control_line, control); 201 } 202 203 /* 204 * Entering this while loop means files were ADDED to the test 205 * manifest. 206 */ 207 while (test_pos != -1) { 208 (void) sscanf(test_line, "%1023s", test_fname); 209 if (report_add(test_fname, test_type)) { 210 report_error(test_fname, ADD_KEYWORD, NULL, 211 NULL, prog_fmt); 212 return_status = WARNING_EXIT; 213 } 214 test_pos = read_manifest_line(test_fd, test_buf, 215 BUF_SIZE, test_pos, &test_line, test); 216 } 217 218 (void) fclose(control_fd); 219 (void) fclose(test_fd); 220 221 /* For programmatic mode, add a newline for cosmetic reasons */ 222 if (prog_fmt && (return_status != 0)) 223 (void) printf("\n"); 224 225 return (return_status); 226 } 227 228 static void 229 parse_line(char *line, char *fname, char *type, char *size, char *mode, 230 char *acl, char *mtime, char *uid, char *gid, char *contents, char *devnode, 231 char *dest) 232 { 233 int pos, line_len; 234 235 line_len = strlen(line); 236 pos = 0; 237 238 get_token(line, &pos, line_len, fname, PATH_MAX); 239 get_token(line, &pos, line_len, type, TYPE_SIZE); 240 get_token(line, &pos, line_len, size, MISC_SIZE); 241 get_token(line, &pos, line_len, mode, MISC_SIZE); 242 get_token(line, &pos, line_len, acl, ACL_SIZE); 243 get_token(line, &pos, line_len, mtime, MISC_SIZE); 244 get_token(line, &pos, line_len, uid, MISC_SIZE); 245 get_token(line, &pos, line_len, gid, MISC_SIZE); 246 247 /* Reset these fields... */ 248 249 *contents = '\0'; 250 *devnode = '\0'; 251 *dest = '\0'; 252 253 /* Handle filetypes which have a last field..... */ 254 if (type[0] == 'F') 255 get_token(line, &pos, line_len, contents, PATH_MAX); 256 else if ((type[0] == 'B') || (type[0] == 'C')) 257 get_token(line, &pos, line_len, devnode, PATH_MAX); 258 else if (type[0] == 'L') 259 get_token(line, &pos, line_len, dest, PATH_MAX); 260 } 261 262 static void 263 get_token(char *line, int *curr_pos, int line_len, char *buf, int buf_size) 264 { 265 int cnt = 0; 266 267 while (isspace(line[*curr_pos]) && (*curr_pos < line_len)) 268 (*curr_pos)++; 269 270 while (!isspace(line[*curr_pos]) && 271 (*curr_pos < line_len) && (cnt < (buf_size-1))) { 272 buf[cnt] = line[*curr_pos]; 273 (*curr_pos)++; 274 cnt++; 275 } 276 buf[cnt] = '\0'; 277 } 278 279 /* 280 * Utility function: extract fname and type from this line 281 */ 282 static void 283 extract_fname_ftype(char *line, char *fname, char *type) 284 { 285 int line_len, pos; 286 287 pos = 0; 288 line_len = strlen(line); 289 290 get_token(line, &pos, line_len, fname, PATH_MAX); 291 get_token(line, &pos, line_len, type, TYPE_SIZE); 292 } 293 294 /* 295 * Utility function: tells us whether or not this addition should be reported 296 * 297 * Returns 0 if the discrepancy is ignored, non-zero if the discrepancy is 298 * reported. 299 */ 300 static int 301 report_add(char *fname, char *type) 302 { 303 struct rule *rule_ptr; 304 305 rule_ptr = check_rules(fname, type[0]); 306 if ((rule_ptr != NULL) && (rule_ptr->attr_list & ATTR_ADD)) 307 return (1); 308 else 309 return (0); 310 } 311 312 /* 313 * Utility function: tells us whether or not this deletion should be reported 314 * 315 * Returns 0 if the discrepancy is ignored, non-zero if the discrepancy is 316 * reported. 317 */ 318 static int 319 report_delete(char *fname, char *type) 320 { 321 struct rule *rule_ptr; 322 323 rule_ptr = check_rules(fname, type[0]); 324 325 if ((rule_ptr != NULL) && (rule_ptr->attr_list & ATTR_DELETE)) 326 return (1); 327 else 328 return (0); 329 } 330 331 /* 332 * This function takes in the two entries, which have been flagged as 333 * different, breaks them up and reports discrepancies. Note, discrepancies 334 * are affected by the 'CHECK' and 'IGNORE' stanzas which may apply to 335 * these entries. 336 * 337 * Returns the number of discrepancies reported. 338 */ 339 static int 340 evaluate_differences(char *control_line, char *test_line, 341 boolean_t prog_fmt, int flags) 342 { 343 char ctrl_fname[PATH_MAX], test_fname[PATH_MAX], 344 ctrl_type[TYPE_SIZE], test_type[TYPE_SIZE], 345 ctrl_size[MISC_SIZE], ctrl_mode[MISC_SIZE], 346 ctrl_acl[ACL_SIZE], ctrl_mtime[MISC_SIZE], 347 ctrl_uid[MISC_SIZE], ctrl_gid[MISC_SIZE], 348 ctrl_dest[PATH_MAX], ctrl_contents[PATH_MAX], 349 ctrl_devnode[PATH_MAX], test_size[MISC_SIZE], 350 test_mode[MISC_SIZE], test_acl[ACL_SIZE], 351 test_mtime[MISC_SIZE], test_uid[MISC_SIZE], 352 test_gid[MISC_SIZE], test_dest[PATH_MAX], 353 test_contents[PATH_MAX], test_devnode[PATH_MAX], 354 *tag; 355 int ret_val; 356 struct rule *rule_ptr; 357 358 ret_val = 0; 359 360 parse_line(control_line, ctrl_fname, ctrl_type, ctrl_size, ctrl_mode, 361 ctrl_acl, ctrl_mtime, ctrl_uid, ctrl_gid, ctrl_contents, 362 ctrl_devnode, ctrl_dest); 363 364 /* 365 * Now we know the fname and type, let's get the rule that matches this 366 * manifest entry. If there is a match, make sure to setup the 367 * correct reporting flags. 368 */ 369 rule_ptr = check_rules(ctrl_fname, ctrl_type[0]); 370 if (rule_ptr != NULL) 371 flags = rule_ptr->attr_list; 372 373 parse_line(test_line, test_fname, test_type, test_size, test_mode, 374 test_acl, test_mtime, test_uid, test_gid, test_contents, 375 test_devnode, test_dest); 376 377 /* 378 * Report the errors based upon which keywords have been set by 379 * the user. 380 */ 381 if ((flags & ATTR_TYPE) && (ctrl_type[0] != test_type[0])) { 382 report_error(ctrl_fname, TYPE_KEYWORD, ctrl_type, 383 test_type, prog_fmt); 384 ret_val++; 385 } 386 387 if ((flags & ATTR_SIZE) && (strcmp(ctrl_size, test_size) != 0)) { 388 report_error(ctrl_fname, SIZE_KEYWORD, ctrl_size, 389 test_size, prog_fmt); 390 ret_val++; 391 } 392 393 if ((flags & ATTR_MODE) && (strcmp(ctrl_mode, test_mode) != 0)) { 394 report_error(ctrl_fname, MODE_KEYWORD, ctrl_mode, 395 test_mode, prog_fmt); 396 ret_val++; 397 } 398 399 if ((flags & ATTR_ACL) && (strcmp(ctrl_acl, test_acl) != 0)) { 400 report_error(ctrl_fname, ACL_KEYWORD, ctrl_acl, 401 test_acl, prog_fmt); 402 ret_val++; 403 } 404 405 if ((flags & ATTR_MTIME) && (ctrl_type[0] == test_type[0])) { 406 if (strcmp(ctrl_mtime, test_mtime) != 0) { 407 switch (ctrl_type[0]) { 408 case 'D': 409 tag = "dirmtime"; 410 break; 411 case 'L': 412 tag = "lnmtime"; 413 break; 414 default: 415 tag = "mtime"; 416 break; 417 } 418 if (flags == 0) { 419 report_error(ctrl_fname, tag, ctrl_mtime, 420 test_mtime, prog_fmt); 421 ret_val++; 422 } 423 } 424 425 if ((ctrl_type[0] == 'F') && (flags & ATTR_MTIME) && 426 (strcmp(ctrl_mtime, test_mtime) != 0)) { 427 report_error(ctrl_fname, MTIME_KEYWORD, ctrl_mtime, test_mtime, 428 prog_fmt); 429 ret_val++; 430 } 431 432 if ((ctrl_type[0] == 'D') && (flags & ATTR_DIRMTIME) && 433 (strcmp(ctrl_mtime, test_mtime) != 0)) { 434 report_error(ctrl_fname, DIRMTIME_KEYWORD, ctrl_mtime, 435 test_mtime, prog_fmt); 436 ret_val++; 437 } 438 439 if ((ctrl_type[0] == 'L') && (flags & ATTR_LNMTIME) && 440 (strcmp(ctrl_mtime, test_mtime) != 0)) { 441 report_error(ctrl_fname, LNMTIME_KEYWORD, ctrl_mtime, 442 test_mtime, prog_fmt); 443 ret_val++; 444 } 445 } else if ((flags & ATTR_MTIME) && 446 (strcmp(ctrl_mtime, test_mtime) != 0)) { 447 report_error(ctrl_fname, MTIME_KEYWORD, ctrl_mtime, 448 test_mtime, prog_fmt); 449 ret_val++; 450 } 451 452 if ((flags & ATTR_UID) && (strcmp(ctrl_uid, test_uid) != 0)) { 453 report_error(ctrl_fname, UID_KEYWORD, ctrl_uid, 454 test_uid, prog_fmt); 455 ret_val++; 456 } 457 458 if ((flags & ATTR_GID) && (strcmp(ctrl_gid, test_gid) != 0)) { 459 report_error(ctrl_fname, GID_KEYWORD, ctrl_gid, 460 test_gid, prog_fmt); 461 ret_val++; 462 } 463 464 if ((flags & ATTR_DEVNODE) && 465 (strcmp(ctrl_devnode, test_devnode) != 0)) { 466 report_error(ctrl_fname, DEVNODE_KEYWORD, ctrl_devnode, 467 test_devnode, prog_fmt); 468 ret_val++; 469 } 470 471 if ((flags & ATTR_DEST) && (strcmp(ctrl_dest, test_dest) != 0)) { 472 report_error(ctrl_fname, DEST_KEYWORD, ctrl_dest, 473 test_dest, prog_fmt); 474 ret_val++; 475 } 476 477 if ((flags & ATTR_CONTENTS) && 478 (strcmp(ctrl_contents, test_contents)) != 0) { 479 report_error(ctrl_fname, CONTENTS_KEYWORD, ctrl_contents, 480 test_contents, prog_fmt); 481 ret_val++; 482 } 483 484 return (ret_val); 485 } 486 487 /* 488 * Function responsible for reporting errors. 489 */ 490 static void 491 report_error(char *fname, char *type, char *ctrl_val, char *test_val, 492 boolean_t prog_fmt) 493 { 494 static char last_fname[PATH_MAX] = ""; 495 496 if (!prog_fmt) { 497 /* Verbose mode */ 498 if (strcmp(fname, last_fname) != 0) { 499 (void) printf("%s:\n", fname); 500 (void) strlcpy(last_fname, fname, sizeof (last_fname)); 501 } 502 503 if (strcmp(type, ADD_KEYWORD) == 0 || 504 strcmp(type, DELETE_KEYWORD) == 0) 505 (void) printf(" %s\n", type); 506 else 507 (void) printf(" %s control:%s test:%s\n", type, 508 ctrl_val, test_val); 509 } else { 510 /* Programmatic mode */ 511 if (strcmp(fname, last_fname) != 0) { 512 /* Ensure a line is not printed for the initial case */ 513 if (strlen(last_fname) != 0) 514 (void) printf("\n"); 515 (void) strlcpy(last_fname, fname, sizeof (last_fname)); 516 (void) printf("%s ", fname); 517 } 518 519 (void) printf("%s ", type); 520 if (strcmp(type, ADD_KEYWORD) != 0 && 521 strcmp(type, DELETE_KEYWORD) != 0) { 522 (void) printf("%s ", ctrl_val); 523 (void) printf("%s ", test_val); 524 } 525 } 526 } 527 528 /* 529 * Function responsible for reading in a line from the manifest. 530 * Takes in the file ptr and a buffer, parses the buffer and sets the 'line' 531 * ptr correctly. In the case when the buffer is fully parsed, this function 532 * reads more data from the file ptr and refills the buffer. 533 */ 534 static int 535 read_manifest_line(FILE *fd, char *buf, int buf_size, int start_pos, 536 char **line, char *fname) 537 { 538 int end_pos, len, iscomment = 0, filepos; 539 540 /* 541 * Initialization case: make sure the manifest version is OK 542 */ 543 if (start_pos == 0) { 544 end_pos = 0; 545 buf[0] = '\0'; 546 filepos = ftell(fd); 547 (void) fread((void *) buf, (size_t)buf_size, (size_t)1, fd); 548 549 *line = buf; 550 551 if (filepos == 0) { 552 if (strncmp(buf, MANIFEST_VER, 553 strlen(MANIFEST_VER)) != 0) 554 (void) fprintf(stderr, MISSING_VER, fname); 555 if ((*line[0] == '!') || (*line[0] == '#')) 556 iscomment++; 557 558 while (iscomment) { 559 while ((buf[end_pos] != '\n') && 560 (buf[end_pos] != '\0') && 561 (end_pos < buf_size)) 562 end_pos++; 563 564 if (end_pos >= buf_size) 565 return (-1); 566 567 end_pos++; 568 *line = &(buf[end_pos]); 569 iscomment = 0; 570 if ((*line[0] == '!') || (*line[0] == '#')) 571 iscomment++; 572 } 573 } 574 575 while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && 576 (end_pos < buf_size)) 577 end_pos++; 578 579 if (end_pos < buf_size) { 580 if (buf[end_pos] == '\n') { 581 buf[end_pos] = '\0'; 582 return (end_pos); 583 } 584 585 if (buf[end_pos] == '\0') 586 return (-1); 587 } 588 589 (void) fprintf(stderr, MANIFEST_ERR); 590 exit(FATAL_EXIT); 591 } 592 593 end_pos = (start_pos+1); 594 *line = &(buf[end_pos]); 595 596 /* Read the buffer until EOL or the buffer is empty */ 597 while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && 598 (end_pos < buf_size)) 599 end_pos++; 600 601 if (end_pos < buf_size) { 602 /* Found the end of the line, normal exit */ 603 if (buf[end_pos] == '\n') { 604 buf[end_pos] = '\0'; 605 return (end_pos); 606 } 607 608 /* No more input to read */ 609 if (buf[end_pos] == '\0') 610 return (-1); 611 } 612 613 /* 614 * The following code takes the remainder of the buffer and 615 * puts it at the beginning. The space after the remainder, which 616 * is now at the beginning, is blanked. 617 * At this point, read in more data and continue to find the EOL.... 618 */ 619 len = end_pos - (start_pos + 1); 620 (void) memcpy(buf, &(buf[start_pos+1]), (size_t)len); 621 (void) memset(&buf[len], '\0', (buf_size - len)); 622 (void) fread((void *) &buf[len], (size_t)(buf_size-len), (size_t)1, fd); 623 *line = buf; 624 end_pos = len; 625 626 /* Read the buffer until EOL or the buffer is empty */ 627 while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && 628 (end_pos < buf_size)) 629 end_pos++; 630 631 if (end_pos < buf_size) { 632 /* Found the end of the line, normal exit */ 633 if (buf[end_pos] == '\n') { 634 buf[end_pos] = '\0'; 635 return (end_pos); 636 } 637 638 /* No more input to read */ 639 if (buf[end_pos] == '\0') 640 return (-1); 641 } 642 643 (void) fprintf(stderr, MANIFEST_ERR); 644 exit(FATAL_EXIT); 645 646 /* NOTREACHED */ 647 } 648 649 static void 650 init_default_flags(uint_t *flags) 651 { 652 /* Default behavior: everything is checked *except* dirmtime */ 653 *flags = ATTR_ALL & ~(ATTR_DIRMTIME); 654 }