1 /* $OpenBSD: rcsdiff.c,v 1.75 2007/07/03 00:56:23 ray 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 29 #include <err.h> 30 #include <fcntl.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 36 #include "rcsprog.h" 37 #include "diff.h" 38 39 static int rcsdiff_file(RCSFILE *, RCSNUM *, const char *, int); 40 static int rcsdiff_rev(RCSFILE *, RCSNUM *, RCSNUM *, int); 41 static void push_ignore_pats(char *); 42 43 static int quiet; 44 static int kflag = RCS_KWEXP_ERR; 45 static char *diff_ignore_pats; 46 47 int 48 rcsdiff_main(int argc, char **argv) 49 { 50 int fd, i, ch, dflags, status; 51 RCSNUM *rev1, *rev2; 52 RCSFILE *file; 53 char fpath[MAXPATHLEN], *rev_str1, *rev_str2; 54 const char *errstr; 55 56 rev1 = rev2 = NULL; 57 rev_str1 = rev_str2 = NULL; 58 status = D_SAME; 59 dflags = 0; 60 61 if (strlcpy(diffargs, "diff", sizeof(diffargs)) >= sizeof(diffargs)) 62 errx(D_ERROR, "diffargs too long"); 63 64 while ((ch = rcs_getopt(argc, argv, "abC:cdI:ik:npqr:TtU:uVwx::z::")) != -1) { 65 switch (ch) { 66 case 'a': 67 if (strlcat(diffargs, " -a", sizeof(diffargs)) >= 68 sizeof(diffargs)) 69 errx(D_ERROR, "diffargs too long"); 70 dflags |= D_FORCEASCII; 71 break; 72 case 'b': 73 if (strlcat(diffargs, " -b", sizeof(diffargs)) >= 74 sizeof(diffargs)) 75 errx(D_ERROR, "diffargs too long"); 76 dflags |= D_FOLDBLANKS; 77 break; 78 case 'C': 79 (void)strlcat(diffargs, " -C", sizeof(diffargs)); 80 if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= 81 sizeof(diffargs)) 82 errx(D_ERROR, "diffargs too long"); 83 diff_context = strtonum(rcs_optarg, 0, INT_MAX, &errstr); 84 if (errstr) 85 errx(D_ERROR, "context is %s: %s", 86 errstr, rcs_optarg); 87 diff_format = D_CONTEXT; 88 break; 89 case 'c': 90 if (strlcat(diffargs, " -c", sizeof(diffargs)) >= 91 sizeof(diffargs)) 92 errx(D_ERROR, "diffargs too long"); 93 diff_format = D_CONTEXT; 94 break; 95 case 'd': 96 if (strlcat(diffargs, " -d", sizeof(diffargs)) >= 97 sizeof(diffargs)) 98 errx(D_ERROR, "diffargs too long"); 99 dflags |= D_MINIMAL; 100 break; 101 case 'i': 102 if (strlcat(diffargs, " -i", sizeof(diffargs)) >= 103 sizeof(diffargs)) 104 errx(D_ERROR, "diffargs too long"); 105 dflags |= D_IGNORECASE; 106 break; 107 case 'I': 108 (void)strlcat(diffargs, " -I", sizeof(diffargs)); 109 if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= 110 sizeof(diffargs)) 111 errx(D_ERROR, "diffargs too long"); 112 push_ignore_pats(rcs_optarg); 113 break; 114 case 'k': 115 kflag = rcs_kflag_get(rcs_optarg); 116 if (RCS_KWEXP_INVAL(kflag)) { 117 warnx("invalid RCS keyword substitution mode"); 118 (usage)(); 119 exit(D_ERROR); 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 exit(D_ERROR); 187 } 188 } 189 190 argc -= rcs_optind; 191 argv += rcs_optind; 192 193 if (argc == 0) { 194 warnx("no input file"); 195 (usage)(); 196 exit(D_ERROR); 197 } 198 199 if (diff_ignore_pats != NULL) { 200 char buf[BUFSIZ]; 201 int error; 202 203 diff_ignore_re = xmalloc(sizeof(*diff_ignore_re)); 204 if ((error = regcomp(diff_ignore_re, diff_ignore_pats, 205 REG_NEWLINE | REG_EXTENDED)) != 0) { 206 regerror(error, diff_ignore_re, buf, sizeof(buf)); 207 if (*diff_ignore_pats != '\0') 208 errx(D_ERROR, "%s: %s", diff_ignore_pats, buf); 209 else 210 errx(D_ERROR, "%s", buf); 211 } 212 } 213 214 for (i = 0; i < argc; i++) { 215 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 216 if (fd < 0) { 217 warn("%s", fpath); 218 continue; 219 } 220 221 if ((file = rcs_open(fpath, fd, 222 RCS_READ|RCS_PARSE_FULLY)) == NULL) 223 continue; 224 225 rcs_kwexp_set(file, kflag); 226 227 if (rev_str1 != NULL) { 228 if ((rev1 = rcs_getrevnum(rev_str1, file)) == NULL) 229 errx(D_ERROR, "bad revision number"); 230 } 231 if (rev_str2 != NULL) { 232 if ((rev2 = rcs_getrevnum(rev_str2, file)) == NULL) 233 errx(D_ERROR, "bad revision number"); 234 } 235 236 if (!quiet) { 237 fprintf(stderr, "%s\n", RCS_DIFF_DIV); 238 fprintf(stderr, "RCS file: %s\n", fpath); 239 } 240 241 diff_file = argv[i]; 242 243 /* No revisions given. */ 244 if (rev_str1 == NULL) 245 status = rcsdiff_file(file, file->rf_head, argv[i], 246 dflags); 247 /* One revision given. */ 248 else if (rev_str2 == NULL) 249 status = rcsdiff_file(file, rev1, argv[i], dflags); 250 /* Two revisions given. */ 251 else 252 status = rcsdiff_rev(file, rev1, rev2, dflags); 253 254 rcs_close(file); 255 256 if (rev1 != NULL) { 257 rcsnum_free(rev1); 258 rev1 = NULL; 259 } 260 if (rev2 != NULL) { 261 rcsnum_free(rev2); 262 rev2 = NULL; 263 } 264 } 265 266 return (status); 267 } 268 269 void 270 rcsdiff_usage(void) 271 { 272 fprintf(stderr, 273 "usage: rcsdiff [-cnquV] [-kmode] [-rrev] [-xsuffixes] [-ztz]\n" 274 " [diff_options] file ...\n"); 275 } 276 277 static int 278 rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename, int dflags) 279 { 280 int ret, fd; 281 time_t t; 282 struct stat st; 283 char *path1, *path2; 284 BUF *b1, *b2; 285 char rbuf[RCS_REV_BUFSZ]; 286 struct tm *tb; 287 struct timeval tv[2], tv2[2]; 288 289 memset(&tv, 0, sizeof(tv)); 290 memset(&tv2, 0, sizeof(tv2)); 291 292 ret = D_ERROR; 293 b1 = b2 = NULL; 294 295 diff_rev1 = rev; 296 diff_rev2 = NULL; 297 path1 = path2 = NULL; 298 299 if ((fd = open(filename, O_RDONLY)) == -1) { 300 warn("%s", filename); 301 goto out; 302 } 303 304 rcsnum_tostr(rev, rbuf, sizeof(rbuf)); 305 if (!quiet) { 306 fprintf(stderr, "retrieving revision %s\n", rbuf); 307 fprintf(stderr, "%s -r%s %s\n", diffargs, rbuf, filename); 308 } 309 310 if ((b1 = rcs_getrev(file, rev)) == NULL) { 311 warnx("failed to retrieve revision %s", rbuf); 312 goto out; 313 } 314 315 b1 = rcs_kwexp_buf(b1, file, rev); 316 tv[0].tv_sec = (long)rcs_rev_getdate(file, rev); 317 tv[1].tv_sec = tv[0].tv_sec; 318 319 if ((b2 = rcs_buf_load(filename, BUF_AUTOEXT)) == NULL) { 320 warnx("failed to load file: `%s'", filename); 321 goto out; 322 } 323 324 /* XXX - GNU uses GMT */ 325 if (fstat(fd, &st) == -1) 326 err(D_ERROR, "%s", filename); 327 328 tb = gmtime(&st.st_mtime); 329 t = mktime(tb); 330 331 tv2[0].tv_sec = t; 332 tv2[1].tv_sec = t; 333 334 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 335 rcs_buf_write_stmp(b1, path1); 336 337 rcs_buf_free(b1); 338 b1 = NULL; 339 340 if (utimes(path1, (const struct timeval *)&tv) < 0) 341 warn("utimes"); 342 343 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 344 rcs_buf_write_stmp(b2, path2); 345 346 rcs_buf_free(b2); 347 b2 = NULL; 348 349 if (utimes(path2, (const struct timeval *)&tv2) < 0) 350 warn("utimes"); 351 352 ret = diffreg(path1, path2, NULL, dflags); 353 354 out: 355 if (fd != -1) 356 (void)close(fd); 357 if (b1 != NULL) 358 rcs_buf_free(b1); 359 if (b2 != NULL) 360 rcs_buf_free(b2); 361 if (path1 != NULL) 362 xfree(path1); 363 if (path2 != NULL) 364 xfree(path2); 365 366 return (ret); 367 } 368 369 static int 370 rcsdiff_rev(RCSFILE *file, RCSNUM *rev1, RCSNUM *rev2, int dflags) 371 { 372 struct timeval tv[2], tv2[2]; 373 BUF *b1, *b2; 374 int ret; 375 char *path1, *path2, rbuf1[RCS_REV_BUFSZ], rbuf2[RCS_REV_BUFSZ]; 376 377 ret = D_ERROR; 378 b1 = b2 = NULL; 379 memset(&tv, 0, sizeof(tv)); 380 memset(&tv2, 0, sizeof(tv2)); 381 382 diff_rev1 = rev1; 383 diff_rev2 = rev2; 384 path1 = path2 = NULL; 385 386 rcsnum_tostr(rev1, rbuf1, sizeof(rbuf1)); 387 if (!quiet) 388 fprintf(stderr, "retrieving revision %s\n", rbuf1); 389 390 if ((b1 = rcs_getrev(file, rev1)) == NULL) { 391 warnx("failed to retrieve revision %s", rbuf1); 392 goto out; 393 } 394 395 b1 = rcs_kwexp_buf(b1, file, rev1); 396 tv[0].tv_sec = (long)rcs_rev_getdate(file, rev1); 397 tv[1].tv_sec = tv[0].tv_sec; 398 399 rcsnum_tostr(rev2, rbuf2, sizeof(rbuf2)); 400 if (!quiet) 401 fprintf(stderr, "retrieving revision %s\n", rbuf2); 402 403 if ((b2 = rcs_getrev(file, rev2)) == NULL) { 404 warnx("failed to retrieve revision %s", rbuf2); 405 goto out; 406 } 407 408 b2 = rcs_kwexp_buf(b2, file, rev2); 409 tv2[0].tv_sec = (long)rcs_rev_getdate(file, rev2); 410 tv2[1].tv_sec = tv2[0].tv_sec; 411 412 if (!quiet) 413 fprintf(stderr, "%s -r%s -r%s\n", diffargs, rbuf1, rbuf2); 414 415 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 416 rcs_buf_write_stmp(b1, path1); 417 418 rcs_buf_free(b1); 419 b1 = NULL; 420 421 if (utimes(path1, (const struct timeval *)&tv) < 0) 422 warn("utimes"); 423 424 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 425 rcs_buf_write_stmp(b2, path2); 426 427 rcs_buf_free(b2); 428 b2 = NULL; 429 430 if (utimes(path2, (const struct timeval *)&tv2) < 0) 431 warn("utimes"); 432 433 ret = diffreg(path1, path2, NULL, dflags); 434 435 out: 436 if (b1 != NULL) 437 rcs_buf_free(b1); 438 if (b2 != NULL) 439 rcs_buf_free(b2); 440 if (path1 != NULL) 441 xfree(path1); 442 if (path2 != NULL) 443 xfree(path2); 444 445 return (ret); 446 } 447 448 static void 449 push_ignore_pats(char *pattern) 450 { 451 size_t len; 452 453 if (diff_ignore_pats == NULL) { 454 len = strlen(pattern) + 1; 455 diff_ignore_pats = xmalloc(len); 456 strlcpy(diff_ignore_pats, pattern, len); 457 } else { 458 /* old + "|" + new + NUL */ 459 len = strlen(diff_ignore_pats) + strlen(pattern) + 2; 460 diff_ignore_pats = xrealloc(diff_ignore_pats, len, 1); 461 strlcat(diff_ignore_pats, "|", len); 462 strlcat(diff_ignore_pats, pattern, len); 463 } 464 } 465