1 /* $OpenBSD: rcsdiff.c,v 1.85 2019/06/28 13:35:03 deraadt Exp $ */ 2 /* 3 * Copyright (c) 2005 Joris Vink <joris@openbsd.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <sys/stat.h> 28 #include <sys/time.h> 29 30 #include <err.h> 31 #include <fcntl.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 37 #include "rcsprog.h" 38 #include "diff.h" 39 40 static int rcsdiff_file(RCSFILE *, RCSNUM *, const char *, int); 41 static int rcsdiff_rev(RCSFILE *, RCSNUM *, RCSNUM *, int); 42 static void push_ignore_pats(char *); 43 44 static int quiet; 45 static int kflag = RCS_KWEXP_ERR; 46 static char *diff_ignore_pats; 47 48 int 49 rcsdiff_main(int argc, char **argv) 50 { 51 int fd, i, ch, dflags, status; 52 RCSNUM *rev1, *rev2; 53 RCSFILE *file; 54 char fpath[PATH_MAX], *rev_str1, *rev_str2; 55 const char *errstr; 56 57 rev1 = rev2 = NULL; 58 rev_str1 = rev_str2 = NULL; 59 status = D_SAME; 60 dflags = 0; 61 62 if (strlcpy(diffargs, "diff", sizeof(diffargs)) >= sizeof(diffargs)) 63 errx(D_ERROR, "diffargs too long"); 64 65 while ((ch = rcs_getopt(argc, argv, "abC:cdI:ik:npqr:TtU:uVwx::z::")) != -1) { 66 switch (ch) { 67 case 'a': 68 if (strlcat(diffargs, " -a", sizeof(diffargs)) >= 69 sizeof(diffargs)) 70 errx(D_ERROR, "diffargs too long"); 71 dflags |= D_FORCEASCII; 72 break; 73 case 'b': 74 if (strlcat(diffargs, " -b", sizeof(diffargs)) >= 75 sizeof(diffargs)) 76 errx(D_ERROR, "diffargs too long"); 77 dflags |= D_FOLDBLANKS; 78 break; 79 case 'C': 80 (void)strlcat(diffargs, " -C", sizeof(diffargs)); 81 if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= 82 sizeof(diffargs)) 83 errx(D_ERROR, "diffargs too long"); 84 diff_context = strtonum(rcs_optarg, 0, INT_MAX, &errstr); 85 if (errstr) 86 errx(D_ERROR, "context is %s: %s", 87 errstr, rcs_optarg); 88 diff_format = D_CONTEXT; 89 break; 90 case 'c': 91 if (strlcat(diffargs, " -c", sizeof(diffargs)) >= 92 sizeof(diffargs)) 93 errx(D_ERROR, "diffargs too long"); 94 diff_format = D_CONTEXT; 95 break; 96 case 'd': 97 if (strlcat(diffargs, " -d", sizeof(diffargs)) >= 98 sizeof(diffargs)) 99 errx(D_ERROR, "diffargs too long"); 100 dflags |= D_MINIMAL; 101 break; 102 case 'i': 103 if (strlcat(diffargs, " -i", sizeof(diffargs)) >= 104 sizeof(diffargs)) 105 errx(D_ERROR, "diffargs too long"); 106 dflags |= D_IGNORECASE; 107 break; 108 case 'I': 109 (void)strlcat(diffargs, " -I", sizeof(diffargs)); 110 if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= 111 sizeof(diffargs)) 112 errx(D_ERROR, "diffargs too long"); 113 push_ignore_pats(rcs_optarg); 114 break; 115 case 'k': 116 kflag = rcs_kflag_get(rcs_optarg); 117 if (RCS_KWEXP_INVAL(kflag)) { 118 warnx("invalid RCS keyword substitution mode"); 119 (usage)(); 120 } 121 break; 122 case 'n': 123 if (strlcat(diffargs, " -n", sizeof(diffargs)) >= 124 sizeof(diffargs)) 125 errx(D_ERROR, "diffargs too long"); 126 diff_format = D_RCSDIFF; 127 break; 128 case 'p': 129 if (strlcat(diffargs, " -p", sizeof(diffargs)) >= 130 sizeof(diffargs)) 131 errx(D_ERROR, "diffargs too long"); 132 dflags |= D_PROTOTYPE; 133 break; 134 case 'q': 135 quiet = 1; 136 break; 137 case 'r': 138 rcs_setrevstr2(&rev_str1, &rev_str2, rcs_optarg); 139 break; 140 case 'T': 141 /* 142 * kept for compatibility 143 */ 144 break; 145 case 't': 146 if (strlcat(diffargs, " -t", sizeof(diffargs)) >= 147 sizeof(diffargs)) 148 errx(D_ERROR, "diffargs too long"); 149 dflags |= D_EXPANDTABS; 150 break; 151 case 'U': 152 (void)strlcat(diffargs, " -U", sizeof(diffargs)); 153 if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= 154 sizeof(diffargs)) 155 errx(D_ERROR, "diffargs too long"); 156 diff_context = strtonum(rcs_optarg, 0, INT_MAX, &errstr); 157 if (errstr) 158 errx(D_ERROR, "context is %s: %s", 159 errstr, rcs_optarg); 160 diff_format = D_UNIFIED; 161 break; 162 case 'u': 163 if (strlcat(diffargs, " -u", sizeof(diffargs)) >= 164 sizeof(diffargs)) 165 errx(D_ERROR, "diffargs too long"); 166 diff_format = D_UNIFIED; 167 break; 168 case 'V': 169 printf("%s\n", rcs_version); 170 exit(0); 171 case 'w': 172 if (strlcat(diffargs, " -w", sizeof(diffargs)) >= 173 sizeof(diffargs)) 174 errx(D_ERROR, "diffargs too long"); 175 dflags |= D_IGNOREBLANKS; 176 break; 177 case 'x': 178 /* Use blank extension if none given. */ 179 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 180 break; 181 case 'z': 182 timezone_flag = rcs_optarg; 183 break; 184 default: 185 (usage)(); 186 } 187 } 188 189 argc -= rcs_optind; 190 argv += rcs_optind; 191 192 if (argc == 0) { 193 warnx("no input file"); 194 (usage)(); 195 } 196 197 if (diff_ignore_pats != NULL) { 198 char buf[BUFSIZ]; 199 int error; 200 201 diff_ignore_re = xmalloc(sizeof(*diff_ignore_re)); 202 if ((error = regcomp(diff_ignore_re, diff_ignore_pats, 203 REG_NEWLINE | REG_EXTENDED)) != 0) { 204 regerror(error, diff_ignore_re, buf, sizeof(buf)); 205 if (*diff_ignore_pats != '\0') 206 errx(D_ERROR, "%s: %s", diff_ignore_pats, buf); 207 else 208 errx(D_ERROR, "%s", buf); 209 } 210 } 211 212 for (i = 0; i < argc; i++) { 213 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 214 if (fd < 0) { 215 warn("%s", fpath); 216 continue; 217 } 218 219 if ((file = rcs_open(fpath, fd, 220 RCS_READ|RCS_PARSE_FULLY)) == NULL) 221 continue; 222 223 rcs_kwexp_set(file, kflag); 224 225 if (rev_str1 != NULL) { 226 if ((rev1 = rcs_getrevnum(rev_str1, file)) == NULL) 227 errx(D_ERROR, "bad revision number"); 228 } 229 if (rev_str2 != NULL) { 230 if ((rev2 = rcs_getrevnum(rev_str2, file)) == NULL) 231 errx(D_ERROR, "bad revision number"); 232 } 233 234 if (!quiet) { 235 fprintf(stderr, "%s\n", RCS_DIFF_DIV); 236 fprintf(stderr, "RCS file: %s\n", fpath); 237 } 238 239 diff_file = argv[i]; 240 241 /* No revisions given. */ 242 if (rev_str1 == NULL) 243 status = rcsdiff_file(file, file->rf_head, argv[i], 244 dflags); 245 /* One revision given. */ 246 else if (rev_str2 == NULL) 247 status = rcsdiff_file(file, rev1, argv[i], dflags); 248 /* Two revisions given. */ 249 else 250 status = rcsdiff_rev(file, rev1, rev2, dflags); 251 252 rcs_close(file); 253 rcsnum_free(rev1); 254 rcsnum_free(rev2); 255 rev1 = rev2 = NULL; 256 } 257 258 return (status); 259 } 260 261 __dead void 262 rcsdiff_usage(void) 263 { 264 fprintf(stderr, 265 "usage: rcsdiff [-cnquV] [-kmode] [-rrev] [-xsuffixes] [-ztz]\n" 266 " [diff_options] file ...\n"); 267 268 exit(D_ERROR); 269 } 270 271 static int 272 rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename, int dflags) 273 { 274 int ret, fd; 275 time_t t; 276 struct stat st; 277 char *path1, *path2; 278 BUF *b1, *b2; 279 char rbuf[RCS_REV_BUFSZ]; 280 struct tm *tb; 281 struct timeval tv[2], tv2[2]; 282 283 memset(&tv, 0, sizeof(tv)); 284 memset(&tv2, 0, sizeof(tv2)); 285 286 ret = D_ERROR; 287 b1 = b2 = NULL; 288 289 diff_rev1 = rev; 290 diff_rev2 = NULL; 291 path1 = path2 = NULL; 292 293 if ((fd = open(filename, O_RDONLY)) == -1) { 294 warn("%s", filename); 295 goto out; 296 } 297 298 rcsnum_tostr(rev, rbuf, sizeof(rbuf)); 299 if (!quiet) { 300 fprintf(stderr, "retrieving revision %s\n", rbuf); 301 fprintf(stderr, "%s -r%s %s\n", diffargs, rbuf, filename); 302 } 303 304 if ((b1 = rcs_getrev(file, rev)) == NULL) { 305 warnx("failed to retrieve revision %s", rbuf); 306 goto out; 307 } 308 309 b1 = rcs_kwexp_buf(b1, file, rev); 310 tv[0].tv_sec = rcs_rev_getdate(file, rev); 311 tv[1].tv_sec = tv[0].tv_sec; 312 313 if ((b2 = buf_load(filename)) == NULL) { 314 warnx("failed to load file: `%s'", filename); 315 goto out; 316 } 317 318 /* XXX - GNU uses GMT */ 319 if (fstat(fd, &st) == -1) 320 err(D_ERROR, "%s", filename); 321 322 tb = gmtime(&st.st_mtime); 323 t = mktime(tb); 324 325 tv2[0].tv_sec = t; 326 tv2[1].tv_sec = t; 327 328 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 329 buf_write_stmp(b1, path1); 330 331 buf_free(b1); 332 b1 = NULL; 333 334 if (utimes(path1, (const struct timeval *)&tv) == -1) 335 warn("utimes"); 336 337 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 338 buf_write_stmp(b2, path2); 339 340 buf_free(b2); 341 b2 = NULL; 342 343 if (utimes(path2, (const struct timeval *)&tv2) == -1) 344 warn("utimes"); 345 346 ret = diffreg(path1, path2, NULL, dflags); 347 348 out: 349 if (fd != -1) 350 (void)close(fd); 351 buf_free(b1); 352 buf_free(b2); 353 free(path1); 354 free(path2); 355 356 return (ret); 357 } 358 359 static int 360 rcsdiff_rev(RCSFILE *file, RCSNUM *rev1, RCSNUM *rev2, int dflags) 361 { 362 struct timeval tv[2], tv2[2]; 363 BUF *b1, *b2; 364 int ret; 365 char *path1, *path2, rbuf1[RCS_REV_BUFSZ], rbuf2[RCS_REV_BUFSZ]; 366 367 ret = D_ERROR; 368 b1 = b2 = NULL; 369 memset(&tv, 0, sizeof(tv)); 370 memset(&tv2, 0, sizeof(tv2)); 371 372 diff_rev1 = rev1; 373 diff_rev2 = rev2; 374 path1 = path2 = NULL; 375 376 rcsnum_tostr(rev1, rbuf1, sizeof(rbuf1)); 377 if (!quiet) 378 fprintf(stderr, "retrieving revision %s\n", rbuf1); 379 380 if ((b1 = rcs_getrev(file, rev1)) == NULL) { 381 warnx("failed to retrieve revision %s", rbuf1); 382 goto out; 383 } 384 385 b1 = rcs_kwexp_buf(b1, file, rev1); 386 tv[0].tv_sec = rcs_rev_getdate(file, rev1); 387 tv[1].tv_sec = tv[0].tv_sec; 388 389 rcsnum_tostr(rev2, rbuf2, sizeof(rbuf2)); 390 if (!quiet) 391 fprintf(stderr, "retrieving revision %s\n", rbuf2); 392 393 if ((b2 = rcs_getrev(file, rev2)) == NULL) { 394 warnx("failed to retrieve revision %s", rbuf2); 395 goto out; 396 } 397 398 b2 = rcs_kwexp_buf(b2, file, rev2); 399 tv2[0].tv_sec = rcs_rev_getdate(file, rev2); 400 tv2[1].tv_sec = tv2[0].tv_sec; 401 402 if (!quiet) 403 fprintf(stderr, "%s -r%s -r%s\n", diffargs, rbuf1, rbuf2); 404 405 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 406 buf_write_stmp(b1, path1); 407 408 buf_free(b1); 409 b1 = NULL; 410 411 if (utimes(path1, (const struct timeval *)&tv) == -1) 412 warn("utimes"); 413 414 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 415 buf_write_stmp(b2, path2); 416 417 buf_free(b2); 418 b2 = NULL; 419 420 if (utimes(path2, (const struct timeval *)&tv2) == -1) 421 warn("utimes"); 422 423 ret = diffreg(path1, path2, NULL, dflags); 424 425 out: 426 buf_free(b1); 427 buf_free(b2); 428 free(path1); 429 free(path2); 430 431 return (ret); 432 } 433 434 static void 435 push_ignore_pats(char *pattern) 436 { 437 size_t len; 438 439 if (diff_ignore_pats == NULL) { 440 len = strlen(pattern) + 1; 441 diff_ignore_pats = xmalloc(len); 442 strlcpy(diff_ignore_pats, pattern, len); 443 } else { 444 /* old + "|" + new + NUL */ 445 len = strlen(diff_ignore_pats) + strlen(pattern) + 2; 446 diff_ignore_pats = xreallocarray(diff_ignore_pats, len, 1); 447 strlcat(diff_ignore_pats, "|", len); 448 strlcat(diff_ignore_pats, pattern, len); 449 } 450 } 451