1 /* 2 * Copyright (c) 1980 Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1980 Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)sccs.c 5.9 (Berkeley) 06/01/90"; 16 #endif /* not lint */ 17 18 # include <stdio.h> 19 # include <sys/param.h> 20 # include <sys/stat.h> 21 # include <sys/dir.h> 22 # include <errno.h> 23 # include <signal.h> 24 # include <sysexits.h> 25 # include <pwd.h> 26 # include "pathnames.h" 27 28 /* 29 ** SCCS.C -- human-oriented front end to the SCCS system. 30 ** 31 ** Without trying to add any functionality to speak of, this 32 ** program tries to make SCCS a little more accessible to human 33 ** types. The main thing it does is automatically put the 34 ** string "SCCS/s." on the front of names. Also, it has a 35 ** couple of things that are designed to shorten frequent 36 ** combinations, e.g., "delget" which expands to a "delta" 37 ** and a "get". 38 ** 39 ** This program can also function as a setuid front end. 40 ** To do this, you should copy the source, renaming it to 41 ** whatever you want, e.g., "syssccs". Change any defaults 42 ** in the program (e.g., syssccs might default -d to 43 ** "/usr/src/sys"). Then recompile and put the result 44 ** as setuid to whomever you want. In this mode, sccs 45 ** knows to not run setuid for certain programs in order 46 ** to preserve security, and so forth. 47 ** 48 ** Usage: 49 ** sccs [flags] command [args] 50 ** 51 ** Flags: 52 ** -d<dir> <dir> represents a directory to search 53 ** out of. It should be a full pathname 54 ** for general usage. E.g., if <dir> is 55 ** "/usr/src/sys", then a reference to the 56 ** file "dev/bio.c" becomes a reference to 57 ** "/usr/src/sys/dev/bio.c". 58 ** -p<path> prepends <path> to the final component 59 ** of the pathname. By default, this is 60 ** "SCCS". For example, in the -d example 61 ** above, the path then gets modified to 62 ** "/usr/src/sys/dev/SCCS/s.bio.c". In 63 ** more common usage (without the -d flag), 64 ** "prog.c" would get modified to 65 ** "SCCS/s.prog.c". In both cases, the 66 ** "s." gets automatically prepended. 67 ** -r run as the real user. 68 ** 69 ** Commands: 70 ** admin, 71 ** get, 72 ** delta, 73 ** rmdel, 74 ** cdc, 75 ** etc. Straight out of SCCS; only difference 76 ** is that pathnames get modified as 77 ** described above. 78 ** enter Front end doing "sccs admin -i<name> <name>" 79 ** create Macro for "enter" followed by "get". 80 ** edit Macro for "get -e". 81 ** unedit Removes a file being edited, knowing 82 ** about p-files, etc. 83 ** delget Macro for "delta" followed by "get". 84 ** deledit Macro for "delta" followed by "get -e". 85 ** branch Macro for "get -b -e", followed by "delta 86 ** -s -n", followd by "get -e -t -g". 87 ** diffs "diff" the specified version of files 88 ** and the checked-out version. 89 ** print Macro for "prs -e" followed by "get -p -m". 90 ** tell List what files are being edited. 91 ** info Print information about files being edited. 92 ** clean Remove all files that can be 93 ** regenerated from SCCS files. 94 ** check Like info, but return exit status, for 95 ** use in makefiles. 96 ** fix Remove a top delta & reedit, but save 97 ** the previous changes in that delta. 98 ** 99 ** Compilation Flags: 100 ** UIDUSER -- determine who the user is by looking at the 101 ** uid rather than the login name -- for machines 102 ** where SCCS gets the user in this way. 103 ** SCCSDIR -- if defined, forces the -d flag to take on 104 ** this value. This is so that the setuid 105 ** aspects of this program cannot be abused. 106 ** This flag also disables the -p flag. 107 ** SCCSPATH -- the default for the -p flag. 108 ** MYNAME -- the title this program should print when it 109 ** gives error messages. 110 ** 111 ** Compilation Instructions: 112 ** cc -O -n -s sccs.c 113 ** The flags listed above can be -D defined to simplify 114 ** recompilation for variant versions. 115 ** 116 ** Author: 117 ** Eric Allman, UCB/INGRES 118 ** Copyright 1980 Regents of the University of California 119 */ 120 121 122 /******************* Configuration Information ********************/ 123 124 # ifndef SCCSPATH 125 # define SCCSPATH "SCCS" /* pathname in which to find s-files */ 126 # endif NOT SCCSPATH 127 128 # ifndef MYNAME 129 # define MYNAME "sccs" /* name used for printing errors */ 130 # endif NOT MYNAME 131 132 /**************** End of Configuration Information ****************/ 133 134 typedef char bool; 135 # define TRUE 1 136 # define FALSE 0 137 138 # define bitset(bit, word) ((bool) ((bit) & (word))) 139 140 struct sccsprog 141 { 142 char *sccsname; /* name of SCCS routine */ 143 short sccsoper; /* opcode, see below */ 144 short sccsflags; /* flags, see below */ 145 char *sccspath; /* pathname of binary implementing */ 146 }; 147 148 /* values for sccsoper */ 149 # define PROG 0 /* call a program */ 150 # define CMACRO 1 /* command substitution macro */ 151 # define FIX 2 /* fix a delta */ 152 # define CLEAN 3 /* clean out recreatable files */ 153 # define UNEDIT 4 /* unedit a file */ 154 # define SHELL 5 /* call a shell file (like PROG) */ 155 # define DIFFS 6 /* diff between sccs & file out */ 156 # define DODIFF 7 /* internal call to diff program */ 157 # define ENTER 8 /* enter new files */ 158 159 /* bits for sccsflags */ 160 # define NO_SDOT 0001 /* no s. on front of args */ 161 # define REALUSER 0002 /* protected (e.g., admin) */ 162 163 /* modes for the "clean", "info", "check" ops */ 164 # define CLEANC 0 /* clean command */ 165 # define INFOC 1 /* info command */ 166 # define CHECKC 2 /* check command */ 167 # define TELLC 3 /* give list of files being edited */ 168 169 /* 170 ** Description of commands known to this program. 171 ** First argument puts the command into a class. Second arg is 172 ** info regarding treatment of this command. Third arg is a 173 ** list of flags this command accepts from macros, etc. Fourth 174 ** arg is the pathname of the implementing program, or the 175 ** macro definition, or the arg to a sub-algorithm. 176 */ 177 178 struct sccsprog SccsProg[] = 179 { 180 "admin", PROG, REALUSER, PROGPATH(admin), 181 "cdc", PROG, 0, PROGPATH(rmdel), 182 "comb", PROG, 0, PROGPATH(comb), 183 "delta", PROG, 0, PROGPATH(delta), 184 "get", PROG, 0, PROGPATH(get), 185 "help", PROG, NO_SDOT, PROGPATH(help), 186 "prs", PROG, 0, PROGPATH(prs), 187 "prt", PROG, 0, PROGPATH(prt), 188 "rmdel", PROG, REALUSER, PROGPATH(rmdel), 189 "val", PROG, 0, PROGPATH(val), 190 "what", PROG, NO_SDOT, PROGPATH(what), 191 "sccsdiff", SHELL, REALUSER, PROGPATH(sccsdiff), 192 "edit", CMACRO, NO_SDOT, "get -e", 193 "delget", CMACRO, NO_SDOT, "delta:mysrp/get:ixbeskcl -t", 194 "deledit", CMACRO, NO_SDOT, "delta:mysrp -n/get:ixbskcl -e -t -g", 195 "fix", FIX, NO_SDOT, NULL, 196 "clean", CLEAN, REALUSER|NO_SDOT, (char *) CLEANC, 197 "info", CLEAN, REALUSER|NO_SDOT, (char *) INFOC, 198 "check", CLEAN, REALUSER|NO_SDOT, (char *) CHECKC, 199 "tell", CLEAN, REALUSER|NO_SDOT, (char *) TELLC, 200 "unedit", UNEDIT, NO_SDOT, NULL, 201 "diffs", DIFFS, NO_SDOT|REALUSER, NULL, 202 "-diff", DODIFF, NO_SDOT|REALUSER, PROGPATH(bdiff), 203 "print", CMACRO, 0, "prs -e/get -p -m -s", 204 "branch", CMACRO, NO_SDOT, 205 "get:ixrc -e -b/delta: -s -n -ybranch-place-holder/get:pl -e -t -g", 206 "enter", ENTER, NO_SDOT, NULL, 207 "create", CMACRO, NO_SDOT, "enter/get:ixbeskcl -t", 208 NULL, -1, 0, NULL 209 }; 210 211 /* one line from a p-file */ 212 struct pfile 213 { 214 char *p_osid; /* old SID */ 215 char *p_nsid; /* new SID */ 216 char *p_user; /* user who did edit */ 217 char *p_date; /* date of get */ 218 char *p_time; /* time of get */ 219 char *p_aux; /* extra info at end */ 220 }; 221 222 char *SccsPath = SCCSPATH; /* pathname of SCCS files */ 223 # ifdef SCCSDIR 224 char *SccsDir = SCCSDIR; /* directory to begin search from */ 225 # else 226 char *SccsDir = ""; 227 # endif 228 char MyName[] = MYNAME; /* name used in messages */ 229 int OutFile = -1; /* override output file for commands */ 230 bool RealUser; /* if set, running as real user */ 231 # ifdef DEBUG 232 bool Debug; /* turn on tracing */ 233 # endif 234 # ifndef V6 235 extern char *getenv(); 236 # endif V6 237 238 extern char *sys_siglist[]; 239 240 char *gstrcat(), *strcat(); 241 char *gstrncat(), *strncat(); 242 char *gstrcpy(), *strcpy(); 243 #define FBUFSIZ BUFSIZ 244 #define PFILELG 120 245 246 main(argc, argv) 247 int argc; 248 char **argv; 249 { 250 register char *p; 251 extern struct sccsprog *lookup(); 252 register int i; 253 # ifndef V6 254 # ifndef SCCSDIR 255 register struct passwd *pw; 256 extern struct passwd *getpwnam(); 257 char buf[FBUFSIZ]; 258 259 /* pull "SccsDir" out of the environment (possibly) */ 260 p = getenv("PROJECTDIR"); 261 if (p != NULL && p[0] != '\0') 262 { 263 if (p[0] == '/') 264 SccsDir = p; 265 else 266 { 267 pw = getpwnam(p); 268 if (pw == NULL) 269 { 270 usrerr("user %s does not exist", p); 271 exit(EX_USAGE); 272 } 273 gstrcpy(buf, pw->pw_dir, sizeof(buf)); 274 gstrcat(buf, "/src", sizeof(buf)); 275 if (access(buf, 0) < 0) 276 { 277 gstrcpy(buf, pw->pw_dir, sizeof(buf)); 278 gstrcat(buf, "/source", sizeof(buf)); 279 if (access(buf, 0) < 0) 280 { 281 usrerr("project %s has no source!", p); 282 exit(EX_USAGE); 283 } 284 } 285 SccsDir = buf; 286 } 287 } 288 # endif SCCSDIR 289 # endif V6 290 291 /* 292 ** Detect and decode flags intended for this program. 293 */ 294 295 if (argc < 2) 296 { 297 fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName); 298 exit(EX_USAGE); 299 } 300 argv[argc] = NULL; 301 302 if (lookup(argv[0]) == NULL) 303 { 304 while ((p = *++argv) != NULL) 305 { 306 if (*p != '-') 307 break; 308 switch (*++p) 309 { 310 case 'r': /* run as real user */ 311 setuid(getuid()); 312 RealUser++; 313 break; 314 315 # ifndef SCCSDIR 316 case 'p': /* path of sccs files */ 317 SccsPath = ++p; 318 if (SccsPath[0] == '\0' && argv[1] != NULL) 319 SccsPath = *++argv; 320 break; 321 322 case 'd': /* directory to search from */ 323 SccsDir = ++p; 324 if (SccsDir[0] == '\0' && argv[1] != NULL) 325 SccsDir = *++argv; 326 break; 327 # endif 328 329 # ifdef DEBUG 330 case 'T': /* trace */ 331 Debug++; 332 break; 333 # endif 334 335 default: 336 usrerr("unknown option -%s", p); 337 break; 338 } 339 } 340 if (SccsPath[0] == '\0') 341 SccsPath = "."; 342 } 343 344 i = command(argv, FALSE, ""); 345 exit(i); 346 } 347 348 /* 349 ** COMMAND -- look up and perform a command 350 ** 351 ** This routine is the guts of this program. Given an 352 ** argument vector, it looks up the "command" (argv[0]) 353 ** in the configuration table and does the necessary stuff. 354 ** 355 ** Parameters: 356 ** argv -- an argument vector to process. 357 ** forkflag -- if set, fork before executing the command. 358 ** editflag -- if set, only include flags listed in the 359 ** sccsklets field of the command descriptor. 360 ** arg0 -- a space-seperated list of arguments to insert 361 ** before argv. 362 ** 363 ** Returns: 364 ** zero -- command executed ok. 365 ** else -- error status. 366 ** 367 ** Side Effects: 368 ** none. 369 */ 370 371 command(argv, forkflag, arg0) 372 char **argv; 373 bool forkflag; 374 char *arg0; 375 { 376 register struct sccsprog *cmd; 377 register char *p; 378 char buf[FBUFSIZ]; 379 extern struct sccsprog *lookup(); 380 char *nav[1000]; 381 char **np; 382 register char **ap; 383 register int i; 384 register char *q; 385 extern bool unedit(); 386 int rval = 0; 387 extern char *index(); 388 extern char *makefile(); 389 char *editchs; 390 extern char *tail(); 391 392 # ifdef DEBUG 393 if (Debug) 394 { 395 printf("command:\n\t\"%s\"\n", arg0); 396 for (np = argv; *np != NULL; np++) 397 printf("\t\"%s\"\n", *np); 398 } 399 # endif 400 401 /* 402 ** Copy arguments. 403 ** Copy from arg0 & if necessary at most one arg 404 ** from argv[0]. 405 */ 406 407 np = ap = &nav[1]; 408 editchs = NULL; 409 for (p = arg0, q = buf; *p != '\0' && *p != '/'; ) 410 { 411 *np++ = q; 412 while (*p == ' ') 413 p++; 414 while (*p != ' ' && *p != '\0' && *p != '/' && *p != ':') 415 *q++ = *p++; 416 *q++ = '\0'; 417 if (*p == ':') 418 { 419 editchs = q; 420 while (*++p != '\0' && *p != '/' && *p != ' ') 421 *q++ = *p; 422 *q++ = '\0'; 423 } 424 } 425 *np = NULL; 426 if (*ap == NULL) 427 *np++ = *argv++; 428 429 /* 430 ** Look up command. 431 ** At this point, *ap is the command name. 432 */ 433 434 cmd = lookup(*ap); 435 if (cmd == NULL) 436 { 437 usrerr("Unknown command \"%s\"", *ap); 438 return (EX_USAGE); 439 } 440 441 /* 442 ** Copy remaining arguments doing editing as appropriate. 443 */ 444 445 for (; *argv != NULL; argv++) 446 { 447 p = *argv; 448 if (*p == '-') 449 { 450 if (p[1] == '\0' || editchs == NULL || index(editchs, p[1]) != NULL) 451 *np++ = p; 452 } 453 else 454 { 455 if (!bitset(NO_SDOT, cmd->sccsflags)) 456 p = makefile(p); 457 if (p != NULL) 458 *np++ = p; 459 } 460 } 461 *np = NULL; 462 463 /* 464 ** Interpret operation associated with this command. 465 */ 466 467 switch (cmd->sccsoper) 468 { 469 case SHELL: /* call a shell file */ 470 *ap = cmd->sccspath; 471 *--ap = "sh"; 472 rval = callprog(_PATH_BSHELL, cmd->sccsflags, ap, forkflag); 473 break; 474 475 case PROG: /* call an sccs prog */ 476 rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag); 477 break; 478 479 case CMACRO: /* command macro */ 480 /* step through & execute each part of the macro */ 481 for (p = cmd->sccspath; *p != '\0'; p++) 482 { 483 q = p; 484 while (*p != '\0' && *p != '/') 485 p++; 486 rval = command(&ap[1], *p != '\0', q); 487 if (rval != 0) 488 break; 489 } 490 break; 491 492 case FIX: /* fix a delta */ 493 if (ap[1]==0 || strncmp(ap[1], "-r", 2)!=0) 494 { 495 usrerr("-r flag needed for fix command"); 496 rval = EX_USAGE; 497 break; 498 } 499 500 /* get the version with all changes */ 501 rval = command(&ap[1], TRUE, "get -k"); 502 503 /* now remove that version from the s-file */ 504 if (rval == 0) 505 rval = command(&ap[1], TRUE, "rmdel:r"); 506 507 /* and edit the old version (but don't clobber new vers) */ 508 if (rval == 0) 509 rval = command(&ap[2], FALSE, "get -e -g"); 510 break; 511 512 case CLEAN: 513 rval = clean((int) cmd->sccspath, ap); 514 break; 515 516 case UNEDIT: 517 for (argv = np = &ap[1]; *argv != NULL; argv++) 518 { 519 if (unedit(*argv)) 520 *np++ = *argv; 521 } 522 *np = NULL; 523 524 /* get all the files that we unedited successfully */ 525 if (np > &ap[1]) 526 rval = command(&ap[1], FALSE, "get"); 527 break; 528 529 case DIFFS: /* diff between s-file & edit file */ 530 /* find the end of the flag arguments */ 531 for (np = &ap[1]; *np != NULL && **np == '-'; np++) 532 continue; 533 argv = np; 534 535 /* for each file, do the diff */ 536 p = argv[1]; 537 while (*np != NULL) 538 { 539 /* messy, but we need a null terminated argv */ 540 *argv = *np++; 541 argv[1] = NULL; 542 i = dodiff(ap, tail(*argv)); 543 if (rval == 0) 544 rval = i; 545 argv[1] = p; 546 } 547 break; 548 549 case DODIFF: /* internal diff call */ 550 setuid(getuid()); 551 for (np = ap; *np != NULL; np++) 552 { 553 if ((*np)[0] == '-' && (*np)[1] == 'C') 554 (*np)[1] = 'c'; 555 } 556 557 /* insert "-" argument */ 558 np[1] = NULL; 559 np[0] = np[-1]; 560 np[-1] = "-"; 561 562 /* execute the diff program of choice */ 563 # ifndef V6 564 execvp("diff", ap); 565 # endif 566 execv(cmd->sccspath, argv); 567 syserr("cannot exec %s", cmd->sccspath); 568 exit(EX_OSERR); 569 570 case ENTER: /* enter new sccs files */ 571 /* skip over flag arguments */ 572 for (np = &ap[1]; *np != NULL && **np == '-'; np++) 573 continue; 574 argv = np; 575 576 /* do an admin for each file */ 577 p = argv[1]; 578 while (*np != NULL) 579 { 580 printf("\n%s:\n", *np); 581 strcpy(buf, "-i"); 582 gstrcat(buf, *np, sizeof(buf)); 583 ap[0] = buf; 584 argv[0] = tail(*np); 585 argv[1] = NULL; 586 rval = command(ap, TRUE, "admin"); 587 argv[1] = p; 588 if (rval == 0) 589 { 590 strcpy(buf, ","); 591 gstrcat(buf, tail(*np), sizeof(buf)); 592 if (link(*np, buf) >= 0) 593 unlink(*np); 594 } 595 np++; 596 } 597 break; 598 599 default: 600 syserr("oper %d", cmd->sccsoper); 601 exit(EX_SOFTWARE); 602 } 603 # ifdef DEBUG 604 if (Debug) 605 printf("command: rval=%d\n", rval); 606 # endif 607 return (rval); 608 } 609 610 /* 611 ** LOOKUP -- look up an SCCS command name. 612 ** 613 ** Parameters: 614 ** name -- the name of the command to look up. 615 ** 616 ** Returns: 617 ** ptr to command descriptor for this command. 618 ** NULL if no such entry. 619 ** 620 ** Side Effects: 621 ** none. 622 */ 623 624 struct sccsprog * 625 lookup(name) 626 char *name; 627 { 628 register struct sccsprog *cmd; 629 630 for (cmd = SccsProg; cmd->sccsname != NULL; cmd++) 631 { 632 if (strcmp(cmd->sccsname, name) == 0) 633 return (cmd); 634 } 635 return (NULL); 636 } 637 638 /* 639 ** CALLPROG -- call a program 640 ** 641 ** Used to call the SCCS programs. 642 ** 643 ** Parameters: 644 ** progpath -- pathname of the program to call. 645 ** flags -- status flags from the command descriptors. 646 ** argv -- an argument vector to pass to the program. 647 ** forkflag -- if true, fork before calling, else just 648 ** exec. 649 ** 650 ** Returns: 651 ** The exit status of the program. 652 ** Nothing if forkflag == FALSE. 653 ** 654 ** Side Effects: 655 ** Can exit if forkflag == FALSE. 656 */ 657 658 callprog(progpath, flags, argv, forkflag) 659 char *progpath; 660 short flags; 661 char **argv; 662 bool forkflag; 663 { 664 register int i; 665 register int wpid; 666 auto int st; 667 register int sigcode; 668 register int coredumped; 669 register char *sigmsg; 670 auto char sigmsgbuf[10+1]; /* "Signal 127" + terminating '\0' */ 671 672 # ifdef DEBUG 673 if (Debug) 674 { 675 printf("callprog:\n"); 676 for (i = 0; argv[i] != NULL; i++) 677 printf("\t\"%s\"\n", argv[i]); 678 } 679 # endif 680 681 if (*argv == NULL) 682 return (-1); 683 684 /* 685 ** Fork if appropriate. 686 */ 687 688 if (forkflag) 689 { 690 # ifdef DEBUG 691 if (Debug) 692 printf("Forking\n"); 693 # endif 694 i = fork(); 695 if (i < 0) 696 { 697 syserr("cannot fork"); 698 exit(EX_OSERR); 699 } 700 else if (i > 0) 701 { 702 while ((wpid = wait(&st)) != -1 && wpid != i) 703 ; 704 if ((sigcode = st & 0377) == 0) 705 st = (st >> 8) & 0377; 706 else 707 { 708 coredumped = sigcode & 0200; 709 sigcode &= 0177; 710 if (sigcode != SIGINT && sigcode != SIGPIPE) 711 { 712 if (sigcode < NSIG) 713 sigmsg = sys_siglist[sigcode]; 714 else 715 { 716 sprintf(sigmsgbuf, "Signal %d", 717 sigcode); 718 sigmsg = sigmsgbuf; 719 } 720 fprintf(stderr, "sccs: %s: %s%s", argv[0], 721 sigmsg, 722 coredumped ? " - core dumped": ""); 723 } 724 st = EX_SOFTWARE; 725 } 726 if (OutFile >= 0) 727 { 728 close(OutFile); 729 OutFile = -1; 730 } 731 return (st); 732 } 733 } 734 else if (OutFile >= 0) 735 { 736 syserr("callprog: setting stdout w/o forking"); 737 exit(EX_SOFTWARE); 738 } 739 740 /* set protection as appropriate */ 741 if (bitset(REALUSER, flags)) 742 setuid(getuid()); 743 744 /* change standard input & output if needed */ 745 if (OutFile >= 0) 746 { 747 close(1); 748 dup(OutFile); 749 close(OutFile); 750 } 751 752 /* call real SCCS program */ 753 execv(progpath, argv); 754 syserr("cannot execute %s", progpath); 755 exit(EX_UNAVAILABLE); 756 /*NOTREACHED*/ 757 } 758 759 /* 760 ** MAKEFILE -- make filename of SCCS file 761 ** 762 ** If the name passed is already the name of an SCCS file, 763 ** just return it. Otherwise, munge the name into the name 764 ** of the actual SCCS file. 765 ** 766 ** There are cases when it is not clear what you want to 767 ** do. For example, if SccsPath is an absolute pathname 768 ** and the name given is also an absolute pathname, we go 769 ** for SccsPath (& only use the last component of the name 770 ** passed) -- this is important for security reasons (if 771 ** sccs is being used as a setuid front end), but not 772 ** particularly intuitive. 773 ** 774 ** Parameters: 775 ** name -- the file name to be munged. 776 ** 777 ** Returns: 778 ** The pathname of the sccs file. 779 ** NULL on error. 780 ** 781 ** Side Effects: 782 ** none. 783 */ 784 785 char * 786 makefile(name) 787 char *name; 788 { 789 register char *p; 790 char buf[3*FBUFSIZ]; 791 extern char *malloc(); 792 extern char *rindex(); 793 extern bool safepath(); 794 extern bool isdir(); 795 register char *q; 796 797 p = rindex(name, '/'); 798 if (p == NULL) 799 p = name; 800 else 801 p++; 802 803 /* 804 ** Check to see that the path is "safe", i.e., that we 805 ** are not letting some nasty person use the setuid part 806 ** of this program to look at or munge some presumably 807 ** hidden files. 808 */ 809 810 if (SccsDir[0] == '/' && !safepath(name)) 811 return (NULL); 812 813 /* 814 ** Create the base pathname. 815 */ 816 817 /* first the directory part */ 818 if (SccsDir[0] != '\0' && name[0] != '/' && strncmp(name, "./", 2) != 0) 819 { 820 gstrcpy(buf, SccsDir, sizeof(buf)); 821 gstrcat(buf, "/", sizeof(buf)); 822 } 823 else 824 gstrcpy(buf, "", sizeof(buf)); 825 826 /* then the head of the pathname */ 827 gstrncat(buf, name, p - name, sizeof(buf)); 828 q = &buf[strlen(buf)]; 829 830 /* now copy the final part of the name, in case useful */ 831 gstrcpy(q, p, sizeof(buf)); 832 833 /* so is it useful? */ 834 if (strncmp(p, "s.", 2) != 0 && !isdir(buf)) 835 { 836 /* sorry, no; copy the SCCS pathname & the "s." */ 837 gstrcpy(q, SccsPath, sizeof(buf)); 838 gstrcat(buf, "/s.", sizeof(buf)); 839 840 /* and now the end of the name */ 841 gstrcat(buf, p, sizeof(buf)); 842 } 843 844 /* if i haven't changed it, why did I do all this? */ 845 if (strcmp(buf, name) == 0) 846 p = name; 847 else 848 { 849 /* but if I have, squirrel it away */ 850 p = malloc(strlen(buf) + 1); 851 if (p == NULL) 852 { 853 perror("Sccs: no mem"); 854 exit(EX_OSERR); 855 } 856 strcpy(p, buf); 857 } 858 859 return (p); 860 } 861 862 /* 863 ** ISDIR -- return true if the argument is a directory. 864 ** 865 ** Parameters: 866 ** name -- the pathname of the file to check. 867 ** 868 ** Returns: 869 ** TRUE if 'name' is a directory, FALSE otherwise. 870 ** 871 ** Side Effects: 872 ** none. 873 */ 874 875 bool 876 isdir(name) 877 char *name; 878 { 879 struct stat stbuf; 880 881 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR); 882 } 883 884 /* 885 ** SAFEPATH -- determine whether a pathname is "safe" 886 ** 887 ** "Safe" pathnames only allow you to get deeper into the 888 ** directory structure, i.e., full pathnames and ".." are 889 ** not allowed. 890 ** 891 ** Parameters: 892 ** p -- the name to check. 893 ** 894 ** Returns: 895 ** TRUE -- if the path is safe. 896 ** FALSE -- if the path is not safe. 897 ** 898 ** Side Effects: 899 ** Prints a message if the path is not safe. 900 */ 901 902 bool 903 safepath(p) 904 register char *p; 905 { 906 extern char *index(); 907 908 if (*p != '/') 909 { 910 while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0) 911 { 912 p = index(p, '/'); 913 if (p == NULL) 914 return (TRUE); 915 p++; 916 } 917 } 918 919 printf("You may not use full pathnames or \"..\"\n"); 920 return (FALSE); 921 } 922 923 /* 924 ** CLEAN -- clean out recreatable files 925 ** 926 ** Any file for which an "s." file exists but no "p." file 927 ** exists in the current directory is purged. 928 ** 929 ** Parameters: 930 ** mode -- tells whether this came from a "clean", "info", or 931 ** "check" command. 932 ** argv -- the rest of the argument vector. 933 ** 934 ** Returns: 935 ** none. 936 ** 937 ** Side Effects: 938 ** Removes files in the current directory. 939 ** Prints information regarding files being edited. 940 ** Exits if a "check" command. 941 */ 942 943 clean(mode, argv) 944 int mode; 945 char **argv; 946 { 947 struct direct *dir; 948 char buf[FBUFSIZ]; 949 char *bufend; 950 register DIR *dirp; 951 register char *basefile; 952 bool gotedit; 953 bool gotpfent; 954 FILE *pfp; 955 bool nobranch = FALSE; 956 extern struct pfile *getpfent(); 957 register struct pfile *pf; 958 register char **ap; 959 extern char *username(); 960 char *usernm = NULL; 961 char *subdir = NULL; 962 char *cmdname; 963 964 /* 965 ** Process the argv 966 */ 967 968 cmdname = *argv; 969 for (ap = argv; *++ap != NULL; ) 970 { 971 if (**ap == '-') 972 { 973 /* we have a flag */ 974 switch ((*ap)[1]) 975 { 976 case 'b': 977 nobranch = TRUE; 978 break; 979 980 case 'u': 981 if ((*ap)[2] != '\0') 982 usernm = &(*ap)[2]; 983 else if (ap[1] != NULL && ap[1][0] != '-') 984 usernm = *++ap; 985 else 986 usernm = username(); 987 break; 988 } 989 } 990 else 991 { 992 if (subdir != NULL) 993 usrerr("too many args"); 994 else 995 subdir = *ap; 996 } 997 } 998 999 /* 1000 ** Find and open the SCCS directory. 1001 */ 1002 1003 gstrcpy(buf, SccsDir, sizeof(buf)); 1004 if (buf[0] != '\0') 1005 gstrcat(buf, "/", sizeof(buf)); 1006 if (subdir != NULL) 1007 { 1008 gstrcat(buf, subdir, sizeof(buf)); 1009 gstrcat(buf, "/", sizeof(buf)); 1010 } 1011 gstrcat(buf, SccsPath, sizeof(buf)); 1012 bufend = &buf[strlen(buf)]; 1013 1014 dirp = opendir(buf); 1015 if (dirp == NULL) 1016 { 1017 usrerr("cannot open %s", buf); 1018 return (EX_NOINPUT); 1019 } 1020 1021 /* 1022 ** Scan the SCCS directory looking for s. files. 1023 ** gotedit tells whether we have tried to clean any 1024 ** files that are being edited. 1025 */ 1026 1027 gotedit = FALSE; 1028 while (dir = readdir(dirp)) { 1029 if (strncmp(dir->d_name, "s.", 2) != 0) 1030 continue; 1031 1032 /* got an s. file -- see if the p. file exists */ 1033 gstrcpy(bufend, "/p.", sizeof(buf)); 1034 basefile = bufend + 3; 1035 gstrcpy(basefile, &dir->d_name[2], sizeof(buf)); 1036 1037 /* 1038 ** open and scan the p-file. 1039 ** 'gotpfent' tells if we have found a valid p-file 1040 ** entry. 1041 */ 1042 1043 pfp = fopen(buf, "r"); 1044 gotpfent = FALSE; 1045 if (pfp != NULL) 1046 { 1047 /* the file exists -- report it's contents */ 1048 while ((pf = getpfent(pfp)) != NULL) 1049 { 1050 if (nobranch && isbranch(pf->p_nsid)) 1051 continue; 1052 if (usernm != NULL && strcmp(usernm, pf->p_user) != 0 && mode != CLEANC) 1053 continue; 1054 gotedit = TRUE; 1055 gotpfent = TRUE; 1056 if (mode == TELLC) 1057 { 1058 printf("%s\n", basefile); 1059 break; 1060 } 1061 printf("%12s: being edited: ", basefile); 1062 putpfent(pf, stdout); 1063 } 1064 fclose(pfp); 1065 } 1066 1067 /* the s. file exists and no p. file exists -- unlink the g-file */ 1068 if (mode == CLEANC && !gotpfent) 1069 { 1070 char unlinkbuf[FBUFSIZ]; 1071 gstrcpy(unlinkbuf, &dir->d_name[2], sizeof(unlinkbuf)); 1072 unlink(unlinkbuf); 1073 } 1074 } 1075 1076 /* cleanup & report results */ 1077 closedir(dirp); 1078 if (!gotedit && mode == INFOC) 1079 { 1080 printf("Nothing being edited"); 1081 if (nobranch) 1082 printf(" (on trunk)"); 1083 if (usernm == NULL) 1084 printf("\n"); 1085 else 1086 printf(" by %s\n", usernm); 1087 } 1088 if (mode == CHECKC) 1089 exit(gotedit); 1090 return (EX_OK); 1091 } 1092 1093 /* 1094 ** ISBRANCH -- is the SID a branch? 1095 ** 1096 ** Parameters: 1097 ** sid -- the sid to check. 1098 ** 1099 ** Returns: 1100 ** TRUE if the sid represents a branch. 1101 ** FALSE otherwise. 1102 ** 1103 ** Side Effects: 1104 ** none. 1105 */ 1106 1107 isbranch(sid) 1108 char *sid; 1109 { 1110 register char *p; 1111 int dots; 1112 1113 dots = 0; 1114 for (p = sid; *p != '\0'; p++) 1115 { 1116 if (*p == '.') 1117 dots++; 1118 if (dots > 1) 1119 return (TRUE); 1120 } 1121 return (FALSE); 1122 } 1123 1124 /* 1125 ** UNEDIT -- unedit a file 1126 ** 1127 ** Checks to see that the current user is actually editting 1128 ** the file and arranges that s/he is not editting it. 1129 ** 1130 ** Parameters: 1131 ** fn -- the name of the file to be unedited. 1132 ** 1133 ** Returns: 1134 ** TRUE -- if the file was successfully unedited. 1135 ** FALSE -- if the file was not unedited for some 1136 ** reason. 1137 ** 1138 ** Side Effects: 1139 ** fn is removed 1140 ** entries are removed from pfile. 1141 */ 1142 1143 bool 1144 unedit(fn) 1145 char *fn; 1146 { 1147 register FILE *pfp; 1148 char *cp, *pfn; 1149 static char tfn[] = _PATH_TMP; 1150 FILE *tfp; 1151 register char *q; 1152 bool delete = FALSE; 1153 bool others = FALSE; 1154 char *myname; 1155 extern char *username(); 1156 struct pfile *pent; 1157 extern struct pfile *getpfent(); 1158 char buf[PFILELG]; 1159 extern char *makefile(), *rindex(), *tail(); 1160 1161 /* make "s." filename & find the trailing component */ 1162 pfn = makefile(fn); 1163 if (pfn == NULL) 1164 return (FALSE); 1165 q = rindex(pfn, '/'); 1166 if (q == NULL) 1167 q = &pfn[-1]; 1168 if (q[1] != 's' || q[2] != '.') 1169 { 1170 usrerr("bad file name \"%s\"", fn); 1171 return (FALSE); 1172 } 1173 1174 /* turn "s." into "p." & try to open it */ 1175 *++q = 'p'; 1176 1177 pfp = fopen(pfn, "r"); 1178 if (pfp == NULL) 1179 { 1180 printf("%12s: not being edited\n", fn); 1181 return (FALSE); 1182 } 1183 1184 /* create temp file for editing p-file */ 1185 mktemp(tfn); 1186 tfp = fopen(tfn, "w"); 1187 if (tfp == NULL) 1188 { 1189 usrerr("cannot create \"%s\"", tfn); 1190 exit(EX_OSERR); 1191 } 1192 1193 /* figure out who I am */ 1194 myname = username(); 1195 1196 /* 1197 ** Copy p-file to temp file, doing deletions as needed. 1198 */ 1199 1200 while ((pent = getpfent(pfp)) != NULL) 1201 { 1202 if (strcmp(pent->p_user, myname) == 0) 1203 { 1204 /* a match */ 1205 delete++; 1206 } 1207 else 1208 { 1209 /* output it again */ 1210 putpfent(pent, tfp); 1211 others++; 1212 } 1213 } 1214 1215 /* 1216 * Before changing anything, make sure we can remove 1217 * the file in question (assuming it exists). 1218 */ 1219 if (delete) { 1220 extern int errno; 1221 1222 cp = tail(fn); 1223 errno = 0; 1224 if (access(cp, 0) < 0 && errno != ENOENT) 1225 goto bad; 1226 if (errno == 0) 1227 /* 1228 * This is wrong, but the rest of the program 1229 * has built in assumptions about "." as well, 1230 * so why make unedit a special case? 1231 */ 1232 if (access(".", 2) < 0) { 1233 bad: 1234 printf("%12s: can't remove\n", cp); 1235 fclose(tfp); 1236 fclose(pfp); 1237 unlink(tfn); 1238 return (FALSE); 1239 } 1240 } 1241 /* do final cleanup */ 1242 if (others) 1243 { 1244 /* copy it back (perhaps it should be linked?) */ 1245 if (freopen(tfn, "r", tfp) == NULL) 1246 { 1247 syserr("cannot reopen \"%s\"", tfn); 1248 exit(EX_OSERR); 1249 } 1250 if (freopen(pfn, "w", pfp) == NULL) 1251 { 1252 usrerr("cannot create \"%s\"", pfn); 1253 return (FALSE); 1254 } 1255 while (fgets(buf, sizeof buf, tfp) != NULL) 1256 fputs(buf, pfp); 1257 } 1258 else 1259 { 1260 /* it's empty -- remove it */ 1261 unlink(pfn); 1262 } 1263 fclose(tfp); 1264 fclose(pfp); 1265 unlink(tfn); 1266 1267 /* actually remove the g-file */ 1268 if (delete) 1269 { 1270 /* 1271 * Since we've checked above, we can 1272 * use the return from unlink to 1273 * determine if the file existed or not. 1274 */ 1275 if (unlink(cp) >= 0) 1276 printf("%12s: removed\n", cp); 1277 return (TRUE); 1278 } 1279 else 1280 { 1281 printf("%12s: not being edited by you\n", fn); 1282 return (FALSE); 1283 } 1284 } 1285 1286 /* 1287 ** DODIFF -- diff an s-file against a g-file 1288 ** 1289 ** Parameters: 1290 ** getv -- argv for the 'get' command. 1291 ** gfile -- name of the g-file to diff against. 1292 ** 1293 ** Returns: 1294 ** Result of get. 1295 ** 1296 ** Side Effects: 1297 ** none. 1298 */ 1299 1300 dodiff(getv, gfile) 1301 char **getv; 1302 char *gfile; 1303 { 1304 int pipev[2]; 1305 int rval; 1306 register int i; 1307 register int pid; 1308 auto int st; 1309 extern int errno; 1310 sig_t osig; 1311 1312 printf("\n------- %s -------\n", gfile); 1313 fflush(stdout); 1314 1315 /* create context for diff to run in */ 1316 if (pipe(pipev) < 0) 1317 { 1318 syserr("dodiff: pipe failed"); 1319 exit(EX_OSERR); 1320 } 1321 if ((pid = fork()) < 0) 1322 { 1323 syserr("dodiff: fork failed"); 1324 exit(EX_OSERR); 1325 } 1326 else if (pid > 0) 1327 { 1328 /* in parent; run get */ 1329 OutFile = pipev[1]; 1330 close(pipev[0]); 1331 rval = command(&getv[1], TRUE, "get:rcixt -s -k -p"); 1332 osig = signal(SIGINT, SIG_IGN); 1333 while (((i = wait(&st)) >= 0 && i != pid) || errno == EINTR) 1334 errno = 0; 1335 signal(SIGINT, osig); 1336 /* ignore result of diff */ 1337 } 1338 else 1339 { 1340 /* in child, run diff */ 1341 if (close(pipev[1]) < 0 || close(0) < 0 || 1342 dup(pipev[0]) != 0 || close(pipev[0]) < 0) 1343 { 1344 syserr("dodiff: magic failed"); 1345 exit(EX_OSERR); 1346 } 1347 command(&getv[1], FALSE, "-diff:elsfhbC"); 1348 } 1349 return (rval); 1350 } 1351 1352 /* 1353 ** TAIL -- return tail of filename. 1354 ** 1355 ** Parameters: 1356 ** fn -- the filename. 1357 ** 1358 ** Returns: 1359 ** a pointer to the tail of the filename; e.g., given 1360 ** "cmd/ls.c", "ls.c" is returned. 1361 ** 1362 ** Side Effects: 1363 ** none. 1364 */ 1365 1366 char * 1367 tail(fn) 1368 register char *fn; 1369 { 1370 register char *p; 1371 1372 for (p = fn; *p != 0; p++) 1373 if (*p == '/' && p[1] != '\0' && p[1] != '/') 1374 fn = &p[1]; 1375 return (fn); 1376 } 1377 1378 /* 1379 ** GETPFENT -- get an entry from the p-file 1380 ** 1381 ** Parameters: 1382 ** pfp -- p-file file pointer 1383 ** 1384 ** Returns: 1385 ** pointer to p-file struct for next entry 1386 ** NULL on EOF or error 1387 ** 1388 ** Side Effects: 1389 ** Each call wipes out results of previous call. 1390 */ 1391 1392 struct pfile * 1393 getpfent(pfp) 1394 FILE *pfp; 1395 { 1396 static struct pfile ent; 1397 static char buf[PFILELG]; 1398 register char *p; 1399 extern char *nextfield(); 1400 1401 if (fgets(buf, sizeof buf, pfp) == NULL) 1402 return (NULL); 1403 1404 ent.p_osid = p = buf; 1405 ent.p_nsid = p = nextfield(p); 1406 ent.p_user = p = nextfield(p); 1407 ent.p_date = p = nextfield(p); 1408 ent.p_time = p = nextfield(p); 1409 ent.p_aux = p = nextfield(p); 1410 1411 return (&ent); 1412 } 1413 1414 1415 char * 1416 nextfield(p) 1417 register char *p; 1418 { 1419 if (p == NULL || *p == '\0') 1420 return (NULL); 1421 while (*p != ' ' && *p != '\n' && *p != '\0') 1422 p++; 1423 if (*p == '\n' || *p == '\0') 1424 { 1425 *p = '\0'; 1426 return (NULL); 1427 } 1428 *p++ = '\0'; 1429 return (p); 1430 } 1431 /* 1432 ** PUTPFENT -- output a p-file entry to a file 1433 ** 1434 ** Parameters: 1435 ** pf -- the p-file entry 1436 ** f -- the file to put it on. 1437 ** 1438 ** Returns: 1439 ** none. 1440 ** 1441 ** Side Effects: 1442 ** pf is written onto file f. 1443 */ 1444 1445 putpfent(pf, f) 1446 register struct pfile *pf; 1447 register FILE *f; 1448 { 1449 fprintf(f, "%s %s %s %s %s", pf->p_osid, pf->p_nsid, 1450 pf->p_user, pf->p_date, pf->p_time); 1451 if (pf->p_aux != NULL) 1452 fprintf(f, " %s", pf->p_aux); 1453 else 1454 fprintf(f, "\n"); 1455 } 1456 1457 /* 1458 ** USRERR -- issue user-level error 1459 ** 1460 ** Parameters: 1461 ** f -- format string. 1462 ** p1-p3 -- parameters to a printf. 1463 ** 1464 ** Returns: 1465 ** -1 1466 ** 1467 ** Side Effects: 1468 ** none. 1469 */ 1470 1471 /*VARARGS1*/ 1472 usrerr(f, p1, p2, p3) 1473 char *f; 1474 { 1475 fprintf(stderr, "\n%s: ", MyName); 1476 fprintf(stderr, f, p1, p2, p3); 1477 fprintf(stderr, "\n"); 1478 1479 return (-1); 1480 } 1481 1482 /* 1483 ** SYSERR -- print system-generated error. 1484 ** 1485 ** Parameters: 1486 ** f -- format string to a printf. 1487 ** p1, p2, p3 -- parameters to f. 1488 ** 1489 ** Returns: 1490 ** never. 1491 ** 1492 ** Side Effects: 1493 ** none. 1494 */ 1495 1496 /*VARARGS1*/ 1497 syserr(f, p1, p2, p3) 1498 char *f; 1499 { 1500 extern int errno; 1501 1502 fprintf(stderr, "\n%s SYSERR: ", MyName); 1503 fprintf(stderr, f, p1, p2, p3); 1504 fprintf(stderr, "\n"); 1505 if (errno == 0) 1506 exit(EX_SOFTWARE); 1507 else 1508 { 1509 perror(NULL); 1510 exit(EX_OSERR); 1511 } 1512 } 1513 /* 1514 ** USERNAME -- return name of the current user 1515 ** 1516 ** Parameters: 1517 ** none 1518 ** 1519 ** Returns: 1520 ** name of current user 1521 ** 1522 ** Side Effects: 1523 ** none 1524 */ 1525 1526 char * 1527 username() 1528 { 1529 # ifdef UIDUSER 1530 extern struct passwd *getpwuid(); 1531 register struct passwd *pw; 1532 1533 pw = getpwuid(getuid()); 1534 if (pw == NULL) 1535 { 1536 syserr("who are you? (uid=%d)", getuid()); 1537 exit(EX_OSERR); 1538 } 1539 return (pw->pw_name); 1540 # else 1541 extern char *getlogin(); 1542 register char *p; 1543 1544 p = getenv("USER"); 1545 if (p == NULL || p[0] == '\0') 1546 p = getlogin(); 1547 return (p); 1548 # endif UIDUSER 1549 } 1550 1551 /* 1552 ** Guarded string manipulation routines; the last argument 1553 ** is the length of the buffer into which the strcpy or strcat 1554 ** is to be done. 1555 */ 1556 char *gstrcat(to, from, length) 1557 char *to, *from; 1558 int length; 1559 { 1560 if (strlen(from) + strlen(to) >= length) { 1561 gstrbotch(to, from); 1562 } 1563 return(strcat(to, from)); 1564 } 1565 1566 char *gstrncat(to, from, n, length) 1567 char *to, *from; 1568 int n; 1569 int length; 1570 { 1571 if (n + strlen(to) >= length) { 1572 gstrbotch(to, from); 1573 } 1574 return(strncat(to, from, n)); 1575 } 1576 1577 char *gstrcpy(to, from, length) 1578 char *to, *from; 1579 int length; 1580 { 1581 if (strlen(from) >= length) { 1582 gstrbotch(from, (char *)0); 1583 } 1584 return(strcpy(to, from)); 1585 } 1586 gstrbotch(str1, str2) 1587 char *str1, *str2; 1588 { 1589 usrerr("Filename(s) too long: %s %s", str1, str2); 1590 } 1591