Home | History | Annotate | Line # | Download | only in cvslatest
      1  1.11  christos /*	$NetBSD: cvslatest.c,v 1.11 2023/03/07 21:24:19 christos Exp $	*/
      2   1.1  christos 
      3   1.1  christos /*-
      4   1.1  christos  * Copyright (c) 2016 The NetBSD Foundation, Inc.
      5   1.1  christos  * All rights reserved.
      6   1.1  christos  *
      7   1.1  christos  * This code is derived from software contributed to The NetBSD Foundation
      8   1.1  christos  * by Christos Zoulas.
      9   1.1  christos  *
     10   1.1  christos  * Redistribution and use in source and binary forms, with or without
     11   1.1  christos  * modification, are permitted provided that the following conditions
     12   1.1  christos  * are met:
     13   1.1  christos  * 1. Redistributions of source code must retain the above copyright
     14   1.1  christos  *    notice, this list of conditions and the following disclaimer.
     15   1.1  christos  * 2. Redistributions in binary form must reproduce the above copyright
     16   1.1  christos  *    notice, this list of conditions and the following disclaimer in the
     17   1.1  christos  *    documentation and/or other materials provided with the distribution.
     18   1.1  christos  *
     19   1.1  christos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20   1.1  christos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21   1.1  christos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22   1.1  christos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23   1.1  christos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24   1.1  christos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25   1.1  christos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26   1.1  christos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27   1.1  christos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28   1.1  christos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29   1.1  christos  * POSSIBILITY OF SUCH DAMAGE.
     30   1.1  christos  */
     31   1.2  christos 
     32   1.6     joerg #ifdef __linux__
     33   1.6     joerg #define _GNU_SOURCE
     34   1.6     joerg #endif
     35   1.6     joerg 
     36   1.2  christos #ifdef HAVE_NBTOOL_CONFIG_H
     37   1.2  christos #include "nbtool_config.h"
     38   1.2  christos #endif
     39   1.2  christos 
     40   1.1  christos #include <sys/cdefs.h>
     41  1.11  christos __RCSID("$NetBSD: cvslatest.c,v 1.11 2023/03/07 21:24:19 christos Exp $");
     42   1.1  christos 
     43   1.1  christos /*
     44   1.1  christos  * Find the latest timestamp in a set of CVS trees, by examining the
     45   1.1  christos  * Entries files
     46   1.1  christos  */
     47   1.1  christos 
     48   1.1  christos #include <sys/param.h>
     49   1.1  christos #include <sys/types.h>
     50  1.10    martin #include <sys/stat.h>
     51   1.1  christos 
     52   1.1  christos #include <stdio.h>
     53   1.1  christos #include <string.h>
     54   1.1  christos #include <stdlib.h>
     55   1.1  christos #include <unistd.h>
     56   1.7  christos #include <dirent.h>
     57   1.1  christos #include <err.h>
     58   1.1  christos #include <fts.h>
     59   1.1  christos #include <time.h>
     60   1.1  christos 
     61   1.1  christos static int debug = 0;
     62   1.1  christos static int ignore = 0;
     63   1.1  christos 
     64   1.1  christos struct latest {
     65   1.1  christos 	time_t time;
     66   1.1  christos 	char path[MAXPATHLEN];
     67   1.1  christos };
     68   1.1  christos 
     69   1.1  christos static void
     70   1.1  christos printlat(const struct latest *lat)
     71   1.1  christos {
     72   1.1  christos 	fprintf(stderr, "%s %s", lat->path, ctime(&lat->time));
     73   1.1  christos }
     74   1.1  christos 
     75   1.1  christos static void
     76   1.5  christos getrepo(const FTSENT *e, char *repo, size_t maxrepo)
     77   1.1  christos {
     78   1.1  christos 	FILE *fp;
     79   1.5  christos 	char name[MAXPATHLEN], ename[MAXPATHLEN];
     80   1.1  christos 	char *ptr;
     81   1.1  christos 
     82   1.5  christos 	snprintf(name, sizeof(name), "%s/Repository", e->fts_accpath);
     83   1.5  christos 	snprintf(ename, sizeof(ename), "%s/Repository", e->fts_path);
     84   1.1  christos 	if ((fp = fopen(name, "r")) == NULL)
     85   1.5  christos 		err(EXIT_FAILURE, "Can't open `%s'", ename);
     86   1.1  christos 	if (fgets(repo, (int)maxrepo, fp) == NULL)
     87   1.5  christos 		err(EXIT_FAILURE, "Can't read `%s'", ename);
     88   1.1  christos 	if ((ptr = strchr(repo, '\n')) == NULL)
     89   1.5  christos 		errx(EXIT_FAILURE, "Malformed line in `%s'", ename);
     90   1.1  christos 	*ptr = '\0';
     91   1.1  christos 	fclose(fp);
     92   1.1  christos }
     93   1.1  christos 
     94   1.1  christos static void
     95  1.11  christos notimestamp(const char *fn, const char *ename, int uncommitted)
     96  1.11  christos {
     97  1.11  christos 	warnx("Can't get timestamp from %s file `%s' in `%s'",
     98  1.11  christos 	    uncommitted ? "uncommitted" : "locally modified", fn, ename);
     99  1.11  christos }
    100  1.11  christos 
    101  1.11  christos static void
    102   1.5  christos getlatest(const FTSENT *e, const char *repo, struct latest *lat)
    103   1.1  christos {
    104   1.1  christos 	static const char fmt[] = "%a %b %d %H:%M:%S %Y";
    105   1.5  christos 	char name[MAXPATHLEN], ename[MAXPATHLEN];
    106   1.1  christos 	char entry[MAXPATHLEN * 2];
    107   1.1  christos 	char *fn, *dt, *p;
    108   1.1  christos 	time_t t;
    109   1.1  christos 	struct tm tm;
    110  1.10    martin 	struct stat sb;
    111   1.1  christos 	FILE *fp;
    112   1.1  christos 
    113   1.5  christos 	snprintf(name, sizeof(name), "%s/Entries", e->fts_accpath);
    114   1.5  christos 	snprintf(ename, sizeof(ename), "%s/Entries", e->fts_path);
    115   1.1  christos 	if ((fp = fopen(name, "r")) == NULL)
    116   1.5  christos 		err(EXIT_FAILURE, "Can't open `%s'", ename);
    117   1.1  christos 
    118   1.1  christos 	while (fgets(entry, (int)sizeof(entry), fp) != NULL) {
    119   1.1  christos 		if (entry[0] != '/')
    120   1.1  christos 		    continue;
    121   1.1  christos 		if ((fn = strtok(entry, "/")) == NULL)
    122   1.1  christos 			goto mal;
    123   1.1  christos 		if (strtok(NULL, "/") == NULL)
    124   1.1  christos 			goto mal;
    125   1.1  christos 		if ((dt = strtok(NULL, "/")) == NULL)
    126   1.1  christos 			goto mal;
    127  1.11  christos 		if (strncmp(dt, "dummy timestamp", 14) == 0) {
    128  1.11  christos 			notimestamp(fn, ename, 1);
    129  1.10    martin 			if (!ignore)
    130  1.10    martin 				exit(EXIT_FAILURE);
    131  1.10    martin 			continue;
    132  1.10    martin 		}
    133  1.10    martin 		if (strcmp(dt, "Result of merge") == 0) {
    134  1.11  christos 			notimestamp(fn, ename, 0);
    135  1.10    martin 			if (fstat(fileno(fp), &sb) == 0) {
    136  1.10    martin 				t = sb.st_mtime;
    137  1.10    martin 				goto compare;
    138  1.10    martin 			}
    139   1.9  christos 			if (!ignore)
    140   1.9  christos 				exit(EXIT_FAILURE);
    141   1.9  christos 			continue;
    142   1.9  christos 		}
    143   1.1  christos 		if ((p = strptime(dt, fmt, &tm)) == NULL || *p) {
    144   1.5  christos 			warnx("Malformed time `%s' in `%s'", dt, ename);
    145   1.1  christos 			if (!ignore)
    146   1.1  christos 				exit(EXIT_FAILURE);
    147   1.9  christos 			continue;
    148   1.1  christos 		}
    149   1.4  christos 		tm.tm_isdst = 0;	// We are in GMT anyway
    150   1.1  christos 		if ((t = mktime(&tm)) == (time_t)-1)
    151   1.1  christos 			errx(EXIT_FAILURE, "Time conversion `%s' in `%s'",
    152   1.5  christos 			    dt, ename);
    153  1.10    martin compare:
    154   1.1  christos 		if (lat->time == 0 || lat->time < t) {
    155   1.1  christos 			lat->time = t;
    156   1.1  christos 			snprintf(lat->path, sizeof(lat->path),
    157   1.1  christos 			    "%s/%s", repo, fn);
    158   1.3  christos 			if (debug > 1)
    159   1.1  christos 				printlat(lat);
    160   1.1  christos 		}
    161   1.1  christos 	}
    162   1.9  christos 	if (ferror(fp))
    163   1.9  christos 		err(EXIT_FAILURE, "Can't read `%s'", ename);
    164   1.1  christos 
    165   1.1  christos 	fclose(fp);
    166   1.1  christos 	return;
    167   1.1  christos 
    168   1.1  christos mal:
    169   1.5  christos 	errx(EXIT_FAILURE, "Malformed line in `%s'", ename);
    170   1.1  christos }
    171   1.1  christos 
    172   1.1  christos static void
    173   1.1  christos cvsscan(char **pathv, const char *name, struct latest *lat)
    174   1.1  christos {
    175   1.1  christos         FTS *dh;
    176   1.1  christos 	char repo[MAXPATHLEN];
    177   1.1  christos         FTSENT *entry;
    178   1.1  christos 
    179   1.1  christos 	lat->time = 0;
    180   1.1  christos 
    181   1.1  christos         dh = fts_open(pathv, FTS_PHYSICAL, NULL);
    182   1.1  christos         if (dh == NULL)
    183   1.1  christos 		err(EXIT_FAILURE, "fts_open `%s'", pathv[0]);
    184   1.1  christos 
    185   1.1  christos         while ((entry = fts_read(dh)) != NULL) {
    186   1.1  christos                 if (entry->fts_info != FTS_D)
    187   1.1  christos 			continue;
    188   1.1  christos 
    189   1.1  christos 		if (strcmp(entry->fts_name, name) != 0)
    190   1.1  christos                         continue;
    191   1.1  christos 
    192   1.5  christos 		getrepo(entry, repo, sizeof(repo));
    193   1.5  christos 		getlatest(entry, repo, lat);
    194   1.1  christos         }
    195   1.1  christos 
    196   1.1  christos         (void)fts_close(dh);
    197   1.1  christos }
    198   1.1  christos 
    199   1.1  christos static __dead void
    200   1.1  christos usage(void)
    201   1.1  christos {
    202   1.1  christos 	fprintf(stderr, "Usage: %s [-di] [-n <name>] <path> ...\n",
    203   1.1  christos 	    getprogname());
    204   1.1  christos 	exit(EXIT_FAILURE);
    205   1.1  christos }
    206   1.1  christos 
    207   1.7  christos static int
    208   1.7  christos checkDir(char *path, size_t pathlen, const char *name)
    209   1.7  christos {
    210   1.7  christos 	static const char *files[] = {
    211   1.7  christos 		"Entries", "Root", "Repository",
    212   1.7  christos 	};
    213   1.7  christos 	size_t i;
    214   1.7  christos 
    215   1.7  christos 	for (i = 0; i < __arraycount(files); i++) {
    216   1.7  christos 		snprintf(path, pathlen, "%s/%s", name, files[i]);
    217   1.7  christos 		if (access(path, F_OK) == -1)
    218   1.7  christos 			return 0;
    219   1.7  christos 	}
    220   1.7  christos 
    221   1.7  christos 	return 1;
    222   1.7  christos }
    223   1.7  christos 
    224   1.7  christos static const char *
    225   1.7  christos findCVSDir(char *path, size_t pathlen, const char *name)
    226   1.7  christos {
    227   1.7  christos 	DIR *dirp;
    228   1.7  christos 	struct dirent *dp;
    229   1.7  christos 	const char *n;
    230   1.7  christos 
    231   1.7  christos 	if ((dirp = opendir(name)) == NULL)
    232   1.7  christos 		err(EXIT_FAILURE, "Can't open `%s'", name);
    233   1.7  christos 
    234   1.7  christos 	while ((dp = readdir(dirp)) != NULL) {
    235   1.7  christos 		n = dp->d_name;
    236   1.7  christos 		if (n[0] == '.' && (n[1] == '\0' ||
    237   1.7  christos 		    (n[1] == '.' && n[2] == '\0')))
    238   1.7  christos 			continue;
    239   1.7  christos 		if (checkDir(path, pathlen, n))
    240   1.7  christos 			goto out;
    241   1.7  christos 	}
    242   1.7  christos 	n = "CVS";
    243   1.7  christos out:
    244   1.8  christos 	strlcpy(path, n, pathlen);
    245   1.7  christos 	closedir(dirp);
    246   1.7  christos 	return path;
    247   1.7  christos }
    248   1.7  christos 
    249   1.7  christos 
    250   1.1  christos int
    251   1.1  christos main(int argc, char *argv[])
    252   1.1  christos {
    253   1.1  christos 	struct latest lat;
    254   1.7  christos 	const char *name = NULL;
    255   1.7  christos 	char path[MAXPATHLEN];
    256   1.1  christos 	int c;
    257   1.1  christos 
    258   1.1  christos 	while ((c = getopt(argc, argv, "din:")) != -1)
    259   1.1  christos 		switch (c) {
    260   1.1  christos 		case 'i':
    261   1.1  christos 			ignore++;
    262   1.1  christos 			break;
    263   1.1  christos 		case 'd':
    264   1.1  christos 			debug++;
    265   1.1  christos 			break;
    266   1.1  christos 		case 'n':
    267   1.1  christos 			name = optarg;
    268   1.1  christos 			break;
    269   1.1  christos 		default:
    270   1.1  christos 			usage();
    271   1.1  christos 		}
    272   1.1  christos 
    273   1.1  christos 	if (argc == optind)
    274   1.1  christos 		usage();
    275   1.1  christos 
    276   1.4  christos 	// So that mktime behaves consistently
    277   1.4  christos 	setenv("TZ", "UTC", 1);
    278   1.4  christos 
    279   1.7  christos 	if (name == NULL)
    280   1.7  christos 		name = findCVSDir(path, sizeof(path), argv[optind]);
    281   1.7  christos 
    282   1.1  christos 	cvsscan(argv + optind, name, &lat);
    283   1.1  christos 	if (debug)
    284   1.1  christos 		printlat(&lat);
    285   1.1  christos 	printf("%jd\n", (intmax_t)lat.time);
    286   1.1  christos 	return 0;
    287   1.1  christos }
    288