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