10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7078Smjnelson  * Common Development and Distribution License (the "License").
6*7078Smjnelson  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
21*7078Smjnelson 
220Sstevel@tonic-gate /*
23*7078Smjnelson  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24*7078Smjnelson  * Use is subject to license terms.
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
270Sstevel@tonic-gate 
280Sstevel@tonic-gate /*
290Sstevel@tonic-gate  * Finds all unreferenced files in a source tree that do not match a list of
300Sstevel@tonic-gate  * permitted pathnames.
310Sstevel@tonic-gate  */
320Sstevel@tonic-gate 
330Sstevel@tonic-gate #include <ctype.h>
340Sstevel@tonic-gate #include <errno.h>
350Sstevel@tonic-gate #include <fnmatch.h>
360Sstevel@tonic-gate #include <ftw.h>
370Sstevel@tonic-gate #include <stdarg.h>
380Sstevel@tonic-gate #include <stdio.h>
390Sstevel@tonic-gate #include <stdlib.h>
400Sstevel@tonic-gate #include <string.h>
410Sstevel@tonic-gate #include <time.h>
420Sstevel@tonic-gate #include <unistd.h>
430Sstevel@tonic-gate #include <sys/param.h>
440Sstevel@tonic-gate #include <sys/stat.h>
450Sstevel@tonic-gate #include <sys/types.h>
460Sstevel@tonic-gate 
470Sstevel@tonic-gate /*
480Sstevel@tonic-gate  * Pathname set: a simple datatype for storing pathname pattern globs and
490Sstevel@tonic-gate  * for checking whether a given pathname is matched by a pattern glob in
500Sstevel@tonic-gate  * the set.
510Sstevel@tonic-gate  */
520Sstevel@tonic-gate typedef struct {
530Sstevel@tonic-gate 	char		**paths;
540Sstevel@tonic-gate 	unsigned int	npath;
550Sstevel@tonic-gate 	unsigned int	maxpaths;
560Sstevel@tonic-gate } pnset_t;
570Sstevel@tonic-gate 
580Sstevel@tonic-gate static int	pnset_add(pnset_t *, const char *);
590Sstevel@tonic-gate static int	pnset_check(const pnset_t *, const char *);
600Sstevel@tonic-gate static void	pnset_empty(pnset_t *);
610Sstevel@tonic-gate static int	checkpath(const char *, const struct stat *, int, struct FTW *);
620Sstevel@tonic-gate static pnset_t	*make_exset(const char *);
630Sstevel@tonic-gate static void	warn(const char *, ...);
640Sstevel@tonic-gate static void	die(const char *, ...);
650Sstevel@tonic-gate 
660Sstevel@tonic-gate static time_t		tstamp;		/* timestamp to compare files to */
670Sstevel@tonic-gate static pnset_t		*exsetp;	/* pathname globs to ignore */
680Sstevel@tonic-gate static const char	*progname;
690Sstevel@tonic-gate static boolean_t	allfiles = B_FALSE;
700Sstevel@tonic-gate 
710Sstevel@tonic-gate int
720Sstevel@tonic-gate main(int argc, char *argv[])
730Sstevel@tonic-gate {
740Sstevel@tonic-gate 	int c;
750Sstevel@tonic-gate 	char path[MAXPATHLEN];
760Sstevel@tonic-gate 	char subtree[MAXPATHLEN] = "./";
770Sstevel@tonic-gate 	char *tstampfile = ".build.tstamp";
780Sstevel@tonic-gate 	struct stat tsstat;
790Sstevel@tonic-gate 
800Sstevel@tonic-gate 	progname = strrchr(argv[0], '/');
810Sstevel@tonic-gate 	if (progname == NULL)
820Sstevel@tonic-gate 		progname = argv[0];
830Sstevel@tonic-gate 	else
840Sstevel@tonic-gate 		progname++;
850Sstevel@tonic-gate 
860Sstevel@tonic-gate 	while ((c = getopt(argc, argv, "as:t:")) != EOF) {
870Sstevel@tonic-gate 		switch (c) {
880Sstevel@tonic-gate 		case 'a':
890Sstevel@tonic-gate 			allfiles = B_TRUE;
900Sstevel@tonic-gate 			break;
910Sstevel@tonic-gate 
920Sstevel@tonic-gate 		case 's':
930Sstevel@tonic-gate 			(void) strlcat(subtree, optarg, MAXPATHLEN);
940Sstevel@tonic-gate 			break;
950Sstevel@tonic-gate 
960Sstevel@tonic-gate 		case 't':
970Sstevel@tonic-gate 			tstampfile = optarg;
980Sstevel@tonic-gate 			break;
990Sstevel@tonic-gate 
1000Sstevel@tonic-gate 		default:
1010Sstevel@tonic-gate 		case '?':
1020Sstevel@tonic-gate 			goto usage;
1030Sstevel@tonic-gate 		}
1040Sstevel@tonic-gate 	}
1050Sstevel@tonic-gate 
1060Sstevel@tonic-gate 	argc -= optind;
1070Sstevel@tonic-gate 	argv += optind;
1080Sstevel@tonic-gate 
1090Sstevel@tonic-gate 	if (argc != 2) {
1100Sstevel@tonic-gate usage:		(void) fprintf(stderr, "usage: %s [-a] [-s subtree] "
1110Sstevel@tonic-gate 		    "[-t tstampfile] srcroot exceptfile\n", progname);
1120Sstevel@tonic-gate 		return (EXIT_FAILURE);
1130Sstevel@tonic-gate 	}
1140Sstevel@tonic-gate 
1150Sstevel@tonic-gate 	/*
1160Sstevel@tonic-gate 	 * Interpret a relative timestamp path as relative to srcroot.
1170Sstevel@tonic-gate 	 */
1180Sstevel@tonic-gate 	if (tstampfile[0] == '/')
1190Sstevel@tonic-gate 		(void) strlcpy(path, tstampfile, MAXPATHLEN);
1200Sstevel@tonic-gate 	else
1210Sstevel@tonic-gate 		(void) snprintf(path, MAXPATHLEN, "%s/%s", argv[0], tstampfile);
1220Sstevel@tonic-gate 
1230Sstevel@tonic-gate 	if (stat(path, &tsstat) == -1)
1240Sstevel@tonic-gate 		die("cannot stat timestamp file \"%s\"", path);
1250Sstevel@tonic-gate 	tstamp = tsstat.st_mtime;
1260Sstevel@tonic-gate 
1270Sstevel@tonic-gate 	/*
1280Sstevel@tonic-gate 	 * Create the exception pathname set.
1290Sstevel@tonic-gate 	 */
1300Sstevel@tonic-gate 	exsetp = make_exset(argv[1]);
1310Sstevel@tonic-gate 	if (exsetp == NULL)
1320Sstevel@tonic-gate 		die("cannot make exception pathname set\n");
1330Sstevel@tonic-gate 
1340Sstevel@tonic-gate 	/*
1350Sstevel@tonic-gate 	 * Walk the specified subtree of the tree rooted at argv[0].
1360Sstevel@tonic-gate 	 */
1370Sstevel@tonic-gate 	(void) chdir(argv[0]);
1380Sstevel@tonic-gate 	if (nftw(subtree, checkpath, 100, FTW_PHYS) != 0)
1390Sstevel@tonic-gate 		die("cannot walk tree rooted at \"%s\"\n", argv[0]);
1400Sstevel@tonic-gate 
1410Sstevel@tonic-gate 	pnset_empty(exsetp);
1420Sstevel@tonic-gate 	return (EXIT_SUCCESS);
1430Sstevel@tonic-gate }
1440Sstevel@tonic-gate 
1450Sstevel@tonic-gate /*
1460Sstevel@tonic-gate  * Using `exceptfile' and a built-in list of exceptions, build and return a
1470Sstevel@tonic-gate  * pnset_t consisting of all of the pathnames globs which are allowed to be
1480Sstevel@tonic-gate  * unreferenced in the source tree.
1490Sstevel@tonic-gate  */
1500Sstevel@tonic-gate static pnset_t *
1510Sstevel@tonic-gate make_exset(const char *exceptfile)
1520Sstevel@tonic-gate {
1530Sstevel@tonic-gate 	FILE		*fp;
1540Sstevel@tonic-gate 	char		line[MAXPATHLEN];
1550Sstevel@tonic-gate 	char		*newline;
1560Sstevel@tonic-gate 	pnset_t		*pnsetp;
1570Sstevel@tonic-gate 	unsigned int	i;
1580Sstevel@tonic-gate 
1590Sstevel@tonic-gate 	pnsetp = calloc(sizeof (pnset_t), 1);
1600Sstevel@tonic-gate 	if (pnsetp == NULL)
1610Sstevel@tonic-gate 		return (NULL);
1620Sstevel@tonic-gate 
1630Sstevel@tonic-gate 	/*
1640Sstevel@tonic-gate 	 * Add any exceptions from the file.
1650Sstevel@tonic-gate 	 */
1660Sstevel@tonic-gate 	fp = fopen(exceptfile, "r");
1670Sstevel@tonic-gate 	if (fp == NULL) {
1680Sstevel@tonic-gate 		warn("cannot open exception file \"%s\"", exceptfile);
1690Sstevel@tonic-gate 		goto fail;
1700Sstevel@tonic-gate 	}
1710Sstevel@tonic-gate 
1720Sstevel@tonic-gate 	while (fgets(line, sizeof (line), fp) != NULL) {
1730Sstevel@tonic-gate 		newline = strrchr(line, '\n');
1740Sstevel@tonic-gate 		if (newline != NULL)
1750Sstevel@tonic-gate 			*newline = '\0';
1760Sstevel@tonic-gate 
1770Sstevel@tonic-gate 		for (i = 0; isspace(line[i]); i++)
1780Sstevel@tonic-gate 			;
1790Sstevel@tonic-gate 
1800Sstevel@tonic-gate 		if (line[i] == '#' || line[i] == '\0')
1810Sstevel@tonic-gate 			continue;
1820Sstevel@tonic-gate 
1830Sstevel@tonic-gate 		if (pnset_add(pnsetp, line) == 0) {
1840Sstevel@tonic-gate 			(void) fclose(fp);
1850Sstevel@tonic-gate 			goto fail;
1860Sstevel@tonic-gate 		}
1870Sstevel@tonic-gate 	}
1880Sstevel@tonic-gate 
1890Sstevel@tonic-gate 	(void) fclose(fp);
1900Sstevel@tonic-gate 	return (pnsetp);
1910Sstevel@tonic-gate fail:
1920Sstevel@tonic-gate 	pnset_empty(pnsetp);
1930Sstevel@tonic-gate 	free(pnsetp);
1940Sstevel@tonic-gate 	return (NULL);
1950Sstevel@tonic-gate }
1960Sstevel@tonic-gate 
1970Sstevel@tonic-gate /*
1980Sstevel@tonic-gate  * FTW callback: print `path' if it's older than `tstamp' and not in `exsetp'.
1990Sstevel@tonic-gate  */
2000Sstevel@tonic-gate static int
2010Sstevel@tonic-gate checkpath(const char *path, const struct stat *statp, int type,
2020Sstevel@tonic-gate     struct FTW *ftwp)
2030Sstevel@tonic-gate {
2040Sstevel@tonic-gate 	char sccspath[MAXPATHLEN];
2050Sstevel@tonic-gate 
2060Sstevel@tonic-gate 	switch (type) {
2070Sstevel@tonic-gate 	case FTW_F:
2080Sstevel@tonic-gate 		/*
2090Sstevel@tonic-gate 		 * Skip if the file is referenced or in the exception list.
2100Sstevel@tonic-gate 		 */
2110Sstevel@tonic-gate 		if (statp->st_atime >= tstamp || pnset_check(exsetp, path))
2120Sstevel@tonic-gate 			return (0);
2130Sstevel@tonic-gate 
2140Sstevel@tonic-gate 		/*
2150Sstevel@tonic-gate 		 * If not explicitly checking all files, restrict ourselves
2160Sstevel@tonic-gate 		 * to unreferenced files under SCCS control.
2170Sstevel@tonic-gate 		 */
2180Sstevel@tonic-gate 		if (!allfiles) {
2190Sstevel@tonic-gate 			(void) snprintf(sccspath, MAXPATHLEN, "%.*s/SCCS/s.%s",
2200Sstevel@tonic-gate 			    ftwp->base, path, path + ftwp->base);
2210Sstevel@tonic-gate 
2220Sstevel@tonic-gate 			if (access(sccspath, F_OK) == -1)
2230Sstevel@tonic-gate 				return (0);
2240Sstevel@tonic-gate 		}
2250Sstevel@tonic-gate 
2260Sstevel@tonic-gate 		(void) puts(path);
2270Sstevel@tonic-gate 		return (0);
2280Sstevel@tonic-gate 
2290Sstevel@tonic-gate 	case FTW_D:
2300Sstevel@tonic-gate 		/*
2310Sstevel@tonic-gate 		 * Prune any directories in the exception list.
2320Sstevel@tonic-gate 		 */
2330Sstevel@tonic-gate 		if (pnset_check(exsetp, path))
2340Sstevel@tonic-gate 			ftwp->quit = FTW_PRUNE;
2350Sstevel@tonic-gate 		return (0);
2360Sstevel@tonic-gate 
2370Sstevel@tonic-gate 	case FTW_DNR:
2380Sstevel@tonic-gate 		warn("cannot read \"%s\"", path);
2390Sstevel@tonic-gate 		return (0);
2400Sstevel@tonic-gate 
2410Sstevel@tonic-gate 	case FTW_NS:
2420Sstevel@tonic-gate 		warn("cannot stat \"%s\"", path);
2430Sstevel@tonic-gate 		return (0);
2440Sstevel@tonic-gate 
2450Sstevel@tonic-gate 	default:
2460Sstevel@tonic-gate 		break;
2470Sstevel@tonic-gate 	}
2480Sstevel@tonic-gate 
2490Sstevel@tonic-gate 	return (0);
2500Sstevel@tonic-gate }
2510Sstevel@tonic-gate 
2520Sstevel@tonic-gate /*
2530Sstevel@tonic-gate  * Add `path' to the pnset_t pointed to by `pnsetp'.
2540Sstevel@tonic-gate  */
2550Sstevel@tonic-gate static int
2560Sstevel@tonic-gate pnset_add(pnset_t *pnsetp, const char *path)
2570Sstevel@tonic-gate {
2580Sstevel@tonic-gate 	char **newpaths;
2590Sstevel@tonic-gate 
2600Sstevel@tonic-gate 	if (pnsetp->npath == pnsetp->maxpaths) {
2610Sstevel@tonic-gate 		newpaths = realloc(pnsetp->paths, sizeof (const char *) *
2620Sstevel@tonic-gate 		    (pnsetp->maxpaths + 15));
2630Sstevel@tonic-gate 		if (newpaths == NULL)
2640Sstevel@tonic-gate 			return (0);
2650Sstevel@tonic-gate 		pnsetp->paths = newpaths;
2660Sstevel@tonic-gate 		pnsetp->maxpaths += 15;
2670Sstevel@tonic-gate 	}
2680Sstevel@tonic-gate 
2690Sstevel@tonic-gate 	pnsetp->paths[pnsetp->npath] = strdup(path);
2700Sstevel@tonic-gate 	if (pnsetp->paths[pnsetp->npath] == NULL)
2710Sstevel@tonic-gate 		return (0);
2720Sstevel@tonic-gate 
2730Sstevel@tonic-gate 	pnsetp->npath++;
2740Sstevel@tonic-gate 	return (1);
2750Sstevel@tonic-gate }
2760Sstevel@tonic-gate 
2770Sstevel@tonic-gate /*
2780Sstevel@tonic-gate  * Check `path' against the pnset_t pointed to by `pnsetp'.
2790Sstevel@tonic-gate  */
2800Sstevel@tonic-gate static int
2810Sstevel@tonic-gate pnset_check(const pnset_t *pnsetp, const char *path)
2820Sstevel@tonic-gate {
2830Sstevel@tonic-gate 	unsigned int i;
2840Sstevel@tonic-gate 
2850Sstevel@tonic-gate 	for (i = 0; i < pnsetp->npath; i++) {
2860Sstevel@tonic-gate 		if (fnmatch(pnsetp->paths[i], path, 0) == 0)
2870Sstevel@tonic-gate 			return (1);
2880Sstevel@tonic-gate 	}
2890Sstevel@tonic-gate 	return (0);
2900Sstevel@tonic-gate }
2910Sstevel@tonic-gate 
2920Sstevel@tonic-gate /*
2930Sstevel@tonic-gate  * Empty the pnset_t pointed to by `pnsetp'.
2940Sstevel@tonic-gate  */
2950Sstevel@tonic-gate static void
2960Sstevel@tonic-gate pnset_empty(pnset_t *pnsetp)
2970Sstevel@tonic-gate {
2980Sstevel@tonic-gate 	while (pnsetp->npath-- != 0)
2990Sstevel@tonic-gate 		free(pnsetp->paths[pnsetp->npath]);
3000Sstevel@tonic-gate 
3010Sstevel@tonic-gate 	free(pnsetp->paths);
3020Sstevel@tonic-gate 	pnsetp->maxpaths = 0;
3030Sstevel@tonic-gate }
3040Sstevel@tonic-gate 
3050Sstevel@tonic-gate /* PRINTFLIKE1 */
3060Sstevel@tonic-gate static void
3070Sstevel@tonic-gate warn(const char *format, ...)
3080Sstevel@tonic-gate {
3090Sstevel@tonic-gate 	va_list alist;
3100Sstevel@tonic-gate 	char *errstr = strerror(errno);
3110Sstevel@tonic-gate 
3120Sstevel@tonic-gate 	if (errstr == NULL)
3130Sstevel@tonic-gate 		errstr = "<unknown error>";
3140Sstevel@tonic-gate 
3150Sstevel@tonic-gate 	(void) fprintf(stderr, "%s: ", progname);
3160Sstevel@tonic-gate 
3170Sstevel@tonic-gate 	va_start(alist, format);
3180Sstevel@tonic-gate 	(void) vfprintf(stderr, format, alist);
3190Sstevel@tonic-gate 	va_end(alist);
3200Sstevel@tonic-gate 
3210Sstevel@tonic-gate 	if (strrchr(format, '\n') == NULL)
3220Sstevel@tonic-gate 		(void) fprintf(stderr, ": %s\n", errstr);
3230Sstevel@tonic-gate }
3240Sstevel@tonic-gate 
3250Sstevel@tonic-gate /* PRINTFLIKE1 */
3260Sstevel@tonic-gate static void
3270Sstevel@tonic-gate die(const char *format, ...)
3280Sstevel@tonic-gate {
3290Sstevel@tonic-gate 	va_list alist;
3300Sstevel@tonic-gate 	char *errstr = strerror(errno);
3310Sstevel@tonic-gate 
3320Sstevel@tonic-gate 	if (errstr == NULL)
3330Sstevel@tonic-gate 		errstr = "<unknown error>";
3340Sstevel@tonic-gate 
3350Sstevel@tonic-gate 	(void) fprintf(stderr, "%s: fatal: ", progname);
3360Sstevel@tonic-gate 
3370Sstevel@tonic-gate 	va_start(alist, format);
3380Sstevel@tonic-gate 	(void) vfprintf(stderr, format, alist);
3390Sstevel@tonic-gate 	va_end(alist);
3400Sstevel@tonic-gate 
3410Sstevel@tonic-gate 	if (strrchr(format, '\n') == NULL)
3420Sstevel@tonic-gate 		(void) fprintf(stderr, ": %s\n", errstr);
3430Sstevel@tonic-gate 
3440Sstevel@tonic-gate 	exit(EXIT_FAILURE);
3450Sstevel@tonic-gate }
346