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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include <signal.h>
26 #include <unistd.h>
27 #include <sys/acl.h>
28 #include <sys/statvfs.h>
29 #include <sys/wait.h>
30 #include "bart.h"
31 #include <aclutils.h>
32
33 static int sanitize_reloc_root(char *root, size_t bufsize);
34 static int create_manifest_filelist(char **argv, char *reloc_root);
35 static int create_manifest_rule(char *reloc_root, FILE *rule_fp);
36 static void output_manifest(void);
37 static int eval_file(const char *fname, const struct stat64 *statb,
38 struct FTW *ftwx);
39 static char *sanitized_fname(const char *, boolean_t);
40 static char *get_acl_string(const char *fname, const struct stat64 *statb,
41 int *err_code);
42 static int generate_hash(int fdin, char *hash_str);
43 static int read_filelist(char *reloc_root, char **argv, char *buf,
44 size_t bufsize);
45 static int walker(const char *name, const struct stat64 *sp,
46 int type, struct FTW *ftwx);
47
48 /*
49 * The following globals are necessary due to the "walker" function
50 * provided by nftw(). Since there is no way to pass them through to the
51 * walker function, they must be global.
52 */
53 static int compute_chksum = 1, eval_err = 0;
54 static struct rule *subtree_root;
55 static char reloc_root[PATH_MAX];
56 static struct statvfs64 parent_vfs;
57
58 int
bart_create(int argc,char ** argv)59 bart_create(int argc, char **argv)
60 {
61 boolean_t filelist_input;
62 int ret, c, output_pipe[2];
63 FILE *rules_fd = NULL;
64 pid_t pid;
65
66 filelist_input = B_FALSE;
67 reloc_root[0] = '\0';
68
69 while ((c = getopt(argc, argv, "Inr:R:")) != EOF) {
70 switch (c) {
71 case 'I':
72 if (rules_fd != NULL) {
73 (void) fprintf(stderr, "%s", INPUT_ERR);
74 usage();
75 }
76 filelist_input = B_TRUE;
77 break;
78
79 case 'n':
80 compute_chksum = 0;
81 break;
82
83 case 'r':
84 if (strcmp(optarg, "-") == 0)
85 rules_fd = stdin;
86 else
87 rules_fd = fopen(optarg, "r");
88 if (rules_fd == NULL) {
89 perror(optarg);
90 usage();
91 }
92 break;
93
94 case 'R':
95 (void) strlcpy(reloc_root, optarg, sizeof (reloc_root));
96 ret = sanitize_reloc_root(reloc_root,
97 sizeof (reloc_root));
98 if (ret == 0)
99 usage();
100 break;
101
102 case '?':
103 default :
104 usage();
105 }
106 }
107 argv += optind;
108
109 if (pipe(output_pipe) < 0) {
110 perror("");
111 exit(FATAL_EXIT);
112 }
113
114 pid = fork();
115 if (pid < 0) {
116 perror(NULL);
117 exit(FATAL_EXIT);
118 }
119
120 /*
121 * Break the creation of a manifest into two parts: the parent process
122 * generated the data whereas the child process sorts the data.
123 *
124 * The processes communicate through the pipe.
125 */
126 if (pid > 0) {
127 /*
128 * Redirect the stdout of this process so it goes into
129 * output_pipe[0]. The output of this process will be read
130 * by the child, which will sort the output.
131 */
132 if (dup2(output_pipe[0], STDOUT_FILENO) != STDOUT_FILENO) {
133 perror(NULL);
134 exit(FATAL_EXIT);
135 }
136 (void) close(output_pipe[0]);
137 (void) close(output_pipe[1]);
138
139 if (filelist_input == B_TRUE) {
140 ret = create_manifest_filelist(argv, reloc_root);
141 } else {
142 ret = create_manifest_rule(reloc_root, rules_fd);
143 }
144
145 /* Close stdout so the sort in the child proc will complete */
146 (void) fclose(stdout);
147 } else {
148 /*
149 * Redirect the stdin of this process so its read in from
150 * the pipe, which is the parent process in this case.
151 */
152 if (dup2(output_pipe[1], STDIN_FILENO) != STDIN_FILENO) {
153 perror(NULL);
154 exit(FATAL_EXIT);
155 }
156 (void) close(output_pipe[0]);
157
158 output_manifest();
159 }
160
161 /* Wait for the child proc (the sort) to complete */
162 (void) wait(0);
163
164 return (ret);
165 }
166
167 /*
168 * Handle the -R option and sets 'root' to be the absolute path of the
169 * relocatable root. This is useful when the user specifies '-R ../../foo'.
170 *
171 * Return code is whether or not the location spec'd by the -R flag is a
172 * directory or not.
173 */
174 static int
sanitize_reloc_root(char * root,size_t bufsize)175 sanitize_reloc_root(char *root, size_t bufsize)
176 {
177 char pwd[PATH_MAX];
178
179 /*
180 * First, save the current directory and go to the location
181 * specified with the -R option.
182 */
183 (void) getcwd(pwd, sizeof (pwd));
184 if (chdir(root) < 0) {
185 /* Failed to change directory, something is wrong.... */
186 perror(root);
187 return (0);
188 }
189
190 /*
191 * Save the absolute path of the relocatable root directory.
192 */
193 (void) getcwd(root, bufsize);
194
195 /*
196 * Now, go back to where we started, necessary for picking up a rules
197 * file.
198 */
199 if (chdir(pwd) < 0) {
200 /* Failed to change directory, something is wrong.... */
201 perror(root);
202 return (0);
203 }
204
205 /*
206 * Make sure the path returned does not have a trailing /. This
207 * can only happen when the entire pathname is "/".
208 */
209 if (strcmp(root, "/") == 0)
210 root[0] = '\0';
211
212 /*
213 * Since the earlier chdir() succeeded, return success.
214 */
215 return (1);
216 }
217
218 /*
219 * This is the worker bee which creates the manifest based upon the command
220 * line options supplied by the user.
221 *
222 * NOTE: create_manifest() eventually outputs data to a pipe, which is read in
223 * by the child process. The child process is running output_manifest(), which
224 * is responsible for generating sorted output.
225 */
226 static int
create_manifest_rule(char * reloc_root,FILE * rule_fp)227 create_manifest_rule(char *reloc_root, FILE *rule_fp)
228 {
229 struct rule *root;
230 int ret_status = EXIT;
231 uint_t flags;
232
233 if (compute_chksum)
234 flags = ATTR_CONTENTS;
235 else
236 flags = 0;
237 ret_status = read_rules(rule_fp, reloc_root, flags, 1);
238
239 /* Loop through every single subtree */
240 for (root = get_first_subtree(); root != NULL;
241 root = get_next_subtree(root)) {
242
243 /*
244 * Check to see if this subtree should have contents
245 * checking turned on or off.
246 *
247 * NOTE: The 'compute_chksum' and 'parent_vfs'
248 * are a necessary hack: the variables are used in
249 * walker(), both directly and indirectly. Since
250 * the parameters to walker() are defined by nftw(),
251 * the globals are really a backdoor mechanism.
252 */
253 ret_status = statvfs64(root->subtree, &parent_vfs);
254 if (ret_status < 0) {
255 perror(root->subtree);
256 continue;
257 }
258
259 /*
260 * Walk the subtree and invoke the callback function walker()
261 * Use FTW_ANYERR to get FTW_NS and FTW_DNR entries *and*
262 * to continue past those errors.
263 */
264 subtree_root = root;
265 (void) nftw64(root->subtree, &walker, 20, FTW_PHYS|FTW_ANYERR);
266
267 /*
268 * Ugly but necessary:
269 *
270 * walker() must return 0, or the tree walk will stop,
271 * so warning flags must be set through a global.
272 */
273 if (eval_err == WARNING_EXIT)
274 ret_status = WARNING_EXIT;
275
276 }
277 return (ret_status);
278 }
279
280 static int
create_manifest_filelist(char ** argv,char * reloc_root)281 create_manifest_filelist(char **argv, char *reloc_root)
282 {
283 int ret_status = EXIT;
284 char input_fname[PATH_MAX];
285
286 while (read_filelist(reloc_root, argv,
287 input_fname, sizeof (input_fname)) != -1) {
288
289 struct stat64 stat_buf;
290 int ret;
291
292 ret = lstat64(input_fname, &stat_buf);
293 if (ret < 0) {
294 ret_status = WARNING_EXIT;
295 perror(input_fname);
296 } else {
297 ret = eval_file(input_fname, &stat_buf, NULL);
298
299 if (ret == WARNING_EXIT)
300 ret_status = WARNING_EXIT;
301 }
302 }
303
304 return (ret_status);
305 }
306
307 /*
308 * output_manifest() the child process. It reads in the output from
309 * create_manifest() and sorts it.
310 */
311 static void
output_manifest(void)312 output_manifest(void)
313 {
314 char *env[] = {"LC_CTYPE=C", "LC_COLLATE=C", "LC_NUMERIC=C", NULL};
315 time_t time_val;
316 struct tm *tm;
317 char time_buf[1024];
318
319 (void) printf("%s", MANIFEST_VER);
320 time_val = time((time_t)0);
321 tm = localtime(&time_val);
322 (void) strftime(time_buf, sizeof (time_buf), "%A, %B %d, %Y (%T)", tm);
323 (void) printf("! %s\n", time_buf);
324 (void) printf("%s", FORMAT_STR);
325 (void) fflush(stdout);
326 /*
327 * Simply run sort and read from the the current stdin, which is really
328 * the output of create_manifest().
329 * Also, make sure the output is unique, since a given file may be
330 * included by several stanzas.
331 */
332 if (execle("/usr/bin/sort", "sort", "-u", NULL, env) < 0) {
333 perror("");
334 exit(FATAL_EXIT);
335 }
336
337 /*NOTREACHED*/
338 }
339
340 /*
341 * Callback function for nftw()
342 */
343 static int
walker(const char * name,const struct stat64 * sp,int type,struct FTW * ftwx)344 walker(const char *name, const struct stat64 *sp, int type, struct FTW *ftwx)
345 {
346 int ret;
347 struct statvfs64 path_vfs;
348 boolean_t dir_flag = B_FALSE;
349 struct rule *rule;
350
351 switch (type) {
352 case FTW_F: /* file */
353 rule = check_rules(name, 'F');
354 if (rule != NULL) {
355 if (rule->attr_list & ATTR_CONTENTS)
356 compute_chksum = 1;
357 else
358 compute_chksum = 0;
359 }
360 break;
361 case FTW_SL: /* symbolic link, FTW_PHYS */
362 case FTW_SLN: /* symbolic link, ~FTW_PHYS */
363 break;
364 case FTW_DP: /* end of directory, FTW_DEPTH */
365 case FTW_D: /* enter directory, ~FTW_DEPTH */
366 dir_flag = B_TRUE;
367 ret = statvfs64(name, &path_vfs);
368 if (ret < 0)
369 eval_err = WARNING_EXIT;
370 break;
371 case FTW_NS: /* unstatable file */
372 (void) fprintf(stderr, UNKNOWN_FILE, name);
373 eval_err = WARNING_EXIT;
374 return (0);
375 case FTW_DNR: /* unreadable directory */
376 (void) fprintf(stderr, CANTLIST_DIR, name);
377 eval_err = WARNING_EXIT;
378 return (0);
379 default:
380 (void) fprintf(stderr, INTERNAL_ERR, name);
381 eval_err = WARNING_EXIT;
382 return (0);
383 }
384
385 /* This is the function which really processes the file */
386 ret = eval_file(name, sp, ftwx);
387
388 /*
389 * Since the parameters to walker() are constrained by nftw(),
390 * need to use a global to reflect a WARNING. Sigh.
391 */
392 if (ret == WARNING_EXIT)
393 eval_err = WARNING_EXIT;
394
395 /*
396 * This is a case of a directory which crosses into a mounted
397 * filesystem of a different type, e.g., UFS -> NFS.
398 * BART should not walk the new filesystem (by specification), so
399 * set this consolidation-private flag so the rest of the subtree
400 * under this directory is not waled.
401 */
402 if (dir_flag &&
403 (strcmp(parent_vfs.f_basetype, path_vfs.f_basetype) != 0))
404 ftwx->quit = FTW_PRUNE;
405
406 return (0);
407 }
408
409 /*
410 * This file does the per-file evaluation and is run to generate every entry
411 * in the manifest.
412 *
413 * All output is written to a pipe which is read by the child process,
414 * which is running output_manifest().
415 */
416 static int
eval_file(const char * fname,const struct stat64 * statb,struct FTW * ftwx)417 eval_file(const char *fname, const struct stat64 *statb, struct FTW *ftwx)
418 {
419 int fd, ret, err_code, i, result;
420 char last_field[PATH_MAX], ftype, *acl_str;
421 char *quoted_name;
422
423 err_code = EXIT;
424
425 switch (statb->st_mode & S_IFMT) {
426 /* Regular file */
427 case S_IFREG: ftype = 'F'; break;
428
429 /* Directory */
430 case S_IFDIR: ftype = 'D'; break;
431
432 /* Block Device */
433 case S_IFBLK: ftype = 'B'; break;
434
435 /* Character Device */
436 case S_IFCHR: ftype = 'C'; break;
437
438 /* Named Pipe */
439 case S_IFIFO: ftype = 'P'; break;
440
441 /* Socket */
442 case S_IFSOCK: ftype = 'S'; break;
443
444 /* Door */
445 case S_IFDOOR: ftype = 'O'; break;
446
447 /* Symbolic link */
448 case S_IFLNK: ftype = 'L'; break;
449
450 default: ftype = '-'; break;
451 }
452
453 /* First, make sure this file should be cataloged */
454
455 if ((subtree_root != NULL) &&
456 ((result = exclude_fname(fname, ftype, subtree_root)) !=
457 NO_EXCLUDE)) {
458 if ((result == EXCLUDE_PRUNE) && (ftwx != (struct FTW *)NULL))
459 ftwx->quit = FTW_PRUNE;
460 return (err_code);
461 }
462 for (i = 0; i < PATH_MAX; i++)
463 last_field[i] = '\0';
464
465 /*
466 * Regular files, compute the MD5 checksum and put it into 'last_field'
467 * UNLESS instructed to ignore the checksums.
468 */
469 if (ftype == 'F') {
470 if (compute_chksum) {
471 fd = open(fname, O_RDONLY|O_LARGEFILE);
472 if (fd < 0) {
473 err_code = WARNING_EXIT;
474 perror(fname);
475
476 /* default value since the computution failed */
477 (void) strcpy(last_field, "-");
478 } else {
479 if (generate_hash(fd, last_field) != 0) {
480 err_code = WARNING_EXIT;
481 (void) fprintf(stderr, CONTENTS_WARN,
482 fname);
483 (void) strcpy(last_field, "-");
484 }
485 }
486 (void) close(fd);
487 }
488 /* Instructed to ignore checksums, just put in a '-' */
489 else
490 (void) strcpy(last_field, "-");
491 }
492
493 /*
494 * For symbolic links, put the destination of the symbolic link into
495 * 'last_field'
496 */
497 if (ftype == 'L') {
498 ret = readlink(fname, last_field, sizeof (last_field));
499 if (ret < 0) {
500 err_code = WARNING_EXIT;
501 perror(fname);
502
503 /* default value since the computation failed */
504 (void) strcpy(last_field, "-");
505 }
506 else
507 (void) strlcpy(last_field,
508 sanitized_fname(last_field, B_FALSE),
509 sizeof (last_field));
510
511 /*
512 * Boundary condition: possible for a symlink to point to
513 * nothing [ ln -s '' link_name ]. For this case, set the
514 * destination to "\000".
515 */
516 if (strlen(last_field) == 0)
517 (void) strcpy(last_field, "\\000");
518 }
519
520 acl_str = get_acl_string(fname, statb, &err_code);
521
522 /* Sanitize 'fname', so its in the proper format for the manifest */
523 quoted_name = sanitized_fname(fname, B_TRUE);
524
525 /* Start to build the entry.... */
526 (void) printf("%s %c %d %o %s %x %d %d", quoted_name, ftype,
527 (int)statb->st_size, (int)statb->st_mode, acl_str,
528 (int)statb->st_mtime, (int)statb->st_uid, (int)statb->st_gid);
529
530 /* Finish it off based upon whether or not it's a device node */
531 if ((ftype == 'B') || (ftype == 'C'))
532 (void) printf(" %x\n", (int)statb->st_rdev);
533 else if (strlen(last_field) > 0)
534 (void) printf(" %s\n", last_field);
535 else
536 (void) printf("\n");
537
538 /* free the memory consumed */
539 free(acl_str);
540 free(quoted_name);
541
542 return (err_code);
543 }
544
545 /*
546 * When creating a manifest, make sure all '?', tabs, space, newline, '/'
547 * and '[' are all properly quoted. Convert them to a "\ooo" where the 'ooo'
548 * represents their octal value. For filesystem objects, as opposed to symlink
549 * targets, also canonicalize the pathname.
550 */
551 static char *
sanitized_fname(const char * fname,boolean_t canon_path)552 sanitized_fname(const char *fname, boolean_t canon_path)
553 {
554 const char *ip;
555 unsigned char ch;
556 char *op, *quoted_name;
557
558 /* Initialize everything */
559 quoted_name = safe_calloc((4 * PATH_MAX) + 1);
560 ip = fname;
561 op = quoted_name;
562
563 if (canon_path) {
564 /*
565 * In the case when a relocatable root was used, the relocatable
566 * root should *not* be part of the manifest.
567 */
568 ip += strlen(reloc_root);
569
570 /*
571 * In the case when the '-I' option was used, make sure
572 * the quoted_name starts with a '/'.
573 */
574 if (*ip != '/')
575 *op++ = '/';
576 }
577
578 /* Now walk through 'fname' and build the quoted string */
579 while ((ch = *ip++) != 0) {
580 switch (ch) {
581 /* Quote the following characters */
582 case ' ':
583 case '*':
584 case '\n':
585 case '?':
586 case '[':
587 case '\\':
588 case '\t':
589 op += sprintf(op, "\\%.3o", (unsigned char)ch);
590 break;
591
592 /* Otherwise, simply append them */
593 default:
594 *op++ = ch;
595 break;
596 }
597 }
598
599 *op = 0;
600
601 return (quoted_name);
602 }
603
604 /*
605 * Function responsible for generating the ACL information for a given
606 * file. Note, the string is put into buffer malloc'd by this function.
607 * It's the responsibility of the caller to free the buffer. This function
608 * should never return a NULL pointer.
609 */
610 static char *
get_acl_string(const char * fname,const struct stat64 * statb,int * err_code)611 get_acl_string(const char *fname, const struct stat64 *statb, int *err_code)
612 {
613 acl_t *aclp;
614 char *acltext;
615 int error;
616
617 if (S_ISLNK(statb->st_mode)) {
618 return (safe_strdup("-"));
619 }
620
621 /*
622 * Include trivial acl's
623 */
624 error = acl_get(fname, 0, &aclp);
625
626 if (error != 0) {
627 *err_code = WARNING_EXIT;
628 (void) fprintf(stderr, "%s: %s\n", fname, acl_strerror(error));
629 return (safe_strdup("-"));
630 } else {
631 acltext = acl_totext(aclp, 0);
632 acl_free(aclp);
633 if (acltext == NULL)
634 return (safe_strdup("-"));
635 else
636 return (acltext);
637 }
638 }
639
640
641 /*
642 *
643 * description: This routine reads stdin in BUF_SIZE chunks, uses the bits
644 * to update the md5 hash buffer, and outputs the chunks
645 * to stdout. When stdin is exhausted, the hash is computed,
646 * converted to a hexadecimal string, and returned.
647 *
648 * returns: The md5 hash of stdin, or NULL if unsuccessful for any reason.
649 */
650 static int
generate_hash(int fdin,char * hash_str)651 generate_hash(int fdin, char *hash_str)
652 {
653 unsigned char buf[BUF_SIZE];
654 unsigned char hash[MD5_DIGEST_LENGTH];
655 int i, amtread;
656 MD5_CTX ctx;
657
658 MD5Init(&ctx);
659
660 for (;;) {
661 amtread = read(fdin, buf, sizeof (buf));
662 if (amtread == 0)
663 break;
664 if (amtread < 0)
665 return (1);
666
667 /* got some data. Now update hash */
668 MD5Update(&ctx, buf, amtread);
669 }
670
671 /* done passing through data, calculate hash */
672 MD5Final(hash, &ctx);
673
674 for (i = 0; i < MD5_DIGEST_LENGTH; i++)
675 (void) sprintf(hash_str + (i*2), "%2.2x", hash[i]);
676
677 return (0);
678 }
679
680 /*
681 * Used by 'bart create' with the '-I' option. Return each entry into a 'buf'
682 * with the appropriate exit code: '0' for success and '-1' for failure.
683 */
684 static int
read_filelist(char * reloc_root,char ** argv,char * buf,size_t bufsize)685 read_filelist(char *reloc_root, char **argv, char *buf, size_t bufsize)
686 {
687 static int argv_index = -1;
688 static boolean_t read_stdinput = B_FALSE;
689 char temp_buf[PATH_MAX];
690 char *cp;
691
692 /*
693 * INITIALIZATION:
694 * Setup this code so it knows whether or not to read sdtin.
695 * Also, if reading from argv, setup the index, "argv_index"
696 */
697 if (argv_index == -1) {
698 argv_index = 0;
699
700 /* In this case, no args after '-I', so read stdin */
701 if (argv[0] == NULL)
702 read_stdinput = B_TRUE;
703 }
704
705 buf[0] = '\0';
706
707 if (read_stdinput) {
708 if (fgets(temp_buf, PATH_MAX, stdin) == NULL)
709 return (-1);
710 cp = strtok(temp_buf, "\n");
711 } else {
712 cp = argv[argv_index++];
713 }
714
715 if (cp == NULL)
716 return (-1);
717
718 /*
719 * Unlike similar code elsewhere, avoid adding a leading
720 * slash for relative pathnames.
721 */
722 (void) snprintf(buf, bufsize,
723 (reloc_root[0] == '\0' || cp[0] == '/') ? "%s%s" : "%s/%s",
724 reloc_root, cp);
725
726 return (0);
727 }
728