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