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