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