1 /* $OpenBSD: rcsprog.c,v 1.160 2015/12/29 19:12:56 gsoares 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 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 flags |= RCS_CREATE; 227 break; 228 case 'k': 229 kflag = rcs_kflag_get(rcs_optarg); 230 if (RCS_KWEXP_INVAL(kflag)) { 231 warnx("invalid RCS keyword substitution mode"); 232 (usage)(); 233 } 234 break; 235 case 'L': 236 if (lkmode == RCS_LOCK_LOOSE) 237 warnx("-U overridden by -L"); 238 lkmode = RCS_LOCK_STRICT; 239 break; 240 case 'l': 241 if (rcsflags & RCSPROG_UFLAG) 242 warnx("-u overridden by -l"); 243 lrev = rcs_optarg; 244 rcsflags &= ~RCSPROG_UFLAG; 245 rcsflags |= RCSPROG_LFLAG; 246 break; 247 case 'm': 248 free(logstr); 249 logstr = xstrdup(rcs_optarg); 250 break; 251 case 'M': 252 /* ignore for the moment */ 253 break; 254 case 'n': 255 nflag = rcs_optarg; 256 break; 257 case 'N': 258 nflag = rcs_optarg; 259 rcsflags |= RCSPROG_NFLAG; 260 break; 261 case 'o': 262 orange = rcs_optarg; 263 break; 264 case 'q': 265 rcsflags |= QUIET; 266 break; 267 case 't': 268 descfile = rcs_optarg; 269 rcsflags |= DESCRIPTION; 270 break; 271 case 'T': 272 rcsflags |= PRESERVETIME; 273 break; 274 case 'U': 275 if (lkmode == RCS_LOCK_STRICT) 276 warnx("-L overridden by -U"); 277 lkmode = RCS_LOCK_LOOSE; 278 break; 279 case 'u': 280 if (rcsflags & RCSPROG_LFLAG) 281 warnx("-l overridden by -u"); 282 urev = rcs_optarg; 283 rcsflags &= ~RCSPROG_LFLAG; 284 rcsflags |= RCSPROG_UFLAG; 285 break; 286 case 'V': 287 printf("%s\n", rcs_version); 288 exit(0); 289 case 'x': 290 /* Use blank extension if none given. */ 291 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 292 break; 293 case 'z': 294 /* 295 * kept for compatibility 296 */ 297 break; 298 default: 299 (usage)(); 300 } 301 } 302 303 argc -= rcs_optind; 304 argv += rcs_optind; 305 306 if (argc == 0) { 307 warnx("no input file"); 308 (usage)(); 309 } 310 311 for (i = 0; i < argc; i++) { 312 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 313 if (fd < 0 && !(flags & RCS_CREATE)) { 314 warn("%s", fpath); 315 continue; 316 } 317 318 if (!(rcsflags & QUIET)) 319 (void)fprintf(stderr, "RCS file: %s\n", fpath); 320 321 if ((file = rcs_open(fpath, fd, flags, fmode)) == NULL) { 322 close(fd); 323 continue; 324 } 325 326 if (rcsflags & DESCRIPTION) { 327 if (rcs_set_description(file, descfile) == -1) { 328 warn("%s", descfile); 329 rcs_close(file); 330 continue; 331 } 332 } 333 else if (flags & RCS_CREATE) { 334 if (rcs_set_description(file, NULL) == -1) { 335 warn("stdin"); 336 rcs_close(file); 337 continue; 338 } 339 } 340 341 if (rcsflags & PRESERVETIME) 342 rcs_mtime = rcs_get_mtime(file); 343 344 if (nflag != NULL) 345 rcs_attach_symbol(file, nflag); 346 347 if (logstr != NULL) { 348 if ((logmsg = strchr(logstr, ':')) == NULL) { 349 warnx("missing log message"); 350 rcs_close(file); 351 continue; 352 } 353 354 *logmsg++ = '\0'; 355 if ((logrev = rcsnum_parse(logstr)) == NULL) { 356 warnx("`%s' bad revision number", logstr); 357 rcs_close(file); 358 continue; 359 } 360 361 if (rcs_rev_setlog(file, logrev, logmsg) < 0) { 362 warnx("failed to set logmsg for `%s' to `%s'", 363 logstr, logmsg); 364 rcs_close(file); 365 rcsnum_free(logrev); 366 continue; 367 } 368 369 rcsnum_free(logrev); 370 } 371 372 /* entries to add from <oldfile> */ 373 if (rcsflags & CO_ACLAPPEND) { 374 RCSFILE *oldfile; 375 int ofd; 376 char ofpath[PATH_MAX]; 377 378 ofd = rcs_choosefile(oldfilename, ofpath, sizeof(ofpath)); 379 if (ofd < 0) { 380 if (!(flags & RCS_CREATE)) 381 warn("%s", ofpath); 382 exit(1); 383 } 384 if ((oldfile = rcs_open(ofpath, ofd, RCS_READ)) == NULL) 385 exit(1); 386 387 TAILQ_FOREACH(acp, &(oldfile->rf_access), ra_list) 388 rcs_access_add(file, acp->ra_name); 389 390 rcs_close(oldfile); 391 (void)close(ofd); 392 } 393 394 /* entries to add to the access list */ 395 if (alist != NULL) { 396 struct rcs_argvector *aargv; 397 398 aargv = rcs_strsplit(alist, ","); 399 for (j = 0; aargv->argv[j] != NULL; j++) 400 rcs_access_add(file, aargv->argv[j]); 401 402 rcs_argv_destroy(aargv); 403 } 404 405 if (comment != NULL) 406 rcs_comment_set(file, comment); 407 408 if (elist != NULL) { 409 struct rcs_argvector *eargv; 410 411 eargv = rcs_strsplit(elist, ","); 412 for (j = 0; eargv->argv[j] != NULL; j++) 413 rcs_access_remove(file, eargv->argv[j]); 414 415 rcs_argv_destroy(eargv); 416 } else if (rcsflags & RCSPROG_EFLAG) { 417 struct rcs_access *rap; 418 419 /* XXX rcs_access_remove(file, NULL); ?? */ 420 while (!TAILQ_EMPTY(&(file->rf_access))) { 421 rap = TAILQ_FIRST(&(file->rf_access)); 422 TAILQ_REMOVE(&(file->rf_access), rap, ra_list); 423 free(rap->ra_name); 424 free(rap); 425 } 426 /* not synced anymore */ 427 file->rf_flags &= ~RCS_SYNCED; 428 } 429 430 rcs_kwexp_set(file, kflag); 431 432 if (lkmode != RCS_LOCK_INVAL) 433 (void)rcs_lock_setmode(file, lkmode); 434 435 if (rcsflags & RCSPROG_LFLAG) { 436 RCSNUM *rev; 437 const char *username; 438 char rev_str[RCS_REV_BUFSZ]; 439 440 if (file->rf_head == NULL) { 441 warnx("%s contains no revisions", fpath); 442 rcs_close(file); 443 continue; 444 } 445 446 if ((username = getlogin()) == NULL) 447 err(1, "getlogin"); 448 if (lrev == NULL) { 449 rev = rcsnum_alloc(); 450 rcsnum_cpy(file->rf_head, rev, 0); 451 } else if ((rev = rcsnum_parse(lrev)) == NULL) { 452 warnx("unable to unlock file"); 453 rcs_close(file); 454 continue; 455 } 456 rcsnum_tostr(rev, rev_str, sizeof(rev_str)); 457 /* Make sure revision exists. */ 458 if (rcs_findrev(file, rev) == NULL) 459 errx(1, "%s: cannot lock nonexisting " 460 "revision %s", fpath, rev_str); 461 if (rcs_lock_add(file, username, rev) != -1 && 462 !(rcsflags & QUIET)) 463 (void)fprintf(stderr, "%s locked\n", rev_str); 464 rcsnum_free(rev); 465 } 466 467 if (rcsflags & RCSPROG_UFLAG) { 468 RCSNUM *rev; 469 const char *username; 470 char rev_str[RCS_REV_BUFSZ]; 471 472 if (file->rf_head == NULL) { 473 warnx("%s contains no revisions", fpath); 474 rcs_close(file); 475 continue; 476 } 477 478 if ((username = getlogin()) == NULL) 479 err(1, "getlogin"); 480 if (urev == NULL) { 481 rev = rcsnum_alloc(); 482 rcsnum_cpy(file->rf_head, rev, 0); 483 } else if ((rev = rcsnum_parse(urev)) == NULL) { 484 warnx("unable to unlock file"); 485 rcs_close(file); 486 continue; 487 } 488 rcsnum_tostr(rev, rev_str, sizeof(rev_str)); 489 /* Make sure revision exists. */ 490 if (rcs_findrev(file, rev) == NULL) 491 errx(1, "%s: cannot unlock nonexisting " 492 "revision %s", fpath, rev_str); 493 if (rcs_lock_remove(file, username, rev) == -1 && 494 !(rcsflags & QUIET)) 495 warnx("%s: warning: No locks are set.", fpath); 496 else { 497 if (!(rcsflags & QUIET)) 498 (void)fprintf(stderr, 499 "%s unlocked\n", rev_str); 500 } 501 rcsnum_free(rev); 502 } 503 504 if (orange != NULL) { 505 struct rcs_delta *rdp, *nrdp; 506 char b[RCS_REV_BUFSZ]; 507 508 rcs_rev_select(file, orange); 509 for (rdp = TAILQ_FIRST(&(file->rf_delta)); 510 rdp != NULL; rdp = nrdp) { 511 nrdp = TAILQ_NEXT(rdp, rd_list); 512 513 /* 514 * Delete selected revisions. 515 */ 516 if (rdp->rd_flags & RCS_RD_SELECT) { 517 rcsnum_tostr(rdp->rd_num, b, sizeof(b)); 518 if (!(rcsflags & QUIET)) { 519 (void)fprintf(stderr, "deleting" 520 " revision %s\n", b); 521 } 522 (void)rcs_rev_remove(file, rdp->rd_num); 523 } 524 } 525 } 526 527 rcs_write(file); 528 529 if (rcsflags & PRESERVETIME) 530 rcs_set_mtime(file, rcs_mtime); 531 532 rcs_close(file); 533 534 if (!(rcsflags & QUIET)) 535 (void)fprintf(stderr, "done\n"); 536 } 537 538 return (0); 539 } 540 541 static void 542 rcs_attach_symbol(RCSFILE *file, const char *symname) 543 { 544 char *rnum; 545 RCSNUM *rev; 546 char rbuf[RCS_REV_BUFSZ]; 547 int rm; 548 549 rm = 0; 550 rev = NULL; 551 if ((rnum = strrchr(symname, ':')) != NULL) { 552 if (rnum[1] == '\0') 553 rev = file->rf_head; 554 *(rnum++) = '\0'; 555 } else { 556 rm = 1; 557 } 558 559 if (rev == NULL && rm != 1) { 560 if ((rev = rcsnum_parse(rnum)) == NULL) 561 errx(1, "bad revision %s", rnum); 562 } 563 564 if (rcsflags & RCSPROG_NFLAG) 565 rm = 1; 566 567 if (rm == 1) { 568 if (rcs_sym_remove(file, symname) < 0) { 569 if (rcs_errno == RCS_ERR_NOENT && 570 !(rcsflags & RCSPROG_NFLAG)) 571 warnx("cannot delete nonexisting symbol %s", 572 symname); 573 } else { 574 if (rcsflags & RCSPROG_NFLAG) 575 rm = 0; 576 } 577 } 578 579 if (rm == 0) { 580 if (rcs_sym_add(file, symname, rev) < 0 && 581 rcs_errno == RCS_ERR_DUPENT) { 582 rcsnum_tostr(rcs_sym_getrev(file, symname), 583 rbuf, sizeof(rbuf)); 584 errx(1, "symbolic name %s already bound to %s", 585 symname, rbuf); 586 } 587 } 588 } 589