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