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