1 /* $OpenBSD: rcsdiff.c,v 1.78 2010/12/06 22:50:34 chl 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[MAXPATHLEN], *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 exit(D_ERROR); 121 } 122 break; 123 case 'n': 124 if (strlcat(diffargs, " -n", sizeof(diffargs)) >= 125 sizeof(diffargs)) 126 errx(D_ERROR, "diffargs too long"); 127 diff_format = D_RCSDIFF; 128 break; 129 case 'p': 130 if (strlcat(diffargs, " -p", sizeof(diffargs)) >= 131 sizeof(diffargs)) 132 errx(D_ERROR, "diffargs too long"); 133 dflags |= D_PROTOTYPE; 134 break; 135 case 'q': 136 quiet = 1; 137 break; 138 case 'r': 139 rcs_setrevstr2(&rev_str1, &rev_str2, rcs_optarg); 140 break; 141 case 'T': 142 /* 143 * kept for compatibility 144 */ 145 break; 146 case 't': 147 if (strlcat(diffargs, " -t", sizeof(diffargs)) >= 148 sizeof(diffargs)) 149 errx(D_ERROR, "diffargs too long"); 150 dflags |= D_EXPANDTABS; 151 break; 152 case 'U': 153 (void)strlcat(diffargs, " -U", sizeof(diffargs)); 154 if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= 155 sizeof(diffargs)) 156 errx(D_ERROR, "diffargs too long"); 157 diff_context = strtonum(rcs_optarg, 0, INT_MAX, &errstr); 158 if (errstr) 159 errx(D_ERROR, "context is %s: %s", 160 errstr, rcs_optarg); 161 diff_format = D_UNIFIED; 162 break; 163 case 'u': 164 if (strlcat(diffargs, " -u", sizeof(diffargs)) >= 165 sizeof(diffargs)) 166 errx(D_ERROR, "diffargs too long"); 167 diff_format = D_UNIFIED; 168 break; 169 case 'V': 170 printf("%s\n", rcs_version); 171 exit(0); 172 case 'w': 173 if (strlcat(diffargs, " -w", sizeof(diffargs)) >= 174 sizeof(diffargs)) 175 errx(D_ERROR, "diffargs too long"); 176 dflags |= D_IGNOREBLANKS; 177 break; 178 case 'x': 179 /* Use blank extension if none given. */ 180 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 181 break; 182 case 'z': 183 timezone_flag = rcs_optarg; 184 break; 185 default: 186 (usage)(); 187 exit(D_ERROR); 188 } 189 } 190 191 argc -= rcs_optind; 192 argv += rcs_optind; 193 194 if (argc == 0) { 195 warnx("no input file"); 196 (usage)(); 197 exit(D_ERROR); 198 } 199 200 if (diff_ignore_pats != NULL) { 201 char buf[BUFSIZ]; 202 int error; 203 204 diff_ignore_re = xmalloc(sizeof(*diff_ignore_re)); 205 if ((error = regcomp(diff_ignore_re, diff_ignore_pats, 206 REG_NEWLINE | REG_EXTENDED)) != 0) { 207 regerror(error, diff_ignore_re, buf, sizeof(buf)); 208 if (*diff_ignore_pats != '\0') 209 errx(D_ERROR, "%s: %s", diff_ignore_pats, buf); 210 else 211 errx(D_ERROR, "%s", buf); 212 } 213 } 214 215 for (i = 0; i < argc; i++) { 216 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 217 if (fd < 0) { 218 warn("%s", fpath); 219 continue; 220 } 221 222 if ((file = rcs_open(fpath, fd, 223 RCS_READ|RCS_PARSE_FULLY)) == NULL) 224 continue; 225 226 rcs_kwexp_set(file, kflag); 227 228 if (rev_str1 != NULL) { 229 if ((rev1 = rcs_getrevnum(rev_str1, file)) == NULL) 230 errx(D_ERROR, "bad revision number"); 231 } 232 if (rev_str2 != NULL) { 233 if ((rev2 = rcs_getrevnum(rev_str2, file)) == NULL) 234 errx(D_ERROR, "bad revision number"); 235 } 236 237 if (!quiet) { 238 fprintf(stderr, "%s\n", RCS_DIFF_DIV); 239 fprintf(stderr, "RCS file: %s\n", fpath); 240 } 241 242 diff_file = argv[i]; 243 244 /* No revisions given. */ 245 if (rev_str1 == NULL) 246 status = rcsdiff_file(file, file->rf_head, argv[i], 247 dflags); 248 /* One revision given. */ 249 else if (rev_str2 == NULL) 250 status = rcsdiff_file(file, rev1, argv[i], dflags); 251 /* Two revisions given. */ 252 else 253 status = rcsdiff_rev(file, rev1, rev2, dflags); 254 255 rcs_close(file); 256 257 if (rev1 != NULL) { 258 rcsnum_free(rev1); 259 rev1 = NULL; 260 } 261 if (rev2 != NULL) { 262 rcsnum_free(rev2); 263 rev2 = NULL; 264 } 265 } 266 267 return (status); 268 } 269 270 void 271 rcsdiff_usage(void) 272 { 273 fprintf(stderr, 274 "usage: rcsdiff [-cnquV] [-kmode] [-rrev] [-xsuffixes] [-ztz]\n" 275 " [diff_options] file ...\n"); 276 } 277 278 static int 279 rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename, int dflags) 280 { 281 int ret, fd; 282 time_t t; 283 struct stat st; 284 char *path1, *path2; 285 BUF *b1, *b2; 286 char rbuf[RCS_REV_BUFSZ]; 287 struct tm *tb; 288 struct timeval tv[2], tv2[2]; 289 290 memset(&tv, 0, sizeof(tv)); 291 memset(&tv2, 0, sizeof(tv2)); 292 293 ret = D_ERROR; 294 b1 = b2 = NULL; 295 296 diff_rev1 = rev; 297 diff_rev2 = NULL; 298 path1 = path2 = NULL; 299 300 if ((fd = open(filename, O_RDONLY)) == -1) { 301 warn("%s", filename); 302 goto out; 303 } 304 305 rcsnum_tostr(rev, rbuf, sizeof(rbuf)); 306 if (!quiet) { 307 fprintf(stderr, "retrieving revision %s\n", rbuf); 308 fprintf(stderr, "%s -r%s %s\n", diffargs, rbuf, filename); 309 } 310 311 if ((b1 = rcs_getrev(file, rev)) == NULL) { 312 warnx("failed to retrieve revision %s", rbuf); 313 goto out; 314 } 315 316 b1 = rcs_kwexp_buf(b1, file, rev); 317 tv[0].tv_sec = (long)rcs_rev_getdate(file, rev); 318 tv[1].tv_sec = tv[0].tv_sec; 319 320 if ((b2 = buf_load(filename)) == NULL) { 321 warnx("failed to load file: `%s'", filename); 322 goto out; 323 } 324 325 /* XXX - GNU uses GMT */ 326 if (fstat(fd, &st) == -1) 327 err(D_ERROR, "%s", filename); 328 329 tb = gmtime(&st.st_mtime); 330 t = mktime(tb); 331 332 tv2[0].tv_sec = t; 333 tv2[1].tv_sec = t; 334 335 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 336 buf_write_stmp(b1, path1); 337 338 buf_free(b1); 339 b1 = NULL; 340 341 if (utimes(path1, (const struct timeval *)&tv) < 0) 342 warn("utimes"); 343 344 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 345 buf_write_stmp(b2, path2); 346 347 buf_free(b2); 348 b2 = NULL; 349 350 if (utimes(path2, (const struct timeval *)&tv2) < 0) 351 warn("utimes"); 352 353 ret = diffreg(path1, path2, NULL, dflags); 354 355 out: 356 if (fd != -1) 357 (void)close(fd); 358 if (b1 != NULL) 359 buf_free(b1); 360 if (b2 != NULL) 361 buf_free(b2); 362 if (path1 != NULL) 363 xfree(path1); 364 if (path2 != NULL) 365 xfree(path2); 366 367 return (ret); 368 } 369 370 static int 371 rcsdiff_rev(RCSFILE *file, RCSNUM *rev1, RCSNUM *rev2, int dflags) 372 { 373 struct timeval tv[2], tv2[2]; 374 BUF *b1, *b2; 375 int ret; 376 char *path1, *path2, rbuf1[RCS_REV_BUFSZ], rbuf2[RCS_REV_BUFSZ]; 377 378 ret = D_ERROR; 379 b1 = b2 = NULL; 380 memset(&tv, 0, sizeof(tv)); 381 memset(&tv2, 0, sizeof(tv2)); 382 383 diff_rev1 = rev1; 384 diff_rev2 = rev2; 385 path1 = path2 = NULL; 386 387 rcsnum_tostr(rev1, rbuf1, sizeof(rbuf1)); 388 if (!quiet) 389 fprintf(stderr, "retrieving revision %s\n", rbuf1); 390 391 if ((b1 = rcs_getrev(file, rev1)) == NULL) { 392 warnx("failed to retrieve revision %s", rbuf1); 393 goto out; 394 } 395 396 b1 = rcs_kwexp_buf(b1, file, rev1); 397 tv[0].tv_sec = (long)rcs_rev_getdate(file, rev1); 398 tv[1].tv_sec = tv[0].tv_sec; 399 400 rcsnum_tostr(rev2, rbuf2, sizeof(rbuf2)); 401 if (!quiet) 402 fprintf(stderr, "retrieving revision %s\n", rbuf2); 403 404 if ((b2 = rcs_getrev(file, rev2)) == NULL) { 405 warnx("failed to retrieve revision %s", rbuf2); 406 goto out; 407 } 408 409 b2 = rcs_kwexp_buf(b2, file, rev2); 410 tv2[0].tv_sec = (long)rcs_rev_getdate(file, rev2); 411 tv2[1].tv_sec = tv2[0].tv_sec; 412 413 if (!quiet) 414 fprintf(stderr, "%s -r%s -r%s\n", diffargs, rbuf1, rbuf2); 415 416 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 417 buf_write_stmp(b1, path1); 418 419 buf_free(b1); 420 b1 = NULL; 421 422 if (utimes(path1, (const struct timeval *)&tv) < 0) 423 warn("utimes"); 424 425 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 426 buf_write_stmp(b2, path2); 427 428 buf_free(b2); 429 b2 = NULL; 430 431 if (utimes(path2, (const struct timeval *)&tv2) < 0) 432 warn("utimes"); 433 434 ret = diffreg(path1, path2, NULL, dflags); 435 436 out: 437 if (b1 != NULL) 438 buf_free(b1); 439 if (b2 != NULL) 440 buf_free(b2); 441 if (path1 != NULL) 442 xfree(path1); 443 if (path2 != NULL) 444 xfree(path2); 445 446 return (ret); 447 } 448 449 static void 450 push_ignore_pats(char *pattern) 451 { 452 size_t len; 453 454 if (diff_ignore_pats == NULL) { 455 len = strlen(pattern) + 1; 456 diff_ignore_pats = xmalloc(len); 457 strlcpy(diff_ignore_pats, pattern, len); 458 } else { 459 /* old + "|" + new + NUL */ 460 len = strlen(diff_ignore_pats) + strlen(pattern) + 2; 461 diff_ignore_pats = xrealloc(diff_ignore_pats, len, 1); 462 strlcat(diff_ignore_pats, "|", len); 463 strlcat(diff_ignore_pats, pattern, len); 464 } 465 } 466