rcsutil.c revision 1.47
1104834Sobrien/* $OpenBSD: rcsutil.c,v 1.47 2020/10/14 20:07:19 naddy Exp $ */ 289857Sobrien/* 389857Sobrien * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org> 489857Sobrien * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org> 589857Sobrien * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org> 689857Sobrien * Copyright (c) 2006 Ray Lai <ray@openbsd.org> 789857Sobrien * All rights reserved. 889857Sobrien * 989857Sobrien * Redistribution and use in source and binary forms, with or without 1089857Sobrien * modification, are permitted provided that the following conditions 1189857Sobrien * are met: 1289857Sobrien * 1389857Sobrien * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 24 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 27 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include <sys/stat.h> 31#include <sys/time.h> 32 33#include <ctype.h> 34#include <err.h> 35#include <fcntl.h> 36#include <stdio.h> 37#include <stdlib.h> 38#include <string.h> 39#include <unistd.h> 40 41#include "rcsprog.h" 42 43/* 44 * rcs_get_mtime() 45 * 46 * Get <filename> last modified time. 47 * Returns last modified time on success, or -1 on failure. 48 */ 49time_t 50rcs_get_mtime(RCSFILE *file) 51{ 52 struct stat st; 53 time_t mtime; 54 55 if (file->rf_file == NULL) 56 return (-1); 57 58 if (fstat(fileno(file->rf_file), &st) == -1) { 59 warn("%s", file->rf_path); 60 return (-1); 61 } 62 63 mtime = st.st_mtimespec.tv_sec; 64 65 return (mtime); 66} 67 68/* 69 * rcs_set_mtime() 70 * 71 * Set <filename> last modified time to <mtime> if it's not set to -1. 72 */ 73void 74rcs_set_mtime(RCSFILE *file, time_t mtime) 75{ 76 static struct timeval tv[2]; 77 78 if (file->rf_file == NULL || mtime == -1) 79 return; 80 81 tv[0].tv_sec = mtime; 82 tv[1].tv_sec = tv[0].tv_sec; 83 84 if (futimes(fileno(file->rf_file), tv) == -1) 85 err(1, "utimes"); 86} 87 88int 89rcs_getopt(int argc, char **argv, const char *optstr) 90{ 91 char *a; 92 const char *c; 93 static int i = 1; 94 int opt, hasargument, ret; 95 96 hasargument = 0; 97 rcs_optarg = NULL; 98 99 if (i >= argc) 100 return (-1); 101 102 a = argv[i++]; 103 if (*a++ != '-') 104 return (-1); 105 106 ret = 0; 107 opt = *a; 108 for (c = optstr; *c != '\0'; c++) { 109 if (*c == opt) { 110 a++; 111 ret = opt; 112 113 if (*(c + 1) == ':') { 114 if (*(c + 2) == ':') { 115 if (*a != '\0') 116 hasargument = 1; 117 } else { 118 if (*a != '\0') { 119 hasargument = 1; 120 } else { 121 ret = 1; 122 break; 123 } 124 } 125 } 126 127 if (hasargument == 1) 128 rcs_optarg = a; 129 130 if (ret == opt) 131 rcs_optind++; 132 break; 133 } 134 } 135 136 if (ret == 0) 137 warnx("unknown option -%c", opt); 138 else if (ret == 1) 139 warnx("missing argument for option -%c", opt); 140 141 return (ret); 142} 143 144/* 145 * rcs_choosefile() 146 * 147 * Given a relative filename, decide where the corresponding RCS file 148 * should be. Tries each extension until a file is found. If no file 149 * was found, returns a path with the first extension. 150 * 151 * Opens and returns file descriptor to RCS file. 152 */ 153int 154rcs_choosefile(const char *filename, char *out, size_t len) 155{ 156 int fd; 157 struct stat sb; 158 char *p, *ext, name[PATH_MAX], *next, *ptr, rcsdir[PATH_MAX], 159 *suffixes, rcspath[PATH_MAX]; 160 161 /* 162 * If `filename' contains a directory, `rcspath' contains that 163 * directory, including a trailing slash. Otherwise `rcspath' 164 * contains an empty string. 165 */ 166 if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath)) 167 errx(1, "rcs_choosefile: truncation"); 168 169 /* If `/' is found, end string after `/'. */ 170 if ((ptr = strrchr(rcspath, '/')) != NULL) 171 *(++ptr) = '\0'; 172 else 173 rcspath[0] = '\0'; 174 175 /* Append RCS/ to `rcspath' if it exists. */ 176 if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) || 177 strlcat(rcsdir, RCSDIR, sizeof(rcsdir)) >= sizeof(rcsdir)) 178 errx(1, "rcs_choosefile: truncation"); 179 180 if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode)) 181 if (strlcpy(rcspath, rcsdir, sizeof(rcspath)) 182 >= sizeof(rcspath) || 183 strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath)) 184 errx(1, "rcs_choosefile: truncation"); 185 186 /* Name of file without path. */ 187 if ((ptr = strrchr(filename, '/')) == NULL) { 188 if (strlcpy(name, filename, sizeof(name)) >= sizeof(name)) 189 errx(1, "rcs_choosefile: truncation"); 190 } else { 191 /* Skip `/'. */ 192 if (strlcpy(name, ptr + 1, sizeof(name)) >= sizeof(name)) 193 errx(1, "rcs_choosefile: truncation"); 194 } 195 196 /* Name of RCS file without an extension. */ 197 if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath)) 198 errx(1, "rcs_choosefile: truncation"); 199 200 /* 201 * If only the empty suffix was given, use existing rcspath. 202 * This ensures that there is at least one suffix for strsep(). 203 */ 204 if (strcmp(rcs_suffixes, "") == 0) { 205 if (strlcpy(out, rcspath, len) >= len) 206 errx(1, "rcs_choosefile: truncation"); 207 fd = open(rcspath, O_RDONLY); 208 return (fd); 209 } 210 211 /* 212 * Cycle through slash-separated `rcs_suffixes', appending each 213 * extension to `rcspath' and testing if the file exists. If it 214 * does, return that string. Otherwise return path with first 215 * extension. 216 */ 217 suffixes = xstrdup(rcs_suffixes); 218 for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) { 219 char fpath[PATH_MAX]; 220 221 if ((p = strrchr(rcspath, ',')) != NULL) { 222 if (!strcmp(p, ext)) { 223 if ((fd = open(rcspath, O_RDONLY)) == -1) 224 continue; 225 226 if (fstat(fd, &sb) == -1) 227 err(1, "%s", rcspath); 228 229 if (strlcpy(out, rcspath, len) >= len) 230 errx(1, "rcs_choosefile: truncation"); 231 232 free(suffixes); 233 return (fd); 234 } 235 236 continue; 237 } 238 239 /* Construct RCS file path. */ 240 if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) || 241 strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath)) 242 errx(1, "rcs_choosefile: truncation"); 243 244 /* Don't use `filename' as RCS file. */ 245 if (strcmp(fpath, filename) == 0) 246 continue; 247 248 if ((fd = open(fpath, O_RDONLY)) == -1) 249 continue; 250 251 if (fstat(fd, &sb) == -1) 252 err(1, "%s", fpath); 253 254 if (strlcpy(out, fpath, len) >= len) 255 errx(1, "rcs_choosefile: truncation"); 256 257 free(suffixes); 258 return (fd); 259 } 260 261 /* 262 * `suffixes' should now be NUL separated, so the first 263 * extension can be read just by reading `suffixes'. 264 */ 265 if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath)) 266 errx(1, "rcs_choosefile: truncation"); 267 268 free(suffixes); 269 270 if (strlcpy(out, rcspath, len) >= len) 271 errx(1, "rcs_choosefile: truncation"); 272 273 fd = open(rcspath, O_RDONLY); 274 275 return (fd); 276} 277 278/* 279 * Set <str> to <new_str>. Print warning if <str> is redefined. 280 */ 281void 282rcs_setrevstr(char **str, char *new_str) 283{ 284 if (new_str == NULL) 285 return; 286 if (*str != NULL) 287 warnx("redefinition of revision number"); 288 *str = new_str; 289} 290 291/* 292 * Set <str1> or <str2> to <new_str>, depending on which is not set. 293 * If both are set, error out. 294 */ 295void 296rcs_setrevstr2(char **str1, char **str2, char *new_str) 297{ 298 if (new_str == NULL) 299 return; 300 if (*str1 == NULL) 301 *str1 = new_str; 302 else if (*str2 == NULL) 303 *str2 = new_str; 304 else 305 errx(1, "too many revision numbers"); 306} 307 308/* 309 * Get revision from file. The revision can be specified as a symbol or 310 * a revision number. 311 */ 312RCSNUM * 313rcs_getrevnum(const char *rev_str, RCSFILE *file) 314{ 315 RCSNUM *rev; 316 317 /* Search for symbol. */ 318 rev = rcs_sym_getrev(file, rev_str); 319 320 /* Search for revision number. */ 321 if (rev == NULL) 322 rev = rcsnum_parse(rev_str); 323 324 return (rev); 325} 326 327/* 328 * Prompt for and store user's input in an allocated string. 329 * 330 * Returns the string's pointer. 331 */ 332char * 333rcs_prompt(const char *prompt, int flags) 334{ 335 BUF *bp; 336 size_t len; 337 char *buf; 338 339 if (!(flags & INTERACTIVE) && isatty(STDIN_FILENO)) 340 flags |= INTERACTIVE; 341 342 bp = buf_alloc(0); 343 if (flags & INTERACTIVE) 344 (void)fprintf(stderr, "%s", prompt); 345 if (flags & INTERACTIVE) 346 (void)fprintf(stderr, ">> "); 347 clearerr(stdin); 348 while ((buf = fgetln(stdin, &len)) != NULL) { 349 /* The last line may not be EOL terminated. */ 350 if (buf[0] == '.' && (len == 1 || buf[1] == '\n')) 351 break; 352 else 353 buf_append(bp, buf, len); 354 355 if (flags & INTERACTIVE) 356 (void)fprintf(stderr, ">> "); 357 } 358 buf_putc(bp, '\0'); 359 360 return (buf_release(bp)); 361} 362 363u_int 364rcs_rev_select(RCSFILE *file, const char *range) 365{ 366 int i; 367 u_int nrev; 368 const char *ep; 369 char *lstr, *rstr; 370 struct rcs_delta *rdp; 371 struct rcs_argvector *revargv, *revrange; 372 RCSNUM lnum, rnum; 373 374 nrev = 0; 375 (void)memset(&lnum, 0, sizeof(lnum)); 376 (void)memset(&rnum, 0, sizeof(rnum)); 377 378 if (range == NULL) { 379 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) 380 if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) { 381 rdp->rd_flags |= RCS_RD_SELECT; 382 return (1); 383 } 384 return (0); 385 } 386 387 revargv = rcs_strsplit(range, ","); 388 for (i = 0; revargv->argv[i] != NULL; i++) { 389 revrange = rcs_strsplit(revargv->argv[i], ":"); 390 if (revrange->argv[0] == NULL) 391 /* should not happen */ 392 errx(1, "invalid revision range: %s", revargv->argv[i]); 393 else if (revrange->argv[1] == NULL) 394 lstr = rstr = revrange->argv[0]; 395 else { 396 if (revrange->argv[2] != NULL) 397 errx(1, "invalid revision range: %s", 398 revargv->argv[i]); 399 lstr = revrange->argv[0]; 400 rstr = revrange->argv[1]; 401 if (strcmp(lstr, "") == 0) 402 lstr = NULL; 403 if (strcmp(rstr, "") == 0) 404 rstr = NULL; 405 } 406 407 if (lstr == NULL) 408 lstr = RCS_HEAD_INIT; 409 if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0')) 410 errx(1, "invalid revision: %s", lstr); 411 412 if (rstr != NULL) { 413 if (rcsnum_aton(rstr, &ep, &rnum) == 0 || (*ep != '\0')) 414 errx(1, "invalid revision: %s", rstr); 415 } else 416 rcsnum_cpy(file->rf_head, &rnum, 0); 417 418 rcs_argv_destroy(revrange); 419 420 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) 421 if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 && 422 rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 && 423 !(rdp->rd_flags & RCS_RD_SELECT)) { 424 rdp->rd_flags |= RCS_RD_SELECT; 425 nrev++; 426 } 427 } 428 rcs_argv_destroy(revargv); 429 430 free(lnum.rn_id); 431 free(rnum.rn_id); 432 433 return (nrev); 434} 435 436/* 437 * Load description from <in> to <file>. 438 * If <in> starts with a `-', <in> is taken as the description. 439 * Otherwise <in> is the name of the file containing the description. 440 * If <in> is NULL, the description is read from stdin. 441 * Returns 0 on success, -1 on failure, setting errno. 442 */ 443int 444rcs_set_description(RCSFILE *file, const char *in, int flags) 445{ 446 BUF *bp; 447 char *content; 448 const char *prompt = 449 "enter description, terminated with single '.' or end of file:\n" 450 "NOTE: This is NOT the log message!\n"; 451 452 /* Description is in file <in>. */ 453 if (in != NULL && *in != '-') { 454 if ((bp = buf_load(in)) == NULL) 455 return (-1); 456 buf_putc(bp, '\0'); 457 content = buf_release(bp); 458 /* Description is in <in>. */ 459 } else if (in != NULL) 460 /* Skip leading `-'. */ 461 content = xstrdup(in + 1); 462 /* Get description from stdin. */ 463 else 464 content = rcs_prompt(prompt, flags); 465 466 rcs_desc_set(file, content); 467 free(content); 468 return (0); 469} 470 471/* 472 * Split the contents of a file into a list of lines. 473 */ 474struct rcs_lines * 475rcs_splitlines(u_char *data, size_t len) 476{ 477 u_char *c, *p; 478 struct rcs_lines *lines; 479 struct rcs_line *lp; 480 size_t i, tlen; 481 482 lines = xcalloc(1, sizeof(*lines)); 483 TAILQ_INIT(&(lines->l_lines)); 484 485 lp = xcalloc(1, sizeof(*lp)); 486 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 487 488 489 p = c = data; 490 for (i = 0; i < len; i++) { 491 if (*p == '\n' || (i == len - 1)) { 492 tlen = p - c + 1; 493 lp = xmalloc(sizeof(*lp)); 494 lp->l_line = c; 495 lp->l_len = tlen; 496 lp->l_lineno = ++(lines->l_nblines); 497 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 498 c = p + 1; 499 } 500 p++; 501 } 502 503 return (lines); 504} 505 506void 507rcs_freelines(struct rcs_lines *lines) 508{ 509 struct rcs_line *lp; 510 511 while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) { 512 TAILQ_REMOVE(&(lines->l_lines), lp, l_list); 513 free(lp); 514 } 515 516 free(lines); 517} 518 519BUF * 520rcs_patchfile(u_char *data, size_t dlen, u_char *patch, size_t plen, 521 int (*p)(struct rcs_lines *, struct rcs_lines *)) 522{ 523 struct rcs_lines *dlines, *plines; 524 struct rcs_line *lp; 525 BUF *res; 526 527 dlines = rcs_splitlines(data, dlen); 528 plines = rcs_splitlines(patch, plen); 529 530 if (p(dlines, plines) < 0) { 531 rcs_freelines(dlines); 532 rcs_freelines(plines); 533 return (NULL); 534 } 535 536 res = buf_alloc(1024); 537 TAILQ_FOREACH(lp, &dlines->l_lines, l_list) { 538 if (lp->l_line == NULL) 539 continue; 540 buf_append(res, lp->l_line, lp->l_len); 541 } 542 543 rcs_freelines(dlines); 544 rcs_freelines(plines); 545 return (res); 546} 547 548/* 549 * rcs_yesno() 550 * 551 * Read a char from standard input, returns defc if the 552 * user enters an equivalent to defc, else whatever char 553 * was entered. Converts input to lower case. 554 */ 555int 556rcs_yesno(int defc) 557{ 558 int c, ret; 559 560 fflush(stderr); 561 fflush(stdout); 562 563 clearerr(stdin); 564 if (isalpha(c = getchar())) 565 c = tolower(c); 566 if (c == defc || c == '\n' || (c == EOF && feof(stdin))) 567 ret = defc; 568 else 569 ret = c; 570 571 while (c != EOF && c != '\n') 572 c = getchar(); 573 574 return (ret); 575} 576 577/* 578 * rcs_strsplit() 579 * 580 * Split a string <str> of <sep>-separated values and allocate 581 * an argument vector for the values found. 582 */ 583struct rcs_argvector * 584rcs_strsplit(const char *str, const char *sep) 585{ 586 struct rcs_argvector *av; 587 size_t i = 0; 588 char *cp, *p; 589 590 cp = xstrdup(str); 591 av = xmalloc(sizeof(*av)); 592 av->str = cp; 593 av->argv = xmalloc(sizeof(*(av->argv))); 594 595 while ((p = strsep(&cp, sep)) != NULL) { 596 av->argv[i++] = p; 597 av->argv = xreallocarray(av->argv, 598 i + 1, sizeof(*(av->argv))); 599 } 600 av->argv[i] = NULL; 601 602 return (av); 603} 604 605/* 606 * rcs_argv_destroy() 607 * 608 * Free an argument vector previously allocated by rcs_strsplit(). 609 */ 610void 611rcs_argv_destroy(struct rcs_argvector *av) 612{ 613 free(av->str); 614 free(av->argv); 615 free(av); 616} 617 618/* 619 * Strip suffix from filename. 620 */ 621void 622rcs_strip_suffix(char *filename) 623{ 624 char *p, *suffixes, *next, *ext; 625 626 if ((p = strrchr(filename, ',')) != NULL) { 627 suffixes = xstrdup(rcs_suffixes); 628 for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) { 629 if (!strcmp(p, ext)) { 630 *p = '\0'; 631 break; 632 } 633 } 634 free(suffixes); 635 } 636} 637