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