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