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