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