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.
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.
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]
22 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
28 #include <sys/statvfs.h>
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
,
39 static char *sanitized_fname(const char *, boolean_t
);
40 static char *get_acl_string(const char *fname
, const struct stat64
*statb
,
42 static int generate_hash(int fdin
, char *hash_str
);
43 static int read_filelist(char *reloc_root
, char **argv
, char *buf
,
45 static int walker(const char *name
, const struct stat64
*sp
,
46 int type
, struct FTW
*ftwx
);
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.
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
;
59 bart_create(int argc
, char **argv
)
61 boolean_t filelist_input
;
62 int ret
, c
, output_pipe
[2];
63 FILE *rules_fd
= NULL
;
66 filelist_input
= B_FALSE
;
69 while ((c
= getopt(argc
, argv
, "Inr:R:")) != EOF
) {
72 if (rules_fd
!= NULL
) {
73 (void) fprintf(stderr
, "%s", INPUT_ERR
);
76 filelist_input
= B_TRUE
;
84 if (strcmp(optarg
, "-") == 0)
87 rules_fd
= fopen(optarg
, "r");
88 if (rules_fd
== NULL
) {
95 (void) strlcpy(reloc_root
, optarg
, sizeof (reloc_root
));
96 ret
= sanitize_reloc_root(reloc_root
,
109 if (pipe(output_pipe
) < 0) {
121 * Break the creation of a manifest into two parts: the parent process
122 * generated the data whereas the child process sorts the data.
124 * The processes communicate through the pipe.
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.
132 if (dup2(output_pipe
[0], STDOUT_FILENO
) != STDOUT_FILENO
) {
136 (void) close(output_pipe
[0]);
137 (void) close(output_pipe
[1]);
139 if (filelist_input
== B_TRUE
) {
140 ret
= create_manifest_filelist(argv
, reloc_root
);
142 ret
= create_manifest_rule(reloc_root
, rules_fd
);
145 /* Close stdout so the sort in the child proc will complete */
146 (void) fclose(stdout
);
149 * Redirect the stdin of this process so its read in from
150 * the pipe, which is the parent process in this case.
152 if (dup2(output_pipe
[1], STDIN_FILENO
) != STDIN_FILENO
) {
156 (void) close(output_pipe
[0]);
161 /* Wait for the child proc (the sort) to complete */
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'.
171 * Return code is whether or not the location spec'd by the -R flag is a
175 sanitize_reloc_root(char *root
, size_t bufsize
)
180 * First, save the current directory and go to the location
181 * specified with the -R option.
183 (void) getcwd(pwd
, sizeof (pwd
));
184 if (chdir(root
) < 0) {
185 /* Failed to change directory, something is wrong.... */
191 * Save the absolute path of the relocatable root directory.
193 (void) getcwd(root
, bufsize
);
196 * Now, go back to where we started, necessary for picking up a rules
199 if (chdir(pwd
) < 0) {
200 /* Failed to change directory, something is wrong.... */
206 * Make sure the path returned does not have a trailing /. This
207 * can only happen when the entire pathname is "/".
209 if (strcmp(root
, "/") == 0)
213 * Since the earlier chdir() succeeded, return success.
219 * This is the worker bee which creates the manifest based upon the command
220 * line options supplied by the user.
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.
227 create_manifest_rule(char *reloc_root
, FILE *rule_fp
)
230 int ret_status
= EXIT
;
234 flags
= ATTR_CONTENTS
;
237 ret_status
= read_rules(rule_fp
, reloc_root
, flags
, 1);
239 /* Loop through every single subtree */
240 for (root
= get_first_subtree(); root
!= NULL
;
241 root
= get_next_subtree(root
)) {
244 * Check to see if this subtree should have contents
245 * checking turned on or off.
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.
253 ret_status
= statvfs64(root
->subtree
, &parent_vfs
);
254 if (ret_status
< 0) {
255 perror(root
->subtree
);
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.
265 (void) nftw64(root
->subtree
, &walker
, 20, FTW_PHYS
|FTW_ANYERR
);
268 * Ugly but necessary:
270 * walker() must return 0, or the tree walk will stop,
271 * so warning flags must be set through a global.
273 if (eval_err
== WARNING_EXIT
)
274 ret_status
= WARNING_EXIT
;
281 create_manifest_filelist(char **argv
, char *reloc_root
)
283 int ret_status
= EXIT
;
284 char input_fname
[PATH_MAX
];
286 while (read_filelist(reloc_root
, argv
,
287 input_fname
, sizeof (input_fname
)) != -1) {
289 struct stat64 stat_buf
;
292 ret
= lstat64(input_fname
, &stat_buf
);
294 ret_status
= WARNING_EXIT
;
297 ret
= eval_file(input_fname
, &stat_buf
, NULL
);
299 if (ret
== WARNING_EXIT
)
300 ret_status
= WARNING_EXIT
;
308 * output_manifest() the child process. It reads in the output from
309 * create_manifest() and sorts it.
312 output_manifest(void)
314 char *env
[] = {"LC_CTYPE=C", "LC_COLLATE=C", "LC_NUMERIC=C", NULL
};
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
);
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.
332 if (execle("/usr/bin/sort", "sort", "-u", NULL
, env
) < 0) {
341 * Callback function for nftw()
344 walker(const char *name
, const struct stat64
*sp
, int type
, struct FTW
*ftwx
)
347 struct statvfs64 path_vfs
;
348 boolean_t dir_flag
= B_FALSE
;
352 case FTW_F
: /* file */
353 rule
= check_rules(name
, 'F');
355 if (rule
->attr_list
& ATTR_CONTENTS
)
361 case FTW_SL
: /* symbolic link, FTW_PHYS */
362 case FTW_SLN
: /* symbolic link, ~FTW_PHYS */
364 case FTW_DP
: /* end of directory, FTW_DEPTH */
365 case FTW_D
: /* enter directory, ~FTW_DEPTH */
367 ret
= statvfs64(name
, &path_vfs
);
369 eval_err
= WARNING_EXIT
;
371 case FTW_NS
: /* unstatable file */
372 (void) fprintf(stderr
, UNKNOWN_FILE
, name
);
373 eval_err
= WARNING_EXIT
;
375 case FTW_DNR
: /* unreadable directory */
376 (void) fprintf(stderr
, CANTLIST_DIR
, name
);
377 eval_err
= WARNING_EXIT
;
380 (void) fprintf(stderr
, INTERNAL_ERR
, name
);
381 eval_err
= WARNING_EXIT
;
385 /* This is the function which really processes the file */
386 ret
= eval_file(name
, sp
, ftwx
);
389 * Since the parameters to walker() are constrained by nftw(),
390 * need to use a global to reflect a WARNING. Sigh.
392 if (ret
== WARNING_EXIT
)
393 eval_err
= WARNING_EXIT
;
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.
403 (strcmp(parent_vfs
.f_basetype
, path_vfs
.f_basetype
) != 0))
404 ftwx
->quit
= FTW_PRUNE
;
410 * This file does the per-file evaluation and is run to generate every entry
413 * All output is written to a pipe which is read by the child process,
414 * which is running output_manifest().
417 eval_file(const char *fname
, const struct stat64
*statb
, struct FTW
*ftwx
)
419 int fd
, ret
, err_code
, i
, result
;
420 char last_field
[PATH_MAX
], ftype
, *acl_str
;
425 switch (statb
->st_mode
& S_IFMT
) {
427 case S_IFREG
: ftype
= 'F'; break;
430 case S_IFDIR
: ftype
= 'D'; break;
433 case S_IFBLK
: ftype
= 'B'; break;
435 /* Character Device */
436 case S_IFCHR
: ftype
= 'C'; break;
439 case S_IFIFO
: ftype
= 'P'; break;
442 case S_IFSOCK
: ftype
= 'S'; break;
445 case S_IFDOOR
: ftype
= 'O'; break;
448 case S_IFLNK
: ftype
= 'L'; break;
450 default: ftype
= '-'; break;
453 /* First, make sure this file should be cataloged */
455 if ((subtree_root
!= NULL
) &&
456 ((result
= exclude_fname(fname
, ftype
, subtree_root
)) !=
458 if ((result
== EXCLUDE_PRUNE
) && (ftwx
!= (struct FTW
*)NULL
))
459 ftwx
->quit
= FTW_PRUNE
;
462 for (i
= 0; i
< PATH_MAX
; i
++)
463 last_field
[i
] = '\0';
466 * Regular files, compute the MD5 checksum and put it into 'last_field'
467 * UNLESS instructed to ignore the checksums.
470 if (compute_chksum
) {
471 fd
= open(fname
, O_RDONLY
|O_LARGEFILE
);
473 err_code
= WARNING_EXIT
;
476 /* default value since the computution failed */
477 (void) strcpy(last_field
, "-");
479 if (generate_hash(fd
, last_field
) != 0) {
480 err_code
= WARNING_EXIT
;
481 (void) fprintf(stderr
, CONTENTS_WARN
,
483 (void) strcpy(last_field
, "-");
488 /* Instructed to ignore checksums, just put in a '-' */
490 (void) strcpy(last_field
, "-");
494 * For symbolic links, put the destination of the symbolic link into
498 ret
= readlink(fname
, last_field
, sizeof (last_field
));
500 err_code
= WARNING_EXIT
;
503 /* default value since the computation failed */
504 (void) strcpy(last_field
, "-");
507 (void) strlcpy(last_field
,
508 sanitized_fname(last_field
, B_FALSE
),
509 sizeof (last_field
));
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".
516 if (strlen(last_field
) == 0)
517 (void) strcpy(last_field
, "\\000");
520 acl_str
= get_acl_string(fname
, statb
, &err_code
);
522 /* Sanitize 'fname', so its in the proper format for the manifest */
523 quoted_name
= sanitized_fname(fname
, B_TRUE
);
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
);
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
);
538 /* free the memory consumed */
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.
552 sanitized_fname(const char *fname
, boolean_t canon_path
)
556 char *op
, *quoted_name
;
558 /* Initialize everything */
559 quoted_name
= safe_calloc((4 * PATH_MAX
) + 1);
565 * In the case when a relocatable root was used, the relocatable
566 * root should *not* be part of the manifest.
568 ip
+= strlen(reloc_root
);
571 * In the case when the '-I' option was used, make sure
572 * the quoted_name starts with a '/'.
578 /* Now walk through 'fname' and build the quoted string */
579 while ((ch
= *ip
++) != 0) {
581 /* Quote the following characters */
589 op
+= sprintf(op
, "\\%.3o", (unsigned char)ch
);
592 /* Otherwise, simply append them */
601 return (quoted_name
);
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.
611 get_acl_string(const char *fname
, const struct stat64
*statb
, int *err_code
)
617 if (S_ISLNK(statb
->st_mode
)) {
618 return (safe_strdup("-"));
622 * Include trivial acl's
624 error
= acl_get(fname
, 0, &aclp
);
627 *err_code
= WARNING_EXIT
;
628 (void) fprintf(stderr
, "%s: %s\n", fname
, acl_strerror(error
));
629 return (safe_strdup("-"));
631 acltext
= acl_totext(aclp
, 0);
634 return (safe_strdup("-"));
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.
648 * returns: The md5 hash of stdin, or NULL if unsuccessful for any reason.
651 generate_hash(int fdin
, char *hash_str
)
653 unsigned char buf
[BUF_SIZE
];
654 unsigned char hash
[MD5_DIGEST_LENGTH
];
661 amtread
= read(fdin
, buf
, sizeof (buf
));
667 /* got some data. Now update hash */
668 MD5Update(&ctx
, buf
, amtread
);
671 /* done passing through data, calculate hash */
672 MD5Final(hash
, &ctx
);
674 for (i
= 0; i
< MD5_DIGEST_LENGTH
; i
++)
675 (void) sprintf(hash_str
+ (i
*2), "%2.2x", hash
[i
]);
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.
685 read_filelist(char *reloc_root
, char **argv
, char *buf
, size_t bufsize
)
687 static int argv_index
= -1;
688 static boolean_t read_stdinput
= B_FALSE
;
689 char temp_buf
[PATH_MAX
];
694 * Setup this code so it knows whether or not to read sdtin.
695 * Also, if reading from argv, setup the index, "argv_index"
697 if (argv_index
== -1) {
700 /* In this case, no args after '-I', so read stdin */
702 read_stdinput
= B_TRUE
;
708 if (fgets(temp_buf
, PATH_MAX
, stdin
) == NULL
)
710 cp
= strtok(temp_buf
, "\n");
712 cp
= argv
[argv_index
++];
719 * Unlike similar code elsewhere, avoid adding a leading
720 * slash for relative pathnames.
722 (void) snprintf(buf
, bufsize
,
723 (reloc_root
[0] == '\0' || cp
[0] == '/') ? "%s%s" : "%s/%s",