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
bart_compare(int argc,char ** argv)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
compare_manifests(FILE * rulesfile,char * control,char * test,boolean_t prog_fmt,uint_t flags)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
parse_line(char * line,char * fname,char * type,char * size,char * mode,char * acl,char * mtime,char * uid,char * gid,char * contents,char * devnode,char * dest)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 = NULL;
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
get_token(char * line,int * curr_pos,int line_len,char * buf,int buf_size)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
extract_fname_ftype(char * line,char * fname,char * type)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
report_add(char * fname,char * type)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
report_delete(char * fname,char * type)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
evaluate_differences(char * control_line,char * test_line,boolean_t prog_fmt,int flags)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
report_error(char * fname,char * type,char * ctrl_val,char * test_val,boolean_t prog_fmt)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
read_manifest_line(FILE * fd,char * buf,int buf_size,int start_pos,char ** line,char * fname)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
init_default_flags(uint_t * flags)650 init_default_flags(uint_t *flags)
651 {
652 /* Default behavior: everything is checked *except* dirmtime */
653 *flags = ATTR_ALL & ~(ATTR_DIRMTIME);
654 }
655