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
33static int	sanitize_reloc_root(char *root, size_t bufsize);
34static int	create_manifest_filelist(char **argv, char *reloc_root);
35static int	create_manifest_rule(char *reloc_root, FILE *rule_fp);
36static void	output_manifest(void);
37static int	eval_file(const char *fname, const struct stat64 *statb,
38	struct FTW *ftwx);
39static char	*sanitized_fname(const char *, boolean_t);
40static char	*get_acl_string(const char *fname, const struct stat64 *statb,
41    int *err_code);
42static int	generate_hash(int fdin, char *hash_str);
43static int	read_filelist(char *reloc_root, char **argv, char *buf,
44    size_t bufsize);
45static 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 */
53static int		compute_chksum = 1, eval_err = 0;
54static struct rule	*subtree_root;
55static char		reloc_root[PATH_MAX];
56static struct statvfs64	parent_vfs;
57
58int
59bart_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 */
174static int
175sanitize_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 */
226static int
227create_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
280static int
281create_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 */
311static void
312output_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 */
343static int
344walker(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 */
416static int
417eval_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 */
551static char *
552sanitized_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 */
610static char *
611get_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 */
650static int
651generate_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 */
684static int
685read_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