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