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