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