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 (c) 2001 by Sun Microsystems, Inc.
24*0Sstevel@tonic-gate  * All rights reserved.
25*0Sstevel@tonic-gate  */
26*0Sstevel@tonic-gate 
27*0Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
28*0Sstevel@tonic-gate 
29*0Sstevel@tonic-gate /*
30*0Sstevel@tonic-gate  * Finds all unreferenced files in a source tree that do not match a list of
31*0Sstevel@tonic-gate  * permitted pathnames.
32*0Sstevel@tonic-gate  */
33*0Sstevel@tonic-gate 
34*0Sstevel@tonic-gate #include <ctype.h>
35*0Sstevel@tonic-gate #include <errno.h>
36*0Sstevel@tonic-gate #include <fnmatch.h>
37*0Sstevel@tonic-gate #include <ftw.h>
38*0Sstevel@tonic-gate #include <stdarg.h>
39*0Sstevel@tonic-gate #include <stdio.h>
40*0Sstevel@tonic-gate #include <stdlib.h>
41*0Sstevel@tonic-gate #include <string.h>
42*0Sstevel@tonic-gate #include <time.h>
43*0Sstevel@tonic-gate #include <unistd.h>
44*0Sstevel@tonic-gate #include <sys/param.h>
45*0Sstevel@tonic-gate #include <sys/stat.h>
46*0Sstevel@tonic-gate #include <sys/types.h>
47*0Sstevel@tonic-gate 
48*0Sstevel@tonic-gate /*
49*0Sstevel@tonic-gate  * Pathname set: a simple datatype for storing pathname pattern globs and
50*0Sstevel@tonic-gate  * for checking whether a given pathname is matched by a pattern glob in
51*0Sstevel@tonic-gate  * the set.
52*0Sstevel@tonic-gate  */
53*0Sstevel@tonic-gate typedef struct {
54*0Sstevel@tonic-gate 	char		**paths;
55*0Sstevel@tonic-gate 	unsigned int	npath;
56*0Sstevel@tonic-gate 	unsigned int	maxpaths;
57*0Sstevel@tonic-gate } pnset_t;
58*0Sstevel@tonic-gate 
59*0Sstevel@tonic-gate static int	pnset_add(pnset_t *, const char *);
60*0Sstevel@tonic-gate static int	pnset_check(const pnset_t *, const char *);
61*0Sstevel@tonic-gate static void	pnset_empty(pnset_t *);
62*0Sstevel@tonic-gate static int	checkpath(const char *, const struct stat *, int, struct FTW *);
63*0Sstevel@tonic-gate static pnset_t	*make_exset(const char *);
64*0Sstevel@tonic-gate static void	warn(const char *, ...);
65*0Sstevel@tonic-gate static void	die(const char *, ...);
66*0Sstevel@tonic-gate 
67*0Sstevel@tonic-gate static time_t		tstamp;		/* timestamp to compare files to */
68*0Sstevel@tonic-gate static pnset_t		*exsetp;	/* pathname globs to ignore */
69*0Sstevel@tonic-gate static const char	*progname;
70*0Sstevel@tonic-gate static boolean_t	allfiles = B_FALSE;
71*0Sstevel@tonic-gate 
72*0Sstevel@tonic-gate int
73*0Sstevel@tonic-gate main(int argc, char *argv[])
74*0Sstevel@tonic-gate {
75*0Sstevel@tonic-gate 	int c;
76*0Sstevel@tonic-gate 	char path[MAXPATHLEN];
77*0Sstevel@tonic-gate 	char subtree[MAXPATHLEN] = "./";
78*0Sstevel@tonic-gate 	char *tstampfile = ".build.tstamp";
79*0Sstevel@tonic-gate 	struct stat tsstat;
80*0Sstevel@tonic-gate 
81*0Sstevel@tonic-gate 	progname = strrchr(argv[0], '/');
82*0Sstevel@tonic-gate 	if (progname == NULL)
83*0Sstevel@tonic-gate 		progname = argv[0];
84*0Sstevel@tonic-gate 	else
85*0Sstevel@tonic-gate 		progname++;
86*0Sstevel@tonic-gate 
87*0Sstevel@tonic-gate 	while ((c = getopt(argc, argv, "as:t:")) != EOF) {
88*0Sstevel@tonic-gate 		switch (c) {
89*0Sstevel@tonic-gate 		case 'a':
90*0Sstevel@tonic-gate 			allfiles = B_TRUE;
91*0Sstevel@tonic-gate 			break;
92*0Sstevel@tonic-gate 
93*0Sstevel@tonic-gate 		case 's':
94*0Sstevel@tonic-gate 			(void) strlcat(subtree, optarg, MAXPATHLEN);
95*0Sstevel@tonic-gate 			break;
96*0Sstevel@tonic-gate 
97*0Sstevel@tonic-gate 		case 't':
98*0Sstevel@tonic-gate 			tstampfile = optarg;
99*0Sstevel@tonic-gate 			break;
100*0Sstevel@tonic-gate 
101*0Sstevel@tonic-gate 		default:
102*0Sstevel@tonic-gate 		case '?':
103*0Sstevel@tonic-gate 			goto usage;
104*0Sstevel@tonic-gate 		}
105*0Sstevel@tonic-gate 	}
106*0Sstevel@tonic-gate 
107*0Sstevel@tonic-gate 	argc -= optind;
108*0Sstevel@tonic-gate 	argv += optind;
109*0Sstevel@tonic-gate 
110*0Sstevel@tonic-gate 	if (argc != 2) {
111*0Sstevel@tonic-gate usage:		(void) fprintf(stderr, "usage: %s [-a] [-s subtree] "
112*0Sstevel@tonic-gate 		    "[-t tstampfile] srcroot exceptfile\n", progname);
113*0Sstevel@tonic-gate 		return (EXIT_FAILURE);
114*0Sstevel@tonic-gate 	}
115*0Sstevel@tonic-gate 
116*0Sstevel@tonic-gate 	/*
117*0Sstevel@tonic-gate 	 * Interpret a relative timestamp path as relative to srcroot.
118*0Sstevel@tonic-gate 	 */
119*0Sstevel@tonic-gate 	if (tstampfile[0] == '/')
120*0Sstevel@tonic-gate 		(void) strlcpy(path, tstampfile, MAXPATHLEN);
121*0Sstevel@tonic-gate 	else
122*0Sstevel@tonic-gate 		(void) snprintf(path, MAXPATHLEN, "%s/%s", argv[0], tstampfile);
123*0Sstevel@tonic-gate 
124*0Sstevel@tonic-gate 	if (stat(path, &tsstat) == -1)
125*0Sstevel@tonic-gate 		die("cannot stat timestamp file \"%s\"", path);
126*0Sstevel@tonic-gate 	tstamp = tsstat.st_mtime;
127*0Sstevel@tonic-gate 
128*0Sstevel@tonic-gate 	/*
129*0Sstevel@tonic-gate 	 * Create the exception pathname set.
130*0Sstevel@tonic-gate 	 */
131*0Sstevel@tonic-gate 	exsetp = make_exset(argv[1]);
132*0Sstevel@tonic-gate 	if (exsetp == NULL)
133*0Sstevel@tonic-gate 		die("cannot make exception pathname set\n");
134*0Sstevel@tonic-gate 
135*0Sstevel@tonic-gate 	/*
136*0Sstevel@tonic-gate 	 * Walk the specified subtree of the tree rooted at argv[0].
137*0Sstevel@tonic-gate 	 */
138*0Sstevel@tonic-gate 	(void) chdir(argv[0]);
139*0Sstevel@tonic-gate 	if (nftw(subtree, checkpath, 100, FTW_PHYS) != 0)
140*0Sstevel@tonic-gate 		die("cannot walk tree rooted at \"%s\"\n", argv[0]);
141*0Sstevel@tonic-gate 
142*0Sstevel@tonic-gate 	pnset_empty(exsetp);
143*0Sstevel@tonic-gate 	return (EXIT_SUCCESS);
144*0Sstevel@tonic-gate }
145*0Sstevel@tonic-gate 
146*0Sstevel@tonic-gate /*
147*0Sstevel@tonic-gate  * Using `exceptfile' and a built-in list of exceptions, build and return a
148*0Sstevel@tonic-gate  * pnset_t consisting of all of the pathnames globs which are allowed to be
149*0Sstevel@tonic-gate  * unreferenced in the source tree.
150*0Sstevel@tonic-gate  */
151*0Sstevel@tonic-gate static pnset_t *
152*0Sstevel@tonic-gate make_exset(const char *exceptfile)
153*0Sstevel@tonic-gate {
154*0Sstevel@tonic-gate 	FILE		*fp;
155*0Sstevel@tonic-gate 	char		line[MAXPATHLEN];
156*0Sstevel@tonic-gate 	char		*newline;
157*0Sstevel@tonic-gate 	pnset_t		*pnsetp;
158*0Sstevel@tonic-gate 	unsigned int	i;
159*0Sstevel@tonic-gate 	char		*builtin[] = { "*/SCCS", "*/.del-*", "*/.make.*",
160*0Sstevel@tonic-gate 			    "*.flg", NULL };
161*0Sstevel@tonic-gate 
162*0Sstevel@tonic-gate 	pnsetp = calloc(sizeof (pnset_t), 1);
163*0Sstevel@tonic-gate 	if (pnsetp == NULL)
164*0Sstevel@tonic-gate 		return (NULL);
165*0Sstevel@tonic-gate 
166*0Sstevel@tonic-gate 	/*
167*0Sstevel@tonic-gate 	 * Add the built-in exceptions.
168*0Sstevel@tonic-gate 	 */
169*0Sstevel@tonic-gate 	for (i = 0; builtin[i] != NULL; i++) {
170*0Sstevel@tonic-gate 		if (pnset_add(pnsetp, builtin[i]) == 0)
171*0Sstevel@tonic-gate 			goto fail;
172*0Sstevel@tonic-gate 	}
173*0Sstevel@tonic-gate 
174*0Sstevel@tonic-gate 	/*
175*0Sstevel@tonic-gate 	 * Add any exceptions from the file.
176*0Sstevel@tonic-gate 	 */
177*0Sstevel@tonic-gate 	fp = fopen(exceptfile, "r");
178*0Sstevel@tonic-gate 	if (fp == NULL) {
179*0Sstevel@tonic-gate 		warn("cannot open exception file \"%s\"", exceptfile);
180*0Sstevel@tonic-gate 		goto fail;
181*0Sstevel@tonic-gate 	}
182*0Sstevel@tonic-gate 
183*0Sstevel@tonic-gate 	while (fgets(line, sizeof (line), fp) != NULL) {
184*0Sstevel@tonic-gate 		newline = strrchr(line, '\n');
185*0Sstevel@tonic-gate 		if (newline != NULL)
186*0Sstevel@tonic-gate 			*newline = '\0';
187*0Sstevel@tonic-gate 
188*0Sstevel@tonic-gate 		for (i = 0; isspace(line[i]); i++)
189*0Sstevel@tonic-gate 			;
190*0Sstevel@tonic-gate 
191*0Sstevel@tonic-gate 		if (line[i] == '#' || line[i] == '\0')
192*0Sstevel@tonic-gate 			continue;
193*0Sstevel@tonic-gate 
194*0Sstevel@tonic-gate 		if (pnset_add(pnsetp, line) == 0) {
195*0Sstevel@tonic-gate 			(void) fclose(fp);
196*0Sstevel@tonic-gate 			goto fail;
197*0Sstevel@tonic-gate 		}
198*0Sstevel@tonic-gate 	}
199*0Sstevel@tonic-gate 
200*0Sstevel@tonic-gate 	(void) fclose(fp);
201*0Sstevel@tonic-gate 	return (pnsetp);
202*0Sstevel@tonic-gate fail:
203*0Sstevel@tonic-gate 	pnset_empty(pnsetp);
204*0Sstevel@tonic-gate 	free(pnsetp);
205*0Sstevel@tonic-gate 	return (NULL);
206*0Sstevel@tonic-gate }
207*0Sstevel@tonic-gate 
208*0Sstevel@tonic-gate /*
209*0Sstevel@tonic-gate  * FTW callback: print `path' if it's older than `tstamp' and not in `exsetp'.
210*0Sstevel@tonic-gate  */
211*0Sstevel@tonic-gate static int
212*0Sstevel@tonic-gate checkpath(const char *path, const struct stat *statp, int type,
213*0Sstevel@tonic-gate     struct FTW *ftwp)
214*0Sstevel@tonic-gate {
215*0Sstevel@tonic-gate 	char sccspath[MAXPATHLEN];
216*0Sstevel@tonic-gate 
217*0Sstevel@tonic-gate 	switch (type) {
218*0Sstevel@tonic-gate 	case FTW_F:
219*0Sstevel@tonic-gate 		/*
220*0Sstevel@tonic-gate 		 * Skip if the file is referenced or in the exception list.
221*0Sstevel@tonic-gate 		 */
222*0Sstevel@tonic-gate 		if (statp->st_atime >= tstamp || pnset_check(exsetp, path))
223*0Sstevel@tonic-gate 			return (0);
224*0Sstevel@tonic-gate 
225*0Sstevel@tonic-gate 		/*
226*0Sstevel@tonic-gate 		 * If not explicitly checking all files, restrict ourselves
227*0Sstevel@tonic-gate 		 * to unreferenced files under SCCS control.
228*0Sstevel@tonic-gate 		 */
229*0Sstevel@tonic-gate 		if (!allfiles) {
230*0Sstevel@tonic-gate 			(void) snprintf(sccspath, MAXPATHLEN, "%.*s/SCCS/s.%s",
231*0Sstevel@tonic-gate 			    ftwp->base, path, path + ftwp->base);
232*0Sstevel@tonic-gate 
233*0Sstevel@tonic-gate 			if (access(sccspath, F_OK) == -1)
234*0Sstevel@tonic-gate 				return (0);
235*0Sstevel@tonic-gate 		}
236*0Sstevel@tonic-gate 
237*0Sstevel@tonic-gate 		(void) puts(path);
238*0Sstevel@tonic-gate 		return (0);
239*0Sstevel@tonic-gate 
240*0Sstevel@tonic-gate 	case FTW_D:
241*0Sstevel@tonic-gate 		/*
242*0Sstevel@tonic-gate 		 * Prune any directories in the exception list.
243*0Sstevel@tonic-gate 		 */
244*0Sstevel@tonic-gate 		if (pnset_check(exsetp, path))
245*0Sstevel@tonic-gate 			ftwp->quit = FTW_PRUNE;
246*0Sstevel@tonic-gate 		return (0);
247*0Sstevel@tonic-gate 
248*0Sstevel@tonic-gate 	case FTW_DNR:
249*0Sstevel@tonic-gate 		warn("cannot read \"%s\"", path);
250*0Sstevel@tonic-gate 		return (0);
251*0Sstevel@tonic-gate 
252*0Sstevel@tonic-gate 	case FTW_NS:
253*0Sstevel@tonic-gate 		warn("cannot stat \"%s\"", path);
254*0Sstevel@tonic-gate 		return (0);
255*0Sstevel@tonic-gate 
256*0Sstevel@tonic-gate 	default:
257*0Sstevel@tonic-gate 		break;
258*0Sstevel@tonic-gate 	}
259*0Sstevel@tonic-gate 
260*0Sstevel@tonic-gate 	return (0);
261*0Sstevel@tonic-gate }
262*0Sstevel@tonic-gate 
263*0Sstevel@tonic-gate /*
264*0Sstevel@tonic-gate  * Add `path' to the pnset_t pointed to by `pnsetp'.
265*0Sstevel@tonic-gate  */
266*0Sstevel@tonic-gate static int
267*0Sstevel@tonic-gate pnset_add(pnset_t *pnsetp, const char *path)
268*0Sstevel@tonic-gate {
269*0Sstevel@tonic-gate 	char **newpaths;
270*0Sstevel@tonic-gate 
271*0Sstevel@tonic-gate 	if (pnsetp->npath == pnsetp->maxpaths) {
272*0Sstevel@tonic-gate 		newpaths = realloc(pnsetp->paths, sizeof (const char *) *
273*0Sstevel@tonic-gate 		    (pnsetp->maxpaths + 15));
274*0Sstevel@tonic-gate 		if (newpaths == NULL)
275*0Sstevel@tonic-gate 			return (0);
276*0Sstevel@tonic-gate 		pnsetp->paths = newpaths;
277*0Sstevel@tonic-gate 		pnsetp->maxpaths += 15;
278*0Sstevel@tonic-gate 	}
279*0Sstevel@tonic-gate 
280*0Sstevel@tonic-gate 	pnsetp->paths[pnsetp->npath] = strdup(path);
281*0Sstevel@tonic-gate 	if (pnsetp->paths[pnsetp->npath] == NULL)
282*0Sstevel@tonic-gate 		return (0);
283*0Sstevel@tonic-gate 
284*0Sstevel@tonic-gate 	pnsetp->npath++;
285*0Sstevel@tonic-gate 	return (1);
286*0Sstevel@tonic-gate }
287*0Sstevel@tonic-gate 
288*0Sstevel@tonic-gate /*
289*0Sstevel@tonic-gate  * Check `path' against the pnset_t pointed to by `pnsetp'.
290*0Sstevel@tonic-gate  */
291*0Sstevel@tonic-gate static int
292*0Sstevel@tonic-gate pnset_check(const pnset_t *pnsetp, const char *path)
293*0Sstevel@tonic-gate {
294*0Sstevel@tonic-gate 	unsigned int i;
295*0Sstevel@tonic-gate 
296*0Sstevel@tonic-gate 	for (i = 0; i < pnsetp->npath; i++) {
297*0Sstevel@tonic-gate 		if (fnmatch(pnsetp->paths[i], path, 0) == 0)
298*0Sstevel@tonic-gate 			return (1);
299*0Sstevel@tonic-gate 	}
300*0Sstevel@tonic-gate 	return (0);
301*0Sstevel@tonic-gate }
302*0Sstevel@tonic-gate 
303*0Sstevel@tonic-gate /*
304*0Sstevel@tonic-gate  * Empty the pnset_t pointed to by `pnsetp'.
305*0Sstevel@tonic-gate  */
306*0Sstevel@tonic-gate static void
307*0Sstevel@tonic-gate pnset_empty(pnset_t *pnsetp)
308*0Sstevel@tonic-gate {
309*0Sstevel@tonic-gate 	while (pnsetp->npath-- != 0)
310*0Sstevel@tonic-gate 		free(pnsetp->paths[pnsetp->npath]);
311*0Sstevel@tonic-gate 
312*0Sstevel@tonic-gate 	free(pnsetp->paths);
313*0Sstevel@tonic-gate 	pnsetp->maxpaths = 0;
314*0Sstevel@tonic-gate }
315*0Sstevel@tonic-gate 
316*0Sstevel@tonic-gate /* PRINTFLIKE1 */
317*0Sstevel@tonic-gate static void
318*0Sstevel@tonic-gate warn(const char *format, ...)
319*0Sstevel@tonic-gate {
320*0Sstevel@tonic-gate 	va_list alist;
321*0Sstevel@tonic-gate 	char *errstr = strerror(errno);
322*0Sstevel@tonic-gate 
323*0Sstevel@tonic-gate 	if (errstr == NULL)
324*0Sstevel@tonic-gate 		errstr = "<unknown error>";
325*0Sstevel@tonic-gate 
326*0Sstevel@tonic-gate 	(void) fprintf(stderr, "%s: ", progname);
327*0Sstevel@tonic-gate 
328*0Sstevel@tonic-gate 	va_start(alist, format);
329*0Sstevel@tonic-gate 	(void) vfprintf(stderr, format, alist);
330*0Sstevel@tonic-gate 	va_end(alist);
331*0Sstevel@tonic-gate 
332*0Sstevel@tonic-gate 	if (strrchr(format, '\n') == NULL)
333*0Sstevel@tonic-gate 		(void) fprintf(stderr, ": %s\n", errstr);
334*0Sstevel@tonic-gate }
335*0Sstevel@tonic-gate 
336*0Sstevel@tonic-gate /* PRINTFLIKE1 */
337*0Sstevel@tonic-gate static void
338*0Sstevel@tonic-gate die(const char *format, ...)
339*0Sstevel@tonic-gate {
340*0Sstevel@tonic-gate 	va_list alist;
341*0Sstevel@tonic-gate 	char *errstr = strerror(errno);
342*0Sstevel@tonic-gate 
343*0Sstevel@tonic-gate 	if (errstr == NULL)
344*0Sstevel@tonic-gate 		errstr = "<unknown error>";
345*0Sstevel@tonic-gate 
346*0Sstevel@tonic-gate 	(void) fprintf(stderr, "%s: fatal: ", progname);
347*0Sstevel@tonic-gate 
348*0Sstevel@tonic-gate 	va_start(alist, format);
349*0Sstevel@tonic-gate 	(void) vfprintf(stderr, format, alist);
350*0Sstevel@tonic-gate 	va_end(alist);
351*0Sstevel@tonic-gate 
352*0Sstevel@tonic-gate 	if (strrchr(format, '\n') == NULL)
353*0Sstevel@tonic-gate 		(void) fprintf(stderr, ": %s\n", errstr);
354*0Sstevel@tonic-gate 
355*0Sstevel@tonic-gate 	exit(EXIT_FAILURE);
356*0Sstevel@tonic-gate }
357