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