1 /* $OpenBSD: rcsprog.c,v 1.136 2007/01/10 18:04:25 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 "includes.h" 28 29 #include "rcsprog.h" 30 31 #define RCS_CMD_MAXARG 128 32 #define RCSPROG_OPTSTRING "A:a:b::c:e::ik:Ll::m:Mn:N:o:qt::TUu::Vx::z::" 33 34 const char rcs_version[] = "OpenRCS 4.1"; 35 36 int rcsflags; 37 int rcs_optind; 38 char *rcs_optarg; 39 char *rcs_suffixes; 40 char *rcs_tmpdir = RCS_TMPDIR_DEFAULT; 41 42 struct rcs_prog { 43 char *prog_name; 44 int (*prog_hdlr)(int, char **); 45 void (*prog_usage)(void); 46 } programs[] = { 47 { "rcs", rcs_main, rcs_usage }, 48 { "ci", checkin_main, checkin_usage }, 49 { "co", checkout_main, checkout_usage }, 50 { "rcsclean", rcsclean_main, rcsclean_usage }, 51 { "rcsdiff", rcsdiff_main, rcsdiff_usage }, 52 { "rcsmerge", rcsmerge_main, rcsmerge_usage }, 53 { "rlog", rlog_main, rlog_usage }, 54 { "ident", ident_main, ident_usage }, 55 { "merge", merge_main, merge_usage }, 56 }; 57 58 struct rcs_wklhead rcs_temp_files; 59 60 void sighdlr(int); 61 static void rcs_attach_symbol(RCSFILE *, const char *); 62 63 /* ARGSUSED */ 64 void 65 sighdlr(int sig) 66 { 67 rcs_worklist_clean(&rcs_temp_files, rcs_worklist_unlink); 68 _exit(1); 69 } 70 71 int 72 rcs_init(char *envstr, char **argv, int argvlen) 73 { 74 u_int i; 75 int argc, error; 76 char linebuf[256], *lp, *cp; 77 78 if (strlcpy(linebuf, envstr, sizeof(linebuf)) >= sizeof(linebuf)) 79 errx(1, "rcs_init: string truncation"); 80 (void)memset(argv, 0, argvlen * sizeof(char *)); 81 82 error = argc = 0; 83 for (lp = linebuf; lp != NULL;) { 84 cp = strsep(&lp, " \t\b\f\n\r\t\v"); 85 if (cp == NULL) 86 break; 87 else if (*cp == '\0') 88 continue; 89 90 if (argc == argvlen) { 91 error++; 92 break; 93 } 94 95 argv[argc] = xstrdup(cp); 96 argc++; 97 } 98 99 if (error != 0) { 100 for (i = 0; i < (u_int)argc; i++) 101 xfree(argv[i]); 102 argc = -1; 103 } 104 105 return (argc); 106 } 107 108 int 109 main(int argc, char **argv) 110 { 111 u_int i; 112 char *rcsinit, *cmd_argv[RCS_CMD_MAXARG]; 113 int ret, cmd_argc; 114 115 ret = -1; 116 rcs_optind = 1; 117 SLIST_INIT(&rcs_temp_files); 118 119 cmd_argc = 0; 120 cmd_argv[cmd_argc++] = argv[0]; 121 if ((rcsinit = getenv("RCSINIT")) != NULL) { 122 ret = rcs_init(rcsinit, cmd_argv + 1, 123 RCS_CMD_MAXARG - 1); 124 if (ret < 0) { 125 warnx("failed to prepend RCSINIT options"); 126 exit (1); 127 } 128 129 cmd_argc += ret; 130 } 131 132 if ((rcs_tmpdir = getenv("TMPDIR")) == NULL) 133 rcs_tmpdir = RCS_TMPDIR_DEFAULT; 134 135 for (ret = 1; ret < argc; ret++) 136 cmd_argv[cmd_argc++] = argv[ret]; 137 138 signal(SIGHUP, sighdlr); 139 signal(SIGINT, sighdlr); 140 signal(SIGQUIT, sighdlr); 141 signal(SIGABRT, sighdlr); 142 signal(SIGALRM, sighdlr); 143 signal(SIGTERM, sighdlr); 144 145 for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++) 146 if (strcmp(__progname, programs[i].prog_name) == 0) { 147 usage = programs[i].prog_usage; 148 ret = programs[i].prog_hdlr(cmd_argc, cmd_argv); 149 break; 150 } 151 152 /* clean up temporary files */ 153 rcs_worklist_run(&rcs_temp_files, rcs_worklist_unlink); 154 155 exit(ret); 156 } 157 158 159 void 160 rcs_usage(void) 161 { 162 fprintf(stderr, 163 "usage: rcs [-IiLqTUV] [-Aoldfile] [-ausers] [-b[rev]]\n" 164 " [-cstring] [-e[users]] [-kmode] [-l[rev]] [-mrev:msg]\n" 165 " [-orev] [-sstate[:rev]] [-tstr] [-u[rev]]\n" 166 " [-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 overriden 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 overriden 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[16]; 433 434 if ((username = getlogin()) == NULL) 435 err(1, "getlogin"); 436 if (lrev == NULL) { 437 rev = rcsnum_alloc(); 438 rcsnum_cpy(file->rf_head, rev, 0); 439 } else if ((rev = rcsnum_parse(lrev)) == NULL) { 440 warnx("unable to unlock file"); 441 rcs_close(file); 442 continue; 443 } 444 rcsnum_tostr(rev, rev_str, sizeof(rev_str)); 445 /* Make sure revision exists. */ 446 if (rcs_findrev(file, rev) == NULL) 447 errx(1, "%s: cannot lock nonexisting " 448 "revision %s", fpath, rev_str); 449 if (rcs_lock_add(file, username, rev) != -1 && 450 !(rcsflags & QUIET)) 451 (void)fprintf(stderr, "%s locked\n", rev_str); 452 rcsnum_free(rev); 453 } 454 455 if (rcsflags & RCSPROG_UFLAG) { 456 RCSNUM *rev; 457 const char *username; 458 char rev_str[16]; 459 460 if ((username = getlogin()) == NULL) 461 err(1, "getlogin"); 462 if (urev == NULL) { 463 rev = rcsnum_alloc(); 464 rcsnum_cpy(file->rf_head, rev, 0); 465 } else if ((rev = rcsnum_parse(urev)) == NULL) { 466 warnx("unable to unlock file"); 467 rcs_close(file); 468 continue; 469 } 470 rcsnum_tostr(rev, rev_str, sizeof(rev_str)); 471 /* Make sure revision exists. */ 472 if (rcs_findrev(file, rev) == NULL) 473 errx(1, "%s: cannot unlock nonexisting " 474 "revision %s", fpath, rev_str); 475 if (rcs_lock_remove(file, username, rev) == -1 && 476 !(rcsflags & QUIET)) 477 warnx("%s: warning: No locks are set.", fpath); 478 else { 479 if (!(rcsflags & QUIET)) 480 (void)fprintf(stderr, 481 "%s unlocked\n", rev_str); 482 } 483 rcsnum_free(rev); 484 } 485 486 if (orange != NULL) { 487 struct rcs_delta *rdp, *nrdp; 488 char b[16]; 489 490 rcs_rev_select(file, orange); 491 for (rdp = TAILQ_FIRST(&(file->rf_delta)); 492 rdp != NULL; rdp = nrdp) { 493 nrdp = TAILQ_NEXT(rdp, rd_list); 494 495 /* 496 * Delete selected revisions. 497 */ 498 if (rdp->rd_flags & RCS_RD_SELECT) { 499 rcsnum_tostr(rdp->rd_num, b, sizeof(b)); 500 if (!(rcsflags & QUIET)) { 501 (void)fprintf(stderr, "deleting" 502 " revision %s\n", b); 503 } 504 (void)rcs_rev_remove(file, rdp->rd_num); 505 } 506 } 507 } 508 509 rcs_write(file); 510 511 if (rcsflags & PRESERVETIME) 512 rcs_set_mtime(file, rcs_mtime); 513 514 rcs_close(file); 515 516 if (!(rcsflags & QUIET)) 517 (void)fprintf(stderr, "done\n"); 518 } 519 520 return (0); 521 } 522 523 static void 524 rcs_attach_symbol(RCSFILE *file, const char *symname) 525 { 526 char *rnum; 527 RCSNUM *rev; 528 char rbuf[16]; 529 int rm; 530 531 rm = 0; 532 rev = NULL; 533 if ((rnum = strrchr(symname, ':')) != NULL) { 534 if (rnum[1] == '\0') 535 rev = file->rf_head; 536 *(rnum++) = '\0'; 537 } else { 538 rm = 1; 539 } 540 541 if (rev == NULL && rm != 1) { 542 if ((rev = rcsnum_parse(rnum)) == NULL) 543 errx(1, "bad revision %s", rnum); 544 } 545 546 if (rcsflags & RCSPROG_NFLAG) 547 rm = 1; 548 549 if (rm == 1) { 550 if (rcs_sym_remove(file, symname) < 0) { 551 if (rcs_errno == RCS_ERR_NOENT && 552 !(rcsflags & RCSPROG_NFLAG)) 553 warnx("cannot delete nonexisting symbol %s", 554 symname); 555 } else { 556 if (rcsflags & RCSPROG_NFLAG) 557 rm = 0; 558 } 559 } 560 561 if (rm == 0) { 562 if (rcs_sym_add(file, symname, rev) < 0 && 563 rcs_errno == RCS_ERR_DUPENT) { 564 rcsnum_tostr(rcs_sym_getrev(file, symname), 565 rbuf, sizeof(rbuf)); 566 errx(1, "symbolic name %s already bound to %s", 567 symname, rbuf); 568 } 569 } 570 } 571