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