1148Seric # include <stdio.h> 2148Seric # include <sys/types.h> 3148Seric # include <sys/stat.h> 4261Seric # include <sys/dir.h> 51433Seric # include <errno.h> 61433Seric # include <signal.h> 7148Seric # include <sysexits.h> 8202Seric # include <whoami.h> 9148Seric 10828Seric /* 11828Seric ** SCCS.C -- human-oriented front end to the SCCS system. 12828Seric ** 13828Seric ** Without trying to add any functionality to speak of, this 14828Seric ** program tries to make SCCS a little more accessible to human 15828Seric ** types. The main thing it does is automatically put the 16828Seric ** string "SCCS/s." on the front of names. Also, it has a 17828Seric ** couple of things that are designed to shorten frequent 18828Seric ** combinations, e.g., "delget" which expands to a "delta" 19828Seric ** and a "get". 20828Seric ** 21828Seric ** This program can also function as a setuid front end. 22828Seric ** To do this, you should copy the source, renaming it to 23828Seric ** whatever you want, e.g., "syssccs". Change any defaults 24828Seric ** in the program (e.g., syssccs might default -d to 25828Seric ** "/usr/src/sys"). Then recompile and put the result 26828Seric ** as setuid to whomever you want. In this mode, sccs 27828Seric ** knows to not run setuid for certain programs in order 28828Seric ** to preserve security, and so forth. 29828Seric ** 30828Seric ** Usage: 31828Seric ** sccs [flags] command [args] 32828Seric ** 33828Seric ** Flags: 34828Seric ** -d<dir> <dir> represents a directory to search 35828Seric ** out of. It should be a full pathname 36828Seric ** for general usage. E.g., if <dir> is 37828Seric ** "/usr/src/sys", then a reference to the 38828Seric ** file "dev/bio.c" becomes a reference to 39828Seric ** "/usr/src/sys/dev/bio.c". 40828Seric ** -p<path> prepends <path> to the final component 41828Seric ** of the pathname. By default, this is 42828Seric ** "SCCS". For example, in the -d example 43828Seric ** above, the path then gets modified to 44828Seric ** "/usr/src/sys/dev/SCCS/s.bio.c". In 45828Seric ** more common usage (without the -d flag), 46828Seric ** "prog.c" would get modified to 47828Seric ** "SCCS/s.prog.c". In both cases, the 48828Seric ** "s." gets automatically prepended. 49828Seric ** -r run as the real user. 50828Seric ** 51828Seric ** Commands: 52828Seric ** admin, 53828Seric ** get, 54828Seric ** delta, 55828Seric ** rmdel, 56828Seric ** chghist, 57828Seric ** etc. Straight out of SCCS; only difference 58828Seric ** is that pathnames get modified as 59828Seric ** described above. 60828Seric ** edit Macro for "get -e". 61828Seric ** unedit Removes a file being edited, knowing 62828Seric ** about p-files, etc. 63828Seric ** delget Macro for "delta" followed by "get". 64828Seric ** deledit Macro for "delta" followed by "get -e". 65828Seric ** info Tell what files being edited. 66828Seric ** clean Remove all files that can be 67828Seric ** regenerated from SCCS files. 681205Seric ** check Like info, but return exit status, for 69828Seric ** use in makefiles. 70828Seric ** fix Remove a top delta & reedit, but save 71828Seric ** the previous changes in that delta. 72828Seric ** 73828Seric ** Compilation Flags: 74828Seric ** UIDUSER -- determine who the user is by looking at the 75828Seric ** uid rather than the login name -- for machines 76828Seric ** where SCCS gets the user in this way. 771270Seric ** SCCSDIR -- if defined, forces the -d flag to take on 781205Seric ** this value. This is so that the setuid 791205Seric ** aspects of this program cannot be abused. 801270Seric ** This flag also disables the -p flag. 811270Seric ** SCCSPATH -- the default for the -p flag. 821437Seric ** MYNAME -- the title this program should print when it 831437Seric ** gives error messages. 84828Seric ** 85828Seric ** Compilation Instructions: 86828Seric ** cc -O -n -s sccs.c 871437Seric ** The flags listed above can be -D defined to simplify 881437Seric ** recompilation for variant versions. 89828Seric ** 90828Seric ** Author: 91828Seric ** Eric Allman, UCB/INGRES 921270Seric ** Copyright 1980 Regents of the University of California 93828Seric */ 94155Seric 95*1502Seric static char SccsId[] = "@(#)sccs.c 1.41 10/17/80"; 961432Seric 971270Seric /******************* Configuration Information ********************/ 981270Seric 991437Seric /* special defines for local berkeley systems */ 1001437Seric # include <whoami.h> 1011437Seric 102828Seric # ifdef CSVAX 103828Seric # define UIDUSER 1041270Seric # define PROGPATH(name) "/usr/local/name" 1051270Seric # endif CSVAX 106828Seric 1071437Seric /* end of berkeley systems defines */ 1081437Seric 1091437Seric # ifndef SCCSPATH 1101432Seric # define SCCSPATH "SCCS" /* pathname in which to find s-files */ 1111437Seric # endif NOT SCCSPATH 112828Seric 1131437Seric # ifndef MYNAME 1141437Seric # define MYNAME "sccs" /* name used for printing errors */ 1151437Seric # endif NOT MYNAME 1161270Seric 1171432Seric # ifndef PROGPATH 1181432Seric # define PROGPATH(name) "/usr/sccs/name" /* place to find binaries */ 1191432Seric # endif PROGPATH 1201432Seric 1211270Seric /**************** End of Configuration Information ****************/ 1221432Seric 123157Seric typedef char bool; 124200Seric # define TRUE 1 125200Seric # define FALSE 0 126157Seric 1271438Seric # define bitset(bit, word) ((bool) ((bit) & (word))) 1281438Seric 129828Seric # ifdef UIDUSER 130828Seric # include <pwd.h> 131828Seric # endif UIDUSER 132828Seric 133148Seric struct sccsprog 134148Seric { 135148Seric char *sccsname; /* name of SCCS routine */ 136200Seric short sccsoper; /* opcode, see below */ 137200Seric short sccsflags; /* flags, see below */ 1381316Seric char *sccsklets; /* valid key-letters on macros */ 139148Seric char *sccspath; /* pathname of binary implementing */ 140148Seric }; 141148Seric 142200Seric /* values for sccsoper */ 143200Seric # define PROG 0 /* call a program */ 144201Seric # define CMACRO 1 /* command substitution macro */ 145226Seric # define FIX 2 /* fix a delta */ 146261Seric # define CLEAN 3 /* clean out recreatable files */ 147396Seric # define UNEDIT 4 /* unedit a file */ 1481431Seric # define SHELL 5 /* call a shell file (like PROG) */ 1491433Seric # define DIFFS 6 /* diff between sccs & file out */ 150200Seric 151157Seric /* bits for sccsflags */ 152200Seric # define NO_SDOT 0001 /* no s. on front of args */ 153200Seric # define REALUSER 0002 /* protected (e.g., admin) */ 154148Seric 155819Seric /* modes for the "clean", "info", "check" ops */ 156819Seric # define CLEANC 0 /* clean command */ 157819Seric # define INFOC 1 /* info command */ 158819Seric # define CHECKC 2 /* check command */ 159819Seric 1601432Seric /* 1611432Seric ** Description of commands known to this program. 1621432Seric ** First argument puts the command into a class. Second arg is 1631432Seric ** info regarding treatment of this command. Third arg is a 1641432Seric ** list of flags this command accepts from macros, etc. Fourth 1651432Seric ** arg is the pathname of the implementing program, or the 1661432Seric ** macro definition, or the arg to a sub-algorithm. 1671432Seric */ 168202Seric 169148Seric struct sccsprog SccsProg[] = 170148Seric { 1711316Seric "admin", PROG, REALUSER, "", PROGPATH(admin), 1721316Seric "chghist", PROG, 0, "", PROGPATH(rmdel), 1731316Seric "comb", PROG, 0, "", PROGPATH(comb), 1741316Seric "delta", PROG, 0, "mysrp", PROGPATH(delta), 1751317Seric "get", PROG, 0, "ixbeskcl", PROGPATH(get), 1761316Seric "help", PROG, NO_SDOT, "", PROGPATH(help), 1771316Seric "prt", PROG, 0, "", PROGPATH(prt), 1781316Seric "rmdel", PROG, REALUSER, "", PROGPATH(rmdel), 1791316Seric "what", PROG, NO_SDOT, "", PROGPATH(what), 1801431Seric "sccsdiff", SHELL, REALUSER, "", PROGPATH(sccsdiff), 1811317Seric "edit", CMACRO, NO_SDOT, "ixbscl", "get -e", 1821316Seric "delget", CMACRO, NO_SDOT, "", "delta/get -t", 1831316Seric "deledit", CMACRO, NO_SDOT, "", "delta/get -e -t", 1841316Seric "fix", FIX, NO_SDOT, "", NULL, 1851316Seric "clean", CLEAN, REALUSER, "", (char *) CLEANC, 1861316Seric "info", CLEAN, REALUSER, "", (char *) INFOC, 1871316Seric "check", CLEAN, REALUSER, "", (char *) CHECKC, 1881316Seric "unedit", UNEDIT, NO_SDOT, "", NULL, 1891433Seric "diffs", DIFFS, NO_SDOT|REALUSER, "", NULL, 1901316Seric NULL, -1, 0, "", NULL 191148Seric }; 192148Seric 1931432Seric /* one line from a p-file */ 194396Seric struct pfile 195396Seric { 196396Seric char *p_osid; /* old SID */ 197396Seric char *p_nsid; /* new SID */ 198396Seric char *p_user; /* user who did edit */ 199396Seric char *p_date; /* date of get */ 200396Seric char *p_time; /* time of get */ 201396Seric }; 202396Seric 2031270Seric char *SccsPath = SCCSPATH; /* pathname of SCCS files */ 2041270Seric # ifdef SCCSDIR 2051270Seric char *SccsDir = SCCSDIR; /* directory to begin search from */ 2061205Seric # else 2071270Seric char *SccsDir = ""; 2081205Seric # endif 2091437Seric char MyName[] = MYNAME; /* name used in messages */ 2101433Seric int OutFile = -1; /* override output file for commands */ 211157Seric bool RealUser; /* if set, running as real user */ 212393Seric # ifdef DEBUG 213393Seric bool Debug; /* turn on tracing */ 214393Seric # endif 2151432Seric 216148Seric main(argc, argv) 217148Seric int argc; 218148Seric char **argv; 219148Seric { 220148Seric register char *p; 221262Seric extern struct sccsprog *lookup(); 2221282Seric register int i; 223148Seric 224148Seric /* 225148Seric ** Detect and decode flags intended for this program. 226148Seric */ 227148Seric 228200Seric if (argc < 2) 229148Seric { 2301205Seric fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName); 231200Seric exit(EX_USAGE); 232200Seric } 233200Seric argv[argc] = NULL; 234200Seric 235262Seric if (lookup(argv[0]) == NULL) 236200Seric { 237262Seric while ((p = *++argv) != NULL) 238148Seric { 239262Seric if (*p != '-') 240262Seric break; 241262Seric switch (*++p) 242262Seric { 243262Seric case 'r': /* run as real user */ 244262Seric setuid(getuid()); 245262Seric RealUser++; 246262Seric break; 247148Seric 2481270Seric # ifndef SCCSDIR 249262Seric case 'p': /* path of sccs files */ 250262Seric SccsPath = ++p; 251262Seric break; 252148Seric 253588Seric case 'd': /* directory to search from */ 254588Seric SccsDir = ++p; 255588Seric break; 2561205Seric # endif 257588Seric 258393Seric # ifdef DEBUG 259393Seric case 'T': /* trace */ 260393Seric Debug++; 261393Seric break; 262393Seric # endif 263393Seric 264262Seric default: 2651205Seric usrerr("unknown option -%s", p); 266262Seric break; 267262Seric } 268148Seric } 269262Seric if (SccsPath[0] == '\0') 270262Seric SccsPath = "."; 271148Seric } 272148Seric 2731316Seric i = command(argv, FALSE, FALSE, ""); 2741282Seric exit(i); 275200Seric } 2761432Seric 2771432Seric /* 2781282Seric ** COMMAND -- look up and perform a command 2791282Seric ** 2801282Seric ** This routine is the guts of this program. Given an 2811282Seric ** argument vector, it looks up the "command" (argv[0]) 2821282Seric ** in the configuration table and does the necessary stuff. 2831282Seric ** 2841282Seric ** Parameters: 2851282Seric ** argv -- an argument vector to process. 2861282Seric ** forkflag -- if set, fork before executing the command. 2871316Seric ** editflag -- if set, only include flags listed in the 2881316Seric ** sccsklets field of the command descriptor. 2891316Seric ** arg0 -- a space-seperated list of arguments to insert 2901316Seric ** before argv. 2911282Seric ** 2921282Seric ** Returns: 2931282Seric ** zero -- command executed ok. 2941282Seric ** else -- error status. 2951282Seric ** 2961282Seric ** Side Effects: 2971282Seric ** none. 2981282Seric */ 299157Seric 3001316Seric command(argv, forkflag, editflag, arg0) 301200Seric char **argv; 302201Seric bool forkflag; 3031316Seric bool editflag; 3041316Seric char *arg0; 305200Seric { 306200Seric register struct sccsprog *cmd; 307200Seric register char *p; 308201Seric char buf[40]; 309262Seric extern struct sccsprog *lookup(); 3101316Seric char *nav[1000]; 3111316Seric char **np; 3121431Seric register char **ap; 313585Seric register int i; 3141431Seric register char *q; 315585Seric extern bool unedit(); 3161282Seric int rval = 0; 3171316Seric extern char *index(); 3181316Seric extern char *makefile(); 3191435Seric extern char *tail(); 320200Seric 321393Seric # ifdef DEBUG 322393Seric if (Debug) 323393Seric { 3241316Seric printf("command:\n\t\"%s\"\n", arg0); 3251316Seric for (np = argv; *np != NULL; np++) 3261316Seric printf("\t\"%s\"\n", *np); 327393Seric } 328393Seric # endif 329393Seric 330157Seric /* 3311316Seric ** Copy arguments. 3321438Seric ** Copy from arg0 & if necessary at most one arg 3331438Seric ** from argv[0]. 3341316Seric */ 3351316Seric 3361431Seric np = ap = &nav[1]; 3371316Seric for (p = arg0, q = buf; *p != '\0' && *p != '/'; ) 3381316Seric { 3391316Seric *np++ = q; 3401316Seric while (*p == ' ') 3411316Seric p++; 3421316Seric while (*p != ' ' && *p != '\0' && *p != '/') 3431316Seric *q++ = *p++; 3441316Seric *q++ = '\0'; 3451316Seric } 3461316Seric *np = NULL; 3471431Seric if (*ap == NULL) 3481316Seric *np++ = *argv++; 3491316Seric 3501316Seric /* 351148Seric ** Look up command. 3521431Seric ** At this point, *ap is the command name. 353148Seric */ 354148Seric 3551431Seric cmd = lookup(*ap); 356262Seric if (cmd == NULL) 357148Seric { 3581431Seric usrerr("Unknown command \"%s\"", *ap); 3591282Seric return (EX_USAGE); 360148Seric } 361148Seric 362148Seric /* 3631316Seric ** Copy remaining arguments doing editing as appropriate. 3641316Seric */ 3651316Seric 3661316Seric for (; *argv != NULL; argv++) 3671316Seric { 3681316Seric p = *argv; 3691316Seric if (*p == '-') 3701316Seric { 3711316Seric if (p[1] == '\0' || !editflag || cmd->sccsklets == NULL || 3721316Seric index(cmd->sccsklets, p[1]) != NULL) 3731316Seric *np++ = p; 3741316Seric } 3751316Seric else 3761316Seric { 3771316Seric if (!bitset(NO_SDOT, cmd->sccsflags)) 3781316Seric p = makefile(p); 3791316Seric if (p != NULL) 3801316Seric *np++ = p; 3811316Seric } 3821316Seric } 3831316Seric *np = NULL; 3841316Seric 3851316Seric /* 386200Seric ** Interpret operation associated with this command. 387157Seric */ 388157Seric 389200Seric switch (cmd->sccsoper) 390200Seric { 3911431Seric case SHELL: /* call a shell file */ 3921431Seric *ap = cmd->sccspath; 3931431Seric *--ap = "sh"; 3941431Seric rval = callprog("/bin/sh", cmd->sccsflags, ap, forkflag); 3951431Seric break; 3961431Seric 397200Seric case PROG: /* call an sccs prog */ 3981431Seric rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag); 399201Seric break; 400201Seric 401201Seric case CMACRO: /* command macro */ 4021438Seric /* step through & execute each part of the macro */ 403201Seric for (p = cmd->sccspath; *p != '\0'; p++) 404201Seric { 4051316Seric q = p; 4061316Seric while (*p != '\0' && *p != '/') 4071316Seric p++; 4081431Seric rval = command(&ap[1], *p != '\0', TRUE, q); 4091282Seric if (rval != 0) 4101282Seric break; 411201Seric } 4121282Seric break; 413157Seric 414226Seric case FIX: /* fix a delta */ 4151431Seric if (strncmp(ap[1], "-r", 2) != 0) 416226Seric { 4171205Seric usrerr("-r flag needed for fix command"); 4181282Seric rval = EX_USAGE; 419226Seric break; 420226Seric } 4211438Seric 4221438Seric /* get the version with all changes */ 4231431Seric rval = command(&ap[1], TRUE, TRUE, "get -k"); 4241438Seric 4251438Seric /* now remove that version from the s-file */ 4261282Seric if (rval == 0) 4271431Seric rval = command(&ap[1], TRUE, TRUE, "rmdel"); 4281438Seric 4291438Seric /* and edit the old version (but don't clobber new vers) */ 4301282Seric if (rval == 0) 4311431Seric rval = command(&ap[2], FALSE, TRUE, "get -e -g"); 4321282Seric break; 433226Seric 434261Seric case CLEAN: 4351282Seric rval = clean((int) cmd->sccspath); 436261Seric break; 437261Seric 438396Seric case UNEDIT: 4391431Seric for (argv = np = &ap[1]; *argv != NULL; argv++) 440585Seric { 4411316Seric if (unedit(*argv)) 4421316Seric *np++ = *argv; 443585Seric } 4441316Seric *np = NULL; 4451438Seric 4461438Seric /* get all the files that we unedited successfully */ 447585Seric if (i > 0) 4481431Seric rval = command(&ap[1], FALSE, FALSE, "get"); 449396Seric break; 450396Seric 4511433Seric case DIFFS: /* diff between s-file & edit file */ 4521433Seric /* find the end of the flag arguments */ 4531433Seric for (np = &ap[1]; *np != NULL && **np == '-'; np++) 4541433Seric continue; 4551433Seric argv = np; 4561433Seric 4571433Seric /* for each file, do the diff */ 458*1502Seric p = argv[1]; 4591433Seric while (*np != NULL) 4601433Seric { 4611438Seric /* messy, but we need a null terminated argv */ 4621433Seric *argv = *np++; 463*1502Seric argv[1] = NULL; 4641435Seric i = dodiff(ap, tail(*argv)); 4651433Seric if (rval == 0) 4661433Seric rval = i; 467*1502Seric argv[1] = p; 4681433Seric } 4691433Seric break; 4701433Seric 471200Seric default: 4721205Seric syserr("oper %d", cmd->sccsoper); 473200Seric exit(EX_SOFTWARE); 474200Seric } 4751282Seric # ifdef DEBUG 4761282Seric if (Debug) 4771282Seric printf("command: rval=%d\n", rval); 4781282Seric # endif 4791282Seric return (rval); 480200Seric } 4811432Seric 4821432Seric /* 483262Seric ** LOOKUP -- look up an SCCS command name. 484262Seric ** 485262Seric ** Parameters: 486262Seric ** name -- the name of the command to look up. 487262Seric ** 488262Seric ** Returns: 489262Seric ** ptr to command descriptor for this command. 490262Seric ** NULL if no such entry. 491262Seric ** 492262Seric ** Side Effects: 493262Seric ** none. 494262Seric */ 495200Seric 496262Seric struct sccsprog * 497262Seric lookup(name) 498262Seric char *name; 499262Seric { 500262Seric register struct sccsprog *cmd; 501226Seric 502262Seric for (cmd = SccsProg; cmd->sccsname != NULL; cmd++) 503262Seric { 504262Seric if (strcmp(cmd->sccsname, name) == 0) 505262Seric return (cmd); 506262Seric } 507262Seric return (NULL); 508262Seric } 5091432Seric 5101432Seric /* 5111282Seric ** CALLPROG -- call a program 5121282Seric ** 5131316Seric ** Used to call the SCCS programs. 5141282Seric ** 5151282Seric ** Parameters: 5161282Seric ** progpath -- pathname of the program to call. 5171282Seric ** flags -- status flags from the command descriptors. 5181282Seric ** argv -- an argument vector to pass to the program. 5191282Seric ** forkflag -- if true, fork before calling, else just 5201282Seric ** exec. 5211282Seric ** 5221282Seric ** Returns: 5231282Seric ** The exit status of the program. 5241282Seric ** Nothing if forkflag == FALSE. 5251282Seric ** 5261282Seric ** Side Effects: 5271282Seric ** Can exit if forkflag == FALSE. 5281282Seric */ 529226Seric 530200Seric callprog(progpath, flags, argv, forkflag) 531200Seric char *progpath; 532200Seric short flags; 533200Seric char **argv; 534200Seric bool forkflag; 535200Seric { 536200Seric register int i; 537201Seric auto int st; 538200Seric 5391316Seric # ifdef DEBUG 5401316Seric if (Debug) 5411316Seric { 5421316Seric printf("callprog:\n"); 5431316Seric for (i = 0; argv[i] != NULL; i++) 5441316Seric printf("\t\"%s\"\n", argv[i]); 5451316Seric } 5461316Seric # endif 5471316Seric 548200Seric if (*argv == NULL) 549200Seric return (-1); 550200Seric 551157Seric /* 552226Seric ** Fork if appropriate. 553148Seric */ 554148Seric 555200Seric if (forkflag) 556200Seric { 557393Seric # ifdef DEBUG 558393Seric if (Debug) 559393Seric printf("Forking\n"); 560393Seric # endif 561200Seric i = fork(); 562200Seric if (i < 0) 563200Seric { 5641205Seric syserr("cannot fork"); 565200Seric exit(EX_OSERR); 566200Seric } 567200Seric else if (i > 0) 568201Seric { 569201Seric wait(&st); 5701282Seric if ((st & 0377) == 0) 5711282Seric st = (st >> 8) & 0377; 5721433Seric if (OutFile >= 0) 5731433Seric { 5741433Seric close(OutFile); 5751433Seric OutFile = -1; 5761433Seric } 577201Seric return (st); 578201Seric } 579200Seric } 5801433Seric else if (OutFile >= 0) 5811433Seric { 5821433Seric syserr("callprog: setting stdout w/o forking"); 5831433Seric exit(EX_SOFTWARE); 5841433Seric } 585200Seric 5861433Seric /* set protection as appropriate */ 587200Seric if (bitset(REALUSER, flags)) 588200Seric setuid(getuid()); 5891433Seric 5901433Seric /* change standard input & output if needed */ 5911433Seric if (OutFile >= 0) 5921433Seric { 5931433Seric close(1); 5941433Seric dup(OutFile); 5951433Seric close(OutFile); 5961433Seric } 597226Seric 5981433Seric /* call real SCCS program */ 599226Seric execv(progpath, argv); 6001205Seric syserr("cannot execute %s", progpath); 601148Seric exit(EX_UNAVAILABLE); 602148Seric } 6031432Seric 6041432Seric /* 605586Seric ** MAKEFILE -- make filename of SCCS file 606586Seric ** 607586Seric ** If the name passed is already the name of an SCCS file, 608586Seric ** just return it. Otherwise, munge the name into the name 609586Seric ** of the actual SCCS file. 610586Seric ** 611586Seric ** There are cases when it is not clear what you want to 612586Seric ** do. For example, if SccsPath is an absolute pathname 613586Seric ** and the name given is also an absolute pathname, we go 614586Seric ** for SccsPath (& only use the last component of the name 615586Seric ** passed) -- this is important for security reasons (if 616586Seric ** sccs is being used as a setuid front end), but not 617586Seric ** particularly intuitive. 618586Seric ** 619586Seric ** Parameters: 620586Seric ** name -- the file name to be munged. 621586Seric ** 622586Seric ** Returns: 623586Seric ** The pathname of the sccs file. 624586Seric ** NULL on error. 625586Seric ** 626586Seric ** Side Effects: 627586Seric ** none. 628586Seric */ 629148Seric 630148Seric char * 631148Seric makefile(name) 632148Seric char *name; 633148Seric { 634148Seric register char *p; 635148Seric register char c; 636148Seric char buf[512]; 637588Seric struct stat stbuf; 638148Seric extern char *malloc(); 639586Seric extern char *rindex(); 640588Seric extern bool safepath(); 641587Seric extern bool isdir(); 642587Seric register char *q; 643148Seric 644586Seric p = rindex(name, '/'); 645586Seric if (p == NULL) 646586Seric p = name; 647586Seric else 648586Seric p++; 649586Seric 650148Seric /* 651588Seric ** Check to see that the path is "safe", i.e., that we 652588Seric ** are not letting some nasty person use the setuid part 653588Seric ** of this program to look at or munge some presumably 654588Seric ** hidden files. 655148Seric */ 656148Seric 657588Seric if (SccsDir[0] == '/' && !safepath(name)) 658588Seric return (NULL); 659586Seric 660586Seric /* 661588Seric ** Create the base pathname. 662586Seric */ 663586Seric 6641438Seric /* first the directory part */ 665588Seric if (SccsDir[0] != '\0' && name[0] != '/' && strncmp(name, "./", 2) != 0) 666148Seric { 667588Seric strcpy(buf, SccsDir); 668586Seric strcat(buf, "/"); 669586Seric } 670586Seric else 671586Seric strcpy(buf, ""); 6721438Seric 6731438Seric /* then the head of the pathname */ 674587Seric strncat(buf, name, p - name); 675587Seric q = &buf[strlen(buf)]; 6761438Seric 6771438Seric /* now copy the final part of the name, in case useful */ 678587Seric strcpy(q, p); 6791438Seric 6801438Seric /* so is it useful? */ 681587Seric if (strncmp(p, "s.", 2) != 0 && !isdir(buf)) 682586Seric { 6831438Seric /* sorry, no; copy the SCCS pathname & the "s." */ 684588Seric strcpy(q, SccsPath); 685588Seric strcat(buf, "/s."); 6861438Seric 6871438Seric /* and now the end of the name */ 688586Seric strcat(buf, p); 689586Seric } 690148Seric 6911438Seric /* if i haven't changed it, why did I do all this? */ 692588Seric if (strcmp(buf, name) == 0) 693588Seric p = name; 694588Seric else 695148Seric { 6961438Seric /* but if I have, squirrel it away */ 697588Seric p = malloc(strlen(buf) + 1); 698588Seric if (p == NULL) 699588Seric { 700588Seric perror("Sccs: no mem"); 701588Seric exit(EX_OSERR); 702588Seric } 703588Seric strcpy(p, buf); 704148Seric } 7051438Seric 706148Seric return (p); 707148Seric } 7081432Seric 7091432Seric /* 710587Seric ** ISDIR -- return true if the argument is a directory. 711587Seric ** 712587Seric ** Parameters: 713587Seric ** name -- the pathname of the file to check. 714587Seric ** 715587Seric ** Returns: 716587Seric ** TRUE if 'name' is a directory, FALSE otherwise. 717587Seric ** 718587Seric ** Side Effects: 719587Seric ** none. 720587Seric */ 721587Seric 722587Seric bool 723587Seric isdir(name) 724587Seric char *name; 725587Seric { 726587Seric struct stat stbuf; 727587Seric 728587Seric return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR); 729587Seric } 7301432Seric 7311432Seric /* 732586Seric ** SAFEPATH -- determine whether a pathname is "safe" 733586Seric ** 734586Seric ** "Safe" pathnames only allow you to get deeper into the 735586Seric ** directory structure, i.e., full pathnames and ".." are 736586Seric ** not allowed. 737586Seric ** 738586Seric ** Parameters: 739586Seric ** p -- the name to check. 740586Seric ** 741586Seric ** Returns: 742586Seric ** TRUE -- if the path is safe. 743586Seric ** FALSE -- if the path is not safe. 744586Seric ** 745586Seric ** Side Effects: 746586Seric ** Prints a message if the path is not safe. 747586Seric */ 748586Seric 749586Seric bool 750586Seric safepath(p) 751586Seric register char *p; 752586Seric { 753586Seric extern char *index(); 754586Seric 755586Seric if (*p != '/') 756586Seric { 757586Seric while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0) 758586Seric { 759586Seric p = index(p, '/'); 760586Seric if (p == NULL) 761586Seric return (TRUE); 762586Seric p++; 763586Seric } 764586Seric } 765586Seric 766586Seric printf("You may not use full pathnames or \"..\"\n"); 767586Seric return (FALSE); 768586Seric } 7691432Seric 7701432Seric /* 771261Seric ** CLEAN -- clean out recreatable files 772261Seric ** 773261Seric ** Any file for which an "s." file exists but no "p." file 774261Seric ** exists in the current directory is purged. 775261Seric ** 776261Seric ** Parameters: 777819Seric ** tells whether this came from a "clean", "info", or 778819Seric ** "check" command. 779261Seric ** 780261Seric ** Returns: 781261Seric ** none. 782261Seric ** 783261Seric ** Side Effects: 784819Seric ** Removes files in the current directory. 785819Seric ** Prints information regarding files being edited. 786819Seric ** Exits if a "check" command. 787261Seric */ 788261Seric 789819Seric clean(mode) 790819Seric int mode; 791261Seric { 792261Seric struct direct dir; 793261Seric struct stat stbuf; 794261Seric char buf[100]; 795394Seric char pline[120]; 796346Seric register FILE *dirfd; 797346Seric register char *basefile; 798351Seric bool gotedit; 799394Seric FILE *pfp; 800261Seric 8011438Seric /* 8021438Seric ** Find and open the SCCS directory. 8031438Seric */ 8041438Seric 8051207Seric strcpy(buf, SccsDir); 8061207Seric if (buf[0] != '\0') 8071207Seric strcat(buf, "/"); 8081207Seric strcat(buf, SccsPath); 8091438Seric 8101207Seric dirfd = fopen(buf, "r"); 811261Seric if (dirfd == NULL) 812261Seric { 8131207Seric usrerr("cannot open %s", buf); 8141282Seric return (EX_NOINPUT); 815261Seric } 816261Seric 817261Seric /* 818261Seric ** Scan the SCCS directory looking for s. files. 8191438Seric ** gotedit tells whether we have tried to clean any 8201438Seric ** files that are being edited. 821261Seric */ 822261Seric 823351Seric gotedit = FALSE; 824261Seric while (fread(&dir, sizeof dir, 1, dirfd) != NULL) 825261Seric { 826568Seric if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0) 827261Seric continue; 828261Seric 829261Seric /* got an s. file -- see if the p. file exists */ 8301207Seric strcpy(buf, SccsDir); 8311207Seric if (buf[0] != '\0') 8321207Seric strcat(buf, "/"); 8331207Seric strcat(buf, SccsPath); 834261Seric strcat(buf, "/p."); 835346Seric basefile = &buf[strlen(buf)]; 836568Seric strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2); 837346Seric basefile[sizeof dir.d_name - 2] = '\0'; 838394Seric pfp = fopen(buf, "r"); 839394Seric if (pfp != NULL) 840346Seric { 8411438Seric /* the file exists -- report it's contents */ 842394Seric while (fgets(pline, sizeof pline, pfp) != NULL) 843416Seric printf("%12s: being edited: %s", basefile, pline); 844394Seric fclose(pfp); 845351Seric gotedit = TRUE; 846261Seric continue; 847346Seric } 848261Seric 849261Seric /* the s. file exists and no p. file exists -- unlink the g-file */ 850819Seric if (mode == CLEANC) 851346Seric { 852568Seric strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2); 853346Seric buf[sizeof dir.d_name - 2] = '\0'; 854346Seric unlink(buf); 855346Seric } 856261Seric } 857261Seric 8581438Seric /* cleanup & report results */ 859261Seric fclose(dirfd); 860819Seric if (!gotedit && mode == INFOC) 861416Seric printf("Nothing being edited\n"); 862819Seric if (mode == CHECKC) 863819Seric exit(gotedit); 8641282Seric return (EX_OK); 865261Seric } 8661432Seric 8671432Seric /* 868396Seric ** UNEDIT -- unedit a file 869396Seric ** 870396Seric ** Checks to see that the current user is actually editting 871396Seric ** the file and arranges that s/he is not editting it. 872396Seric ** 873396Seric ** Parameters: 874416Seric ** fn -- the name of the file to be unedited. 875396Seric ** 876396Seric ** Returns: 877585Seric ** TRUE -- if the file was successfully unedited. 878585Seric ** FALSE -- if the file was not unedited for some 879585Seric ** reason. 880396Seric ** 881396Seric ** Side Effects: 882396Seric ** fn is removed 883396Seric ** entries are removed from pfile. 884396Seric */ 885396Seric 886585Seric bool 887396Seric unedit(fn) 888396Seric char *fn; 889396Seric { 890396Seric register FILE *pfp; 891396Seric char *pfn; 892396Seric static char tfn[] = "/tmp/sccsXXXXX"; 893396Seric FILE *tfp; 894396Seric register char *p; 895396Seric register char *q; 896396Seric bool delete = FALSE; 897396Seric bool others = FALSE; 898396Seric char *myname; 899396Seric extern char *getlogin(); 900396Seric struct pfile *pent; 901396Seric extern struct pfile *getpfile(); 902396Seric char buf[120]; 9031316Seric extern char *makefile(); 904828Seric # ifdef UIDUSER 905828Seric struct passwd *pw; 906828Seric extern struct passwd *getpwuid(); 907828Seric # endif UIDUSER 908396Seric 909396Seric /* make "s." filename & find the trailing component */ 910396Seric pfn = makefile(fn); 911586Seric if (pfn == NULL) 912586Seric return (FALSE); 913586Seric q = rindex(pfn, '/'); 914586Seric if (q == NULL) 915586Seric q = &pfn[-1]; 916586Seric if (q[1] != 's' || q[2] != '.') 917396Seric { 9181205Seric usrerr("bad file name \"%s\"", fn); 919585Seric return (FALSE); 920396Seric } 921396Seric 9221438Seric /* turn "s." into "p." & try to open it */ 923396Seric *++q = 'p'; 924396Seric 925396Seric pfp = fopen(pfn, "r"); 926396Seric if (pfp == NULL) 927396Seric { 928416Seric printf("%12s: not being edited\n", fn); 929585Seric return (FALSE); 930396Seric } 931396Seric 9321438Seric /* create temp file for editing p-file */ 933396Seric mktemp(tfn); 934396Seric tfp = fopen(tfn, "w"); 935396Seric if (tfp == NULL) 936396Seric { 9371205Seric usrerr("cannot create \"%s\"", tfn); 938396Seric exit(EX_OSERR); 939396Seric } 940396Seric 9411438Seric /* figure out who I am */ 942828Seric # ifdef UIDUSER 943828Seric pw = getpwuid(getuid()); 944828Seric if (pw == NULL) 945828Seric { 9461205Seric syserr("who are you? (uid=%d)", getuid()); 947828Seric exit(EX_OSERR); 948828Seric } 949828Seric myname = pw->pw_name; 950828Seric # else 951396Seric myname = getlogin(); 952828Seric # endif UIDUSER 9531438Seric 9541438Seric /* 9551438Seric ** Copy p-file to temp file, doing deletions as needed. 9561438Seric */ 9571438Seric 958396Seric while ((pent = getpfile(pfp)) != NULL) 959396Seric { 960396Seric if (strcmp(pent->p_user, myname) == 0) 961396Seric { 962396Seric /* a match */ 963396Seric delete++; 964396Seric } 965396Seric else 966396Seric { 9671438Seric /* output it again */ 968396Seric fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid, 969396Seric pent->p_nsid, pent->p_user, pent->p_date, 970396Seric pent->p_time); 971396Seric others++; 972396Seric } 973396Seric } 974396Seric 975396Seric /* do final cleanup */ 976396Seric if (others) 977396Seric { 9781438Seric /* copy it back (perhaps it should be linked?) */ 979396Seric if (freopen(tfn, "r", tfp) == NULL) 980396Seric { 9811205Seric syserr("cannot reopen \"%s\"", tfn); 982396Seric exit(EX_OSERR); 983396Seric } 984396Seric if (freopen(pfn, "w", pfp) == NULL) 985396Seric { 9861205Seric usrerr("cannot create \"%s\"", pfn); 987585Seric return (FALSE); 988396Seric } 989396Seric while (fgets(buf, sizeof buf, tfp) != NULL) 990396Seric fputs(buf, pfp); 991396Seric } 992396Seric else 993396Seric { 9941438Seric /* it's empty -- remove it */ 995396Seric unlink(pfn); 996396Seric } 997396Seric fclose(tfp); 998396Seric fclose(pfp); 999396Seric unlink(tfn); 1000396Seric 10011438Seric /* actually remove the g-file */ 1002396Seric if (delete) 1003396Seric { 10041435Seric unlink(tail(fn)); 10051435Seric printf("%12s: removed\n", tail(fn)); 1006585Seric return (TRUE); 1007396Seric } 1008396Seric else 1009396Seric { 1010416Seric printf("%12s: not being edited by you\n", fn); 1011585Seric return (FALSE); 1012396Seric } 1013396Seric } 10141432Seric 10151432Seric /* 10161433Seric ** DODIFF -- diff an s-file against a g-file 10171433Seric ** 10181433Seric ** Parameters: 10191433Seric ** getv -- argv for the 'get' command. 10201433Seric ** gfile -- name of the g-file to diff against. 10211433Seric ** 10221433Seric ** Returns: 10231433Seric ** Result of get. 10241433Seric ** 10251433Seric ** Side Effects: 10261433Seric ** none. 10271433Seric */ 10281433Seric 10291433Seric dodiff(getv, gfile) 10301433Seric char **getv; 10311433Seric char *gfile; 10321433Seric { 10331433Seric int pipev[2]; 10341433Seric int rval; 10351433Seric register int i; 10361433Seric register int pid; 10371433Seric auto int st; 10381433Seric extern int errno; 10391433Seric int (*osig)(); 10401433Seric 10411501Seric printf("\n------- %s -------\n", gfile); 10421501Seric 10431438Seric /* create context for diff to run in */ 10441433Seric if (pipe(pipev) < 0) 10451433Seric { 10461433Seric syserr("dodiff: pipe failed"); 10471433Seric exit(EX_OSERR); 10481433Seric } 10491433Seric if ((pid = fork()) < 0) 10501433Seric { 10511433Seric syserr("dodiff: fork failed"); 10521433Seric exit(EX_OSERR); 10531433Seric } 10541433Seric else if (pid > 0) 10551433Seric { 10561433Seric /* in parent; run get */ 10571433Seric OutFile = pipev[1]; 10581433Seric close(pipev[0]); 10591433Seric rval = command(&getv[1], TRUE, FALSE, "get -s -k -p"); 10601433Seric osig = signal(SIGINT, SIG_IGN); 10611433Seric while (((i = wait(&st)) >= 0 && i != pid) || errno == EINTR) 10621433Seric errno = 0; 10631433Seric signal(SIGINT, osig); 10641433Seric /* ignore result of diff */ 10651433Seric } 10661433Seric else 10671433Seric { 10681433Seric /* in child, run diff */ 10691433Seric if (close(pipev[1]) < 0 || close(0) < 0 || 10701433Seric dup(pipev[0]) != 0 || close(pipev[0]) < 0) 10711433Seric { 10721433Seric syserr("dodiff: magic failed"); 10731433Seric exit(EX_OSERR); 10741433Seric } 10751433Seric execl(PROGPATH(bdiff), "bdiff", "-", gfile, NULL); 10761433Seric # ifndef V6 10771433Seric execlp("bdiff", "bdiff", "-", gfile, NULL); 10781433Seric execlp("diff", "diff", "-", gfile, NULL); 10791433Seric # endif NOT V6 10801433Seric syserr("bdiff: cannot execute"); 10811433Seric exit(EX_OSERR); 10821433Seric } 10831433Seric return (rval); 10841433Seric } 10851433Seric 10861433Seric /* 10871435Seric ** TAIL -- return tail of filename. 10881435Seric ** 10891435Seric ** Parameters: 10901435Seric ** fn -- the filename. 10911435Seric ** 10921435Seric ** Returns: 10931435Seric ** a pointer to the tail of the filename; e.g., given 10941435Seric ** "cmd/ls.c", "ls.c" is returned. 10951435Seric ** 10961435Seric ** Side Effects: 10971435Seric ** none. 10981435Seric */ 10991435Seric 11001435Seric char * 11011435Seric tail(fn) 11021435Seric register char *fn; 11031435Seric { 11041435Seric register char *p; 11051435Seric 11061435Seric for (p = fn; *p != 0; p++) 11071435Seric if (*p == '/' && p[1] != '\0' && p[1] != '/') 11081435Seric fn = &p[1]; 11091435Seric return (fn); 11101435Seric } 11111435Seric 11121435Seric /* 1113396Seric ** GETPFILE -- get an entry from the p-file 1114396Seric ** 1115396Seric ** Parameters: 1116396Seric ** pfp -- p-file file pointer 1117396Seric ** 1118396Seric ** Returns: 1119396Seric ** pointer to p-file struct for next entry 1120396Seric ** NULL on EOF or error 1121396Seric ** 1122396Seric ** Side Effects: 1123396Seric ** Each call wipes out results of previous call. 1124396Seric */ 1125396Seric 1126396Seric struct pfile * 1127396Seric getpfile(pfp) 1128396Seric FILE *pfp; 1129396Seric { 1130396Seric static struct pfile ent; 1131396Seric static char buf[120]; 1132396Seric register char *p; 1133396Seric extern char *nextfield(); 1134396Seric 1135396Seric if (fgets(buf, sizeof buf, pfp) == NULL) 1136396Seric return (NULL); 1137396Seric 1138396Seric ent.p_osid = p = buf; 1139396Seric ent.p_nsid = p = nextfield(p); 1140396Seric ent.p_user = p = nextfield(p); 1141396Seric ent.p_date = p = nextfield(p); 1142396Seric ent.p_time = p = nextfield(p); 1143396Seric if (p == NULL || nextfield(p) != NULL) 1144396Seric return (NULL); 1145396Seric 1146396Seric return (&ent); 1147396Seric } 1148396Seric 1149396Seric 1150396Seric char * 1151396Seric nextfield(p) 1152396Seric register char *p; 1153396Seric { 1154396Seric if (p == NULL || *p == '\0') 1155396Seric return (NULL); 1156396Seric while (*p != ' ' && *p != '\n' && *p != '\0') 1157396Seric p++; 1158396Seric if (*p == '\n' || *p == '\0') 1159396Seric { 1160396Seric *p = '\0'; 1161396Seric return (NULL); 1162396Seric } 1163396Seric *p++ = '\0'; 1164396Seric return (p); 1165396Seric } 11661432Seric 11671432Seric /* 11681205Seric ** USRERR -- issue user-level error 11691205Seric ** 11701205Seric ** Parameters: 11711205Seric ** f -- format string. 11721205Seric ** p1-p3 -- parameters to a printf. 11731205Seric ** 11741205Seric ** Returns: 11751205Seric ** -1 11761205Seric ** 11771205Seric ** Side Effects: 11781205Seric ** none. 11791205Seric */ 11801205Seric 11811205Seric usrerr(f, p1, p2, p3) 11821205Seric char *f; 11831205Seric { 11841205Seric fprintf(stderr, "\n%s: ", MyName); 11851205Seric fprintf(stderr, f, p1, p2, p3); 11861205Seric fprintf(stderr, "\n"); 11871205Seric 11881205Seric return (-1); 11891205Seric } 11901432Seric 11911432Seric /* 11921205Seric ** SYSERR -- print system-generated error. 11931205Seric ** 11941205Seric ** Parameters: 11951205Seric ** f -- format string to a printf. 11961205Seric ** p1, p2, p3 -- parameters to f. 11971205Seric ** 11981205Seric ** Returns: 11991205Seric ** never. 12001205Seric ** 12011205Seric ** Side Effects: 12021205Seric ** none. 12031205Seric */ 12041205Seric 12051205Seric syserr(f, p1, p2, p3) 12061205Seric char *f; 12071205Seric { 12081205Seric extern int errno; 12091205Seric 12101205Seric fprintf(stderr, "\n%s SYSERR: ", MyName); 12111205Seric fprintf(stderr, f, p1, p2, p3); 12121205Seric fprintf(stderr, "\n"); 12131205Seric if (errno == 0) 12141205Seric exit(EX_SOFTWARE); 12151205Seric else 12161205Seric { 12171205Seric perror(0); 12181205Seric exit(EX_OSERR); 12191205Seric } 12201205Seric } 1221