Home | History | Annotate | Line # | Download | only in cvslatest
cvslatest.c revision 1.10
      1 /*	$NetBSD: cvslatest.c,v 1.10 2023/02/15 17:00:24 martin 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.10 2023/02/15 17:00:24 martin 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 getlatest(const FTSENT *e, const char *repo, struct latest *lat)
     96 {
     97 	static const char fmt[] = "%a %b %d %H:%M:%S %Y";
     98 	char name[MAXPATHLEN], ename[MAXPATHLEN];
     99 	char entry[MAXPATHLEN * 2];
    100 	char *fn, *dt, *p;
    101 	time_t t;
    102 	struct tm tm;
    103 	struct stat sb;
    104 	FILE *fp;
    105 
    106 	snprintf(name, sizeof(name), "%s/Entries", e->fts_accpath);
    107 	snprintf(ename, sizeof(ename), "%s/Entries", e->fts_path);
    108 	if ((fp = fopen(name, "r")) == NULL)
    109 		err(EXIT_FAILURE, "Can't open `%s'", ename);
    110 
    111 	while (fgets(entry, (int)sizeof(entry), fp) != NULL) {
    112 		if (entry[0] != '/')
    113 		    continue;
    114 		if ((fn = strtok(entry, "/")) == NULL)
    115 			goto mal;
    116 		if (strtok(NULL, "/") == NULL)
    117 			goto mal;
    118 		if ((dt = strtok(NULL, "/")) == NULL)
    119 			goto mal;
    120 		if (strcmp(dt, "dummy timestamp") == 0) {
    121 			warnx("Can't get timestamp from uncommitted file %s in `%s'",
    122 			    fn, ename);
    123 			if (!ignore)
    124 				exit(EXIT_FAILURE);
    125 			continue;
    126 		}
    127 		/*
    128 		 * This may not be visible in real world, but the cvs code
    129 		 * has paths that would create this string (mabe server
    130 		 * side only?)
    131 		 */
    132 		if (strcmp(dt, "dummy timestamp from new-entry") == 0) {
    133 			warnx("Can't get timestamp from uncommitted file %s in `%s'",
    134 			    fn, ename);
    135 			if (!ignore)
    136 				exit(EXIT_FAILURE);
    137 			continue;
    138 		}
    139 		if (strcmp(dt, "Result of merge") == 0) {
    140 			warnx("Can't get cvs timestamp for localy modified file %s in `%s', using modification time.",
    141 			    fn, ename);
    142 			if (fstat(fileno(fp), &sb) == 0) {
    143 				t = sb.st_mtime;
    144 				goto compare;
    145 			}
    146 			if (!ignore)
    147 				exit(EXIT_FAILURE);
    148 			continue;
    149 		}
    150 		if ((p = strptime(dt, fmt, &tm)) == NULL || *p) {
    151 			warnx("Malformed time `%s' in `%s'", dt, ename);
    152 			if (!ignore)
    153 				exit(EXIT_FAILURE);
    154 			continue;
    155 		}
    156 		tm.tm_isdst = 0;	// We are in GMT anyway
    157 		if ((t = mktime(&tm)) == (time_t)-1)
    158 			errx(EXIT_FAILURE, "Time conversion `%s' in `%s'",
    159 			    dt, ename);
    160 compare:
    161 		if (lat->time == 0 || lat->time < t) {
    162 			lat->time = t;
    163 			snprintf(lat->path, sizeof(lat->path),
    164 			    "%s/%s", repo, fn);
    165 			if (debug > 1)
    166 				printlat(lat);
    167 		}
    168 	}
    169 	if (ferror(fp))
    170 		err(EXIT_FAILURE, "Can't read `%s'", ename);
    171 
    172 	fclose(fp);
    173 	return;
    174 
    175 mal:
    176 	errx(EXIT_FAILURE, "Malformed line in `%s'", ename);
    177 }
    178 
    179 static void
    180 cvsscan(char **pathv, const char *name, struct latest *lat)
    181 {
    182         FTS *dh;
    183 	char repo[MAXPATHLEN];
    184         FTSENT *entry;
    185 
    186 	lat->time = 0;
    187 
    188         dh = fts_open(pathv, FTS_PHYSICAL, NULL);
    189         if (dh == NULL)
    190 		err(EXIT_FAILURE, "fts_open `%s'", pathv[0]);
    191 
    192         while ((entry = fts_read(dh)) != NULL) {
    193                 if (entry->fts_info != FTS_D)
    194 			continue;
    195 
    196 		if (strcmp(entry->fts_name, name) != 0)
    197                         continue;
    198 
    199 		getrepo(entry, repo, sizeof(repo));
    200 		getlatest(entry, repo, lat);
    201         }
    202 
    203         (void)fts_close(dh);
    204 }
    205 
    206 static __dead void
    207 usage(void)
    208 {
    209 	fprintf(stderr, "Usage: %s [-di] [-n <name>] <path> ...\n",
    210 	    getprogname());
    211 	exit(EXIT_FAILURE);
    212 }
    213 
    214 static int
    215 checkDir(char *path, size_t pathlen, const char *name)
    216 {
    217 	static const char *files[] = {
    218 		"Entries", "Root", "Repository",
    219 	};
    220 	size_t i;
    221 
    222 	for (i = 0; i < __arraycount(files); i++) {
    223 		snprintf(path, pathlen, "%s/%s", name, files[i]);
    224 		if (access(path, F_OK) == -1)
    225 			return 0;
    226 	}
    227 
    228 	return 1;
    229 }
    230 
    231 static const char *
    232 findCVSDir(char *path, size_t pathlen, const char *name)
    233 {
    234 	DIR *dirp;
    235 	struct dirent *dp;
    236 	const char *n;
    237 
    238 	if ((dirp = opendir(name)) == NULL)
    239 		err(EXIT_FAILURE, "Can't open `%s'", name);
    240 
    241 	while ((dp = readdir(dirp)) != NULL) {
    242 		n = dp->d_name;
    243 		if (n[0] == '.' && (n[1] == '\0' ||
    244 		    (n[1] == '.' && n[2] == '\0')))
    245 			continue;
    246 		if (checkDir(path, pathlen, n))
    247 			goto out;
    248 	}
    249 	n = "CVS";
    250 out:
    251 	strlcpy(path, n, pathlen);
    252 	closedir(dirp);
    253 	return path;
    254 }
    255 
    256 
    257 int
    258 main(int argc, char *argv[])
    259 {
    260 	struct latest lat;
    261 	const char *name = NULL;
    262 	char path[MAXPATHLEN];
    263 	int c;
    264 
    265 	while ((c = getopt(argc, argv, "din:")) != -1)
    266 		switch (c) {
    267 		case 'i':
    268 			ignore++;
    269 			break;
    270 		case 'd':
    271 			debug++;
    272 			break;
    273 		case 'n':
    274 			name = optarg;
    275 			break;
    276 		default:
    277 			usage();
    278 		}
    279 
    280 	if (argc == optind)
    281 		usage();
    282 
    283 	// So that mktime behaves consistently
    284 	setenv("TZ", "UTC", 1);
    285 
    286 	if (name == NULL)
    287 		name = findCVSDir(path, sizeof(path), argv[optind]);
    288 
    289 	cvsscan(argv + optind, name, &lat);
    290 	if (debug)
    291 		printlat(&lat);
    292 	printf("%jd\n", (intmax_t)lat.time);
    293 	return 0;
    294 }
    295