Home | History | Annotate | Download | only in findunref
      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