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