#include <ctype.h>
#include <errno.h>
#include <fnmatch.h>
#include <ftw.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
typedef struct {
char **paths;
unsigned int npath;
unsigned int maxpaths;
} pnset_t;
typedef struct scmdata {
pnset_t *manifest;
char metapath[MAXPATHLEN];
char root[MAXPATHLEN];
unsigned int rootlen;
boolean_t rootwarn;
} scmdata_t;
typedef int checkscm_func_t(const char *, const struct FTW *);
typedef void chdirscm_func_t(const char *);
typedef struct {
const char *name;
checkscm_func_t *checkfunc;
chdirscm_func_t *chdirfunc;
} scm_t;
static checkscm_func_t check_tw, check_scmdata;
static chdirscm_func_t chdir_hg, chdir_git;
static int pnset_add(pnset_t *, const char *);
static int pnset_check(const pnset_t *, const char *);
static void pnset_empty(pnset_t *);
static void pnset_free(pnset_t *);
static int checkpath(const char *, const struct stat *, int, struct FTW *);
static pnset_t *make_exset(const char *);
static void warn(const char *, ...);
static void die(const char *, ...);
static const scm_t scms[] = {
{ "tw", check_tw, NULL },
{ "teamware", check_tw, NULL },
{ "hg", check_scmdata, chdir_hg },
{ "mercurial", check_scmdata, chdir_hg },
{ "git", check_scmdata, chdir_git },
{ NULL, NULL, NULL }
};
static const scm_t *scm;
static scmdata_t scmdata;
static time_t tstamp;
static pnset_t *exsetp;
static const char *progname;
int
main(int argc, char *argv[])
{
int c;
char path[MAXPATHLEN];
char subtree[MAXPATHLEN] = "./";
char *tstampfile = ".build.tstamp";
struct stat tsstat;
progname = strrchr(argv[0], '/');
if (progname == NULL)
progname = argv[0];
else
progname++;
while ((c = getopt(argc, argv, "as:t:S:")) != EOF) {
switch (c) {
case 'a':
break;
case 's':
(void) strlcat(subtree, optarg, MAXPATHLEN);
break;
case 't':
tstampfile = optarg;
break;
case 'S':
for (scm = scms; scm->name != NULL; scm++) {
if (strcmp(scm->name, optarg) == 0)
break;
}
if (scm->name == NULL)
die("unsupported SCM `%s'\n", optarg);
break;
default:
case '?':
goto usage;
}
}
argc -= optind;
argv += optind;
if (argc != 2) {
usage: (void) fprintf(stderr, "usage: %s [-s <subtree>] "
"[-t <tstampfile>] [-S hg|tw|git] <srcroot> <exceptfile>\n",
progname);
return (EXIT_FAILURE);
}
if (tstampfile[0] == '/')
(void) strlcpy(path, tstampfile, MAXPATHLEN);
else
(void) snprintf(path, MAXPATHLEN, "%s/%s", argv[0], tstampfile);
if (stat(path, &tsstat) == -1)
die("cannot stat timestamp file \"%s\"", path);
tstamp = tsstat.st_mtime;
exsetp = make_exset(argv[1]);
if (exsetp == NULL)
die("cannot make exception pathname set\n");
if (chdir(argv[0]) == -1)
die("cannot change directory to \"%s\"", argv[0]);
if (nftw(subtree, checkpath, 100, FTW_PHYS) != 0)
die("cannot walk tree rooted at \"%s\"\n", argv[0]);
pnset_empty(exsetp);
return (EXIT_SUCCESS);
}
static pnset_t *
hg_manifest(const char *hgroot)
{
FILE *fp = NULL;
char *hgcmd = NULL;
char *newline;
pnset_t *pnsetp;
char path[MAXPATHLEN];
pnsetp = calloc(1, sizeof (pnset_t));
if (pnsetp == NULL ||
asprintf(&hgcmd, "hg manifest -R %s", hgroot) == -1)
goto fail;
fp = popen(hgcmd, "r");
if (fp == NULL)
goto fail;
while (fgets(path, sizeof (path), fp) != NULL) {
newline = strrchr(path, '\n');
if (newline != NULL)
*newline = '\0';
if (pnset_add(pnsetp, path) == 0)
goto fail;
}
(void) pclose(fp);
free(hgcmd);
return (pnsetp);
fail:
warn("cannot load hg manifest at %s", hgroot);
if (fp != NULL)
(void) pclose(fp);
free(hgcmd);
pnset_free(pnsetp);
return (NULL);
}
static pnset_t *
git_manifest(const char *gitroot)
{
FILE *fp = NULL;
char *gitcmd = NULL;
char *newline;
pnset_t *pnsetp;
char path[MAXPATHLEN];
pnsetp = calloc(1, sizeof (pnset_t));
if (pnsetp == NULL ||
asprintf(&gitcmd, "git --git-dir=%s/.git ls-files", gitroot) == -1)
goto fail;
fp = popen(gitcmd, "r");
if (fp == NULL)
goto fail;
while (fgets(path, sizeof (path), fp) != NULL) {
newline = strrchr(path, '\n');
if (newline != NULL)
*newline = '\0';
if (pnset_add(pnsetp, path) == 0)
goto fail;
}
(void) pclose(fp);
free(gitcmd);
return (pnsetp);
fail:
warn("cannot load git manifest at %s", gitroot);
if (fp != NULL)
(void) pclose(fp);
free(gitcmd);
pnset_free(pnsetp);
return (NULL);
}
static void
chdir_scmdata(const char *path, const char *meta,
pnset_t *(*manifest_func)(const char *path))
{
char scmpath[MAXPATHLEN];
char basepath[MAXPATHLEN];
char *slash;
(void) snprintf(scmpath, MAXPATHLEN, "%s/%s", path, meta);
if (scmdata.manifest == NULL ||
(strcmp(scmpath, scmdata.metapath) != 0 &&
access(scmpath, X_OK) == 0) ||
strncmp(path, scmdata.root, scmdata.rootlen - 1) != 0) {
pnset_free(scmdata.manifest);
scmdata.manifest = NULL;
(void) strlcpy(basepath, path, MAXPATHLEN);
while (access(scmpath, X_OK) == -1) {
slash = strrchr(basepath, '/');
if (slash == NULL) {
if (!scmdata.rootwarn) {
warn("no metadata directory "
"for \"%s\"\n", path);
scmdata.rootwarn = B_TRUE;
}
return;
}
*slash = '\0';
(void) snprintf(scmpath, MAXPATHLEN, "%s/%s", basepath,
meta);
}
(void) strlcpy(scmdata.metapath, scmpath, MAXPATHLEN);
(void) strlcpy(scmdata.root, basepath, MAXPATHLEN);
scmdata.manifest = manifest_func(scmdata.root);
if (scmdata.root[strlen(scmdata.root) - 1] != '/')
(void) strlcat(scmdata.root, "/", MAXPATHLEN);
scmdata.rootlen = strlen(scmdata.root);
}
}
static void
chdir_git(const char *path)
{
chdir_scmdata(path, ".git", git_manifest);
}
static void
chdir_hg(const char *path)
{
chdir_scmdata(path, ".hg", hg_manifest);
}
static int
check_scmdata(const char *path, const struct FTW *ftwp)
{
path += scmdata.rootlen;
return (scmdata.manifest != NULL && pnset_check(scmdata.manifest,
path));
}
static int
check_tw(const char *path, const struct FTW *ftwp)
{
char sccspath[MAXPATHLEN];
(void) snprintf(sccspath, MAXPATHLEN, "%.*s/SCCS/s.%s", ftwp->base,
path, path + ftwp->base);
return (access(sccspath, F_OK) == 0);
}
static pnset_t *
make_exset(const char *exceptfile)
{
FILE *fp;
char line[MAXPATHLEN];
char *newline;
pnset_t *pnsetp;
unsigned int i;
pnsetp = calloc(1, sizeof (pnset_t));
if (pnsetp == NULL)
return (NULL);
fp = fopen(exceptfile, "r");
if (fp == NULL) {
warn("cannot open exception file \"%s\"", exceptfile);
goto fail;
}
while (fgets(line, sizeof (line), fp) != NULL) {
newline = strrchr(line, '\n');
if (newline != NULL)
*newline = '\0';
for (i = 0; isspace(line[i]); i++)
;
if (line[i] == '#' || line[i] == '\0')
continue;
if (pnset_add(pnsetp, line) == 0) {
(void) fclose(fp);
goto fail;
}
}
(void) fclose(fp);
return (pnsetp);
fail:
pnset_free(pnsetp);
return (NULL);
}
static int
checkpath(const char *path, const struct stat *statp, int type,
struct FTW *ftwp)
{
switch (type) {
case FTW_F:
if (statp->st_atime >= tstamp || pnset_check(exsetp, path))
return (0);
if (scm == NULL || scm->checkfunc(path, ftwp))
(void) puts(path);
return (0);
case FTW_D:
if (pnset_check(exsetp, path)) {
ftwp->quit = FTW_PRUNE;
return (0);
}
if (scm != NULL && scm->chdirfunc != NULL)
scm->chdirfunc(path);
return (0);
case FTW_DNR:
warn("cannot read \"%s\"", path);
return (0);
case FTW_NS:
warn("cannot stat \"%s\"", path);
return (0);
default:
break;
}
return (0);
}
static int
pnset_add(pnset_t *pnsetp, const char *path)
{
char **newpaths;
unsigned int maxpaths;
if (pnsetp->npath == pnsetp->maxpaths) {
maxpaths = (pnsetp->maxpaths == 0) ? 512 : pnsetp->maxpaths * 2;
newpaths = realloc(pnsetp->paths, sizeof (char *) * maxpaths);
if (newpaths == NULL)
return (0);
pnsetp->paths = newpaths;
pnsetp->maxpaths = maxpaths;
}
pnsetp->paths[pnsetp->npath] = strdup(path);
if (pnsetp->paths[pnsetp->npath] == NULL)
return (0);
pnsetp->npath++;
return (1);
}
static int
pnset_check(const pnset_t *pnsetp, const char *path)
{
unsigned int i;
for (i = 0; i < pnsetp->npath; i++) {
if (fnmatch(pnsetp->paths[i], path, 0) == 0)
return (1);
}
return (0);
}
static void
pnset_empty(pnset_t *pnsetp)
{
while (pnsetp->npath-- != 0)
free(pnsetp->paths[pnsetp->npath]);
free(pnsetp->paths);
pnsetp->maxpaths = 0;
}
static void
pnset_free(pnset_t *pnsetp)
{
if (pnsetp != NULL) {
pnset_empty(pnsetp);
free(pnsetp);
}
}
static void
warn(const char *format, ...)
{
va_list alist;
char *errstr = strerror(errno);
if (errstr == NULL)
errstr = "<unknown error>";
(void) fprintf(stderr, "%s: ", progname);
va_start(alist, format);
(void) vfprintf(stderr, format, alist);
va_end(alist);
if (strrchr(format, '\n') == NULL)
(void) fprintf(stderr, ": %s\n", errstr);
}
static void
die(const char *format, ...)
{
va_list alist;
char *errstr = strerror(errno);
if (errstr == NULL)
errstr = "<unknown error>";
(void) fprintf(stderr, "%s: fatal: ", progname);
va_start(alist, format);
(void) vfprintf(stderr, format, alist);
va_end(alist);
if (strrchr(format, '\n') == NULL)
(void) fprintf(stderr, ": %s\n", errstr);
exit(EXIT_FAILURE);
}