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