1 /* $OpenBSD: rcsprog.c,v 1.162 2020/06/09 20:05:40 joris Exp $ */ 2 /* 3 * Copyright (c) 2005 Jean-Francois Brousseau <jfb@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 <signal.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 36 #include "rcsprog.h" 37 38 #define RCSPROG_OPTSTRING "A:a:b::c:e::Iik:Ll::m:Mn:N:o:qt::TUu::Vx::z::" 39 40 const char rcs_version[] = "OpenRCS 4.5"; 41 42 int rcsflags; 43 int rcs_optind; 44 char *rcs_optarg; 45 char *rcs_suffixes = RCS_DEFAULT_SUFFIX; 46 char *rcs_tmpdir = RCS_TMPDIR_DEFAULT; 47 48 struct rcs_prog { 49 char *prog_name; 50 int (*prog_hdlr)(int, char **); 51 void (*prog_usage)(void); 52 } programs[] = { 53 { "rcs", rcs_main, rcs_usage }, 54 { "ci", checkin_main, checkin_usage }, 55 { "co", checkout_main, checkout_usage }, 56 { "rcsclean", rcsclean_main, rcsclean_usage }, 57 { "rcsdiff", rcsdiff_main, rcsdiff_usage }, 58 { "rcsmerge", rcsmerge_main, rcsmerge_usage }, 59 { "rlog", rlog_main, rlog_usage }, 60 { "ident", ident_main, ident_usage }, 61 { "merge", merge_main, merge_usage }, 62 }; 63 64 struct wklhead temp_files; 65 66 void sighdlr(int); 67 static void rcs_attach_symbol(RCSFILE *, const char *); 68 69 /* ARGSUSED */ 70 void 71 sighdlr(int sig) 72 { 73 worklist_clean(&temp_files, worklist_unlink); 74 _exit(1); 75 } 76 77 int 78 build_cmd(char ***cmd_argv, char **argv, int argc) 79 { 80 int cmd_argc, i, cur; 81 char *cp, *rcsinit, *linebuf, *lp; 82 83 if ((rcsinit = getenv("RCSINIT")) == NULL) { 84 *cmd_argv = argv; 85 return argc; 86 } 87 88 cur = argc + 2; 89 cmd_argc = 0; 90 *cmd_argv = xcalloc(cur, sizeof(char *)); 91 (*cmd_argv)[cmd_argc++] = argv[0]; 92 93 linebuf = xstrdup(rcsinit); 94 for (lp = linebuf; lp != NULL;) { 95 cp = strsep(&lp, " \t\b\f\n\r\t\v"); 96 if (cp == NULL) 97 break; 98 if (*cp == '\0') 99 continue; 100 101 if (cmd_argc == cur) { 102 cur += 8; 103 *cmd_argv = xreallocarray(*cmd_argv, cur, 104 sizeof(char *)); 105 } 106 107 (*cmd_argv)[cmd_argc++] = cp; 108 } 109 110 if (cmd_argc + argc > cur) { 111 cur = cmd_argc + argc + 1; 112 *cmd_argv = xreallocarray(*cmd_argv, cur, 113 sizeof(char *)); 114 } 115 116 for (i = 1; i < argc; i++) 117 (*cmd_argv)[cmd_argc++] = argv[i]; 118 119 (*cmd_argv)[cmd_argc] = NULL; 120 121 return cmd_argc; 122 } 123 124 int 125 main(int argc, char **argv) 126 { 127 u_int i; 128 char **cmd_argv; 129 int ret, cmd_argc; 130 131 if (pledge("stdio rpath wpath cpath fattr flock getpw", NULL) == -1) 132 err(2, "pledge"); 133 134 ret = -1; 135 rcs_optind = 1; 136 SLIST_INIT(&temp_files); 137 138 cmd_argc = build_cmd(&cmd_argv, argv, argc); 139 140 if ((rcs_tmpdir = getenv("TMPDIR")) == NULL) 141 rcs_tmpdir = RCS_TMPDIR_DEFAULT; 142 143 signal(SIGHUP, sighdlr); 144 signal(SIGINT, sighdlr); 145 signal(SIGQUIT, sighdlr); 146 signal(SIGABRT, sighdlr); 147 signal(SIGALRM, sighdlr); 148 signal(SIGTERM, sighdlr); 149 150 for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++) 151 if (strcmp(__progname, programs[i].prog_name) == 0) { 152 usage = programs[i].prog_usage; 153 ret = programs[i].prog_hdlr(cmd_argc, cmd_argv); 154 break; 155 } 156 157 /* clean up temporary files */ 158 worklist_run(&temp_files, worklist_unlink); 159 160 exit(ret); 161 } 162 163 164 __dead void 165 rcs_usage(void) 166 { 167 fprintf(stderr, 168 "usage: rcs [-IiLqTUV] [-Aoldfile] [-ausers] [-b[rev]]\n" 169 " [-cstring] [-e[users]] [-kmode] [-l[rev]] [-mrev:msg]\n" 170 " [-orev] [-t[str]] [-u[rev]] [-xsuffixes] file ...\n"); 171 172 exit(1); 173 } 174 175 /* 176 * rcs_main() 177 * 178 * Handler for the `rcs' program. 179 * Returns 0 on success, or >0 on error. 180 */ 181 int 182 rcs_main(int argc, char **argv) 183 { 184 int fd; 185 int i, j, ch, flags, kflag, lkmode; 186 const char *nflag, *oldfilename, *orange; 187 char fpath[PATH_MAX]; 188 char *logstr, *logmsg, *descfile; 189 char *alist, *comment, *elist, *lrev, *urev; 190 mode_t fmode; 191 RCSFILE *file; 192 RCSNUM *logrev; 193 struct rcs_access *acp; 194 time_t rcs_mtime = -1; 195 196 kflag = RCS_KWEXP_ERR; 197 lkmode = RCS_LOCK_INVAL; 198 fmode = S_IRUSR|S_IRGRP|S_IROTH; 199 flags = RCS_RDWR|RCS_PARSE_FULLY; 200 lrev = urev = descfile = NULL; 201 logstr = alist = comment = elist = NULL; 202 nflag = oldfilename = orange = NULL; 203 204 /* match GNU */ 205 if (1 < argc && argv[1][0] != '-') 206 warnx("warning: No options were given; " 207 "this usage is obsolescent."); 208 209 while ((ch = rcs_getopt(argc, argv, RCSPROG_OPTSTRING)) != -1) { 210 switch (ch) { 211 case 'A': 212 oldfilename = rcs_optarg; 213 rcsflags |= CO_ACLAPPEND; 214 break; 215 case 'a': 216 alist = rcs_optarg; 217 break; 218 case 'c': 219 comment = rcs_optarg; 220 break; 221 case 'e': 222 elist = rcs_optarg; 223 rcsflags |= RCSPROG_EFLAG; 224 break; 225 case 'I': 226 rcsflags |= INTERACTIVE; 227 break; 228 case 'i': 229 flags |= RCS_CREATE; 230 break; 231 case 'k': 232 kflag = rcs_kflag_get(rcs_optarg); 233 if (RCS_KWEXP_INVAL(kflag)) { 234 warnx("invalid RCS keyword substitution mode"); 235 (usage)(); 236 } 237 break; 238 case 'L': 239 if (lkmode == RCS_LOCK_LOOSE) 240 warnx("-U overridden by -L"); 241 lkmode = RCS_LOCK_STRICT; 242 break; 243 case 'l': 244 if (rcsflags & RCSPROG_UFLAG) 245 warnx("-u overridden by -l"); 246 lrev = rcs_optarg; 247 rcsflags &= ~RCSPROG_UFLAG; 248 rcsflags |= RCSPROG_LFLAG; 249 break; 250 case 'm': 251 free(logstr); 252 logstr = xstrdup(rcs_optarg); 253 break; 254 case 'M': 255 /* ignore for the moment */ 256 break; 257 case 'n': 258 nflag = rcs_optarg; 259 break; 260 case 'N': 261 nflag = rcs_optarg; 262 rcsflags |= RCSPROG_NFLAG; 263 break; 264 case 'o': 265 orange = rcs_optarg; 266 break; 267 case 'q': 268 rcsflags |= QUIET; 269 break; 270 case 't': 271 descfile = rcs_optarg; 272 rcsflags |= DESCRIPTION; 273 break; 274 case 'T': 275 rcsflags |= PRESERVETIME; 276 break; 277 case 'U': 278 if (lkmode == RCS_LOCK_STRICT) 279 warnx("-L overridden by -U"); 280 lkmode = RCS_LOCK_LOOSE; 281 break; 282 case 'u': 283 if (rcsflags & RCSPROG_LFLAG) 284 warnx("-l overridden by -u"); 285 urev = rcs_optarg; 286 rcsflags &= ~RCSPROG_LFLAG; 287 rcsflags |= RCSPROG_UFLAG; 288 break; 289 case 'V': 290 printf("%s\n", rcs_version); 291 exit(0); 292 case 'x': 293 /* Use blank extension if none given. */ 294 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 295 break; 296 case 'z': 297 /* 298 * kept for compatibility 299 */ 300 break; 301 default: 302 (usage)(); 303 } 304 } 305 306 argc -= rcs_optind; 307 argv += rcs_optind; 308 309 if (argc == 0) { 310 warnx("no input file"); 311 (usage)(); 312 } 313 314 for (i = 0; i < argc; i++) { 315 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 316 if (fd < 0 && !(flags & RCS_CREATE)) { 317 warn("%s", fpath); 318 continue; 319 } 320 321 if (!(rcsflags & QUIET)) 322 (void)fprintf(stderr, "RCS file: %s\n", fpath); 323 324 if ((file = rcs_open(fpath, fd, flags, fmode)) == NULL) { 325 close(fd); 326 continue; 327 } 328 329 if (rcsflags & DESCRIPTION) { 330 if (rcs_set_description(file, descfile, rcsflags) == -1) { 331 warn("%s", descfile); 332 rcs_close(file); 333 continue; 334 } 335 } 336 else if (flags & RCS_CREATE) { 337 if (rcs_set_description(file, NULL, rcsflags) == -1) { 338 warn("stdin"); 339 rcs_close(file); 340 continue; 341 } 342 } 343 344 if (rcsflags & PRESERVETIME) 345 rcs_mtime = rcs_get_mtime(file); 346 347 if (nflag != NULL) 348 rcs_attach_symbol(file, nflag); 349 350 if (logstr != NULL) { 351 if ((logmsg = strchr(logstr, ':')) == NULL) { 352 warnx("missing log message"); 353 rcs_close(file); 354 continue; 355 } 356 357 *logmsg++ = '\0'; 358 if ((logrev = rcsnum_parse(logstr)) == NULL) { 359 warnx("`%s' bad revision number", logstr); 360 rcs_close(file); 361 continue; 362 } 363 364 if (rcs_rev_setlog(file, logrev, logmsg) < 0) { 365 warnx("failed to set logmsg for `%s' to `%s'", 366 logstr, logmsg); 367 rcs_close(file); 368 rcsnum_free(logrev); 369 continue; 370 } 371 372 rcsnum_free(logrev); 373 } 374 375 /* entries to add from <oldfile> */ 376 if (rcsflags & CO_ACLAPPEND) { 377 RCSFILE *oldfile; 378 int ofd; 379 char ofpath[PATH_MAX]; 380 381 ofd = rcs_choosefile(oldfilename, ofpath, sizeof(ofpath)); 382 if (ofd < 0) { 383 if (!(flags & RCS_CREATE)) 384 warn("%s", ofpath); 385 exit(1); 386 } 387 if ((oldfile = rcs_open(ofpath, ofd, RCS_READ)) == NULL) 388 exit(1); 389 390 TAILQ_FOREACH(acp, &(oldfile->rf_access), ra_list) 391 rcs_access_add(file, acp->ra_name); 392 393 rcs_close(oldfile); 394 (void)close(ofd); 395 } 396 397 /* entries to add to the access list */ 398 if (alist != NULL) { 399 struct rcs_argvector *aargv; 400 401 aargv = rcs_strsplit(alist, ","); 402 for (j = 0; aargv->argv[j] != NULL; j++) 403 rcs_access_add(file, aargv->argv[j]); 404 405 rcs_argv_destroy(aargv); 406 } 407 408 if (comment != NULL) 409 rcs_comment_set(file, comment); 410 411 if (elist != NULL) { 412 struct rcs_argvector *eargv; 413 414 eargv = rcs_strsplit(elist, ","); 415 for (j = 0; eargv->argv[j] != NULL; j++) 416 rcs_access_remove(file, eargv->argv[j]); 417 418 rcs_argv_destroy(eargv); 419 } else if (rcsflags & RCSPROG_EFLAG) { 420 struct rcs_access *rap; 421 422 /* XXX rcs_access_remove(file, NULL); ?? */ 423 while (!TAILQ_EMPTY(&(file->rf_access))) { 424 rap = TAILQ_FIRST(&(file->rf_access)); 425 TAILQ_REMOVE(&(file->rf_access), rap, ra_list); 426 free(rap->ra_name); 427 free(rap); 428 } 429 /* not synced anymore */ 430 file->rf_flags &= ~RCS_SYNCED; 431 } 432 433 rcs_kwexp_set(file, kflag); 434 435 if (lkmode != RCS_LOCK_INVAL) 436 (void)rcs_lock_setmode(file, lkmode); 437 438 if (rcsflags & RCSPROG_LFLAG) { 439 RCSNUM *rev; 440 const char *username; 441 char rev_str[RCS_REV_BUFSZ]; 442 443 if (file->rf_head == NULL) { 444 warnx("%s contains no revisions", fpath); 445 rcs_close(file); 446 continue; 447 } 448 449 if ((username = getlogin()) == NULL) 450 err(1, "getlogin"); 451 if (lrev == NULL) { 452 rev = rcsnum_alloc(); 453 rcsnum_cpy(file->rf_head, rev, 0); 454 } else if ((rev = rcsnum_parse(lrev)) == NULL) { 455 warnx("unable to unlock file"); 456 rcs_close(file); 457 continue; 458 } 459 rcsnum_tostr(rev, rev_str, sizeof(rev_str)); 460 /* Make sure revision exists. */ 461 if (rcs_findrev(file, rev) == NULL) 462 errx(1, "%s: cannot lock nonexisting " 463 "revision %s", fpath, rev_str); 464 if (rcs_lock_add(file, username, rev) != -1 && 465 !(rcsflags & QUIET)) 466 (void)fprintf(stderr, "%s locked\n", rev_str); 467 rcsnum_free(rev); 468 } 469 470 if (rcsflags & RCSPROG_UFLAG) { 471 RCSNUM *rev; 472 const char *username; 473 char rev_str[RCS_REV_BUFSZ]; 474 475 if (file->rf_head == NULL) { 476 warnx("%s contains no revisions", fpath); 477 rcs_close(file); 478 continue; 479 } 480 481 if ((username = getlogin()) == NULL) 482 err(1, "getlogin"); 483 if (urev == NULL) { 484 rev = rcsnum_alloc(); 485 rcsnum_cpy(file->rf_head, rev, 0); 486 } else if ((rev = rcsnum_parse(urev)) == NULL) { 487 warnx("unable to unlock file"); 488 rcs_close(file); 489 continue; 490 } 491 rcsnum_tostr(rev, rev_str, sizeof(rev_str)); 492 /* Make sure revision exists. */ 493 if (rcs_findrev(file, rev) == NULL) 494 errx(1, "%s: cannot unlock nonexisting " 495 "revision %s", fpath, rev_str); 496 if (rcs_lock_remove(file, username, rev) == -1 && 497 !(rcsflags & QUIET)) 498 warnx("%s: warning: No locks are set.", fpath); 499 else { 500 if (!(rcsflags & QUIET)) 501 (void)fprintf(stderr, 502 "%s unlocked\n", rev_str); 503 } 504 rcsnum_free(rev); 505 } 506 507 if (orange != NULL) { 508 struct rcs_delta *rdp, *nrdp; 509 char b[RCS_REV_BUFSZ]; 510 511 rcs_rev_select(file, orange); 512 for (rdp = TAILQ_FIRST(&(file->rf_delta)); 513 rdp != NULL; rdp = nrdp) { 514 nrdp = TAILQ_NEXT(rdp, rd_list); 515 516 /* 517 * Delete selected revisions. 518 */ 519 if (rdp->rd_flags & RCS_RD_SELECT) { 520 rcsnum_tostr(rdp->rd_num, b, sizeof(b)); 521 522 if (rdp->rd_locker != NULL) { 523 errx(1, "%s: can't remove " 524 "locked revision %s", 525 fpath, b); 526 continue; 527 } 528 529 if (!(rcsflags & QUIET)) { 530 (void)fprintf(stderr, "deleting" 531 " revision %s\n", b); 532 } 533 (void)rcs_rev_remove(file, rdp->rd_num); 534 } 535 } 536 } 537 538 rcs_write(file); 539 540 if (rcsflags & PRESERVETIME) 541 rcs_set_mtime(file, rcs_mtime); 542 543 rcs_close(file); 544 545 if (!(rcsflags & QUIET)) 546 (void)fprintf(stderr, "done\n"); 547 } 548 549 return (0); 550 } 551 552 static void 553 rcs_attach_symbol(RCSFILE *file, const char *symname) 554 { 555 char *rnum; 556 RCSNUM *rev; 557 char rbuf[RCS_REV_BUFSZ]; 558 int rm; 559 560 rm = 0; 561 rev = NULL; 562 if ((rnum = strrchr(symname, ':')) != NULL) { 563 if (rnum[1] == '\0') 564 rev = file->rf_head; 565 *(rnum++) = '\0'; 566 } else { 567 rm = 1; 568 } 569 570 if (rev == NULL && rm != 1) { 571 if ((rev = rcsnum_parse(rnum)) == NULL) 572 errx(1, "bad revision %s", rnum); 573 } 574 575 if (rcsflags & RCSPROG_NFLAG) 576 rm = 1; 577 578 if (rm == 1) { 579 if (rcs_sym_remove(file, symname) < 0) { 580 if (rcs_errno == RCS_ERR_NOENT && 581 !(rcsflags & RCSPROG_NFLAG)) 582 warnx("cannot delete nonexisting symbol %s", 583 symname); 584 } else { 585 if (rcsflags & RCSPROG_NFLAG) 586 rm = 0; 587 } 588 } 589 590 if (rm == 0) { 591 if (rcs_sym_add(file, symname, rev) < 0 && 592 rcs_errno == RCS_ERR_DUPENT) { 593 rcsnum_tostr(rcs_sym_getrev(file, symname), 594 rbuf, sizeof(rbuf)); 595 errx(1, "symbolic name %s already bound to %s", 596 symname, rbuf); 597 } 598 } 599 } 600