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