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