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