** SCCS.C -- human-oriented front end to the SCCS system.
** Without trying to add any functionality to speak of, this
** program tries to make SCCS a little more accessible to human
** types. The main thing it does is automatically put the
** string "SCCS/s." on the front of names. Also, it has a
** couple of things that are designed to shorten frequent
** combinations, e.g., "delget" which expands to a "delta"
** This program can also function as a setuid front end.
** To do this, you should copy the source, renaming it to
** whatever you want, e.g., "syssccs". Change any defaults
** in the program (e.g., syssccs might default -d to
** "/usr/src/sys"). Then recompile and put the result
** as setuid to whomever you want. In this mode, sccs
** knows to not run setuid for certain programs in order
** to preserve security, and so forth.
** sccs [flags] command [args]
** -d<dir> <dir> represents a directory to search
** out of. It should be a full pathname
** for general usage. E.g., if <dir> is
** "/usr/src/sys", then a reference to the
** file "dev/bio.c" becomes a reference to
** "/usr/src/sys/dev/bio.c".
** -p<path> prepends <path> to the final component
** of the pathname. By default, this is
** "SCCS". For example, in the -d example
** above, the path then gets modified to
** "/usr/src/sys/dev/SCCS/s.bio.c". In
** more common usage (without the -d flag),
** "prog.c" would get modified to
** "SCCS/s.prog.c". In both cases, the
** "s." gets automatically prepended.
** -r run as the real user.
** etc. Straight out of SCCS; only difference
** is that pathnames get modified as
** edit Macro for "get -e".
** unedit Removes a file being edited, knowing
** delget Macro for "delta" followed by "get".
** deledit Macro for "delta" followed by "get -e".
** info Tell what files being edited.
** clean Remove all files that can be
** regenerated from SCCS files.
** check Like info, but return exit status, for
** fix Remove a top delta & reedit, but save
** the previous changes in that delta.
** UIDUSER -- determine who the user is by looking at the
** uid rather than the login name -- for machines
** where SCCS gets the user in this way.
** SCCSDIR -- if defined, forces the -d flag to take on
** this value. This is so that the setuid
** aspects of this program cannot be abused.
** This flag also disables the -p flag.
** SCCSPATH -- the default for the -p flag.
** MYNAME -- the title this program should print when it
** Compilation Instructions:
** The flags listed above can be -D defined to simplify
** recompilation for variant versions.
** Eric Allman, UCB/INGRES
** Copyright 1980 Regents of the University of California
static char SccsId
[] = "@(#)sccs.c 1.64 %G%";
/******************* Configuration Information ********************/
/* special defines for local berkeley systems */
# define PROGPATH(name) "/usr/local/name"
# define PROGPATH(name) "/usr/local/name"
# define PROGPATH(name) "/usr/eecs/bin/name"
/* end of berkeley systems defines */
# define SCCSPATH "SCCS" /* pathname in which to find s-files */
# define MYNAME "sccs" /* name used for printing errors */
# define PROGPATH(name) "/usr/sccs/name" /* place to find binaries */
/**************** End of Configuration Information ****************/
# define bitset(bit, word) ((bool) ((bit) & (word)))
char *sccsname
; /* name of SCCS routine */
short sccsoper
; /* opcode, see below */
short sccsflags
; /* flags, see below */
char *sccspath
; /* pathname of binary implementing */
/* values for sccsoper */
# define PROG 0 /* call a program */
# define CMACRO 1 /* command substitution macro */
# define FIX 2 /* fix a delta */
# define CLEAN 3 /* clean out recreatable files */
# define UNEDIT 4 /* unedit a file */
# define SHELL 5 /* call a shell file (like PROG) */
# define DIFFS 6 /* diff between sccs & file out */
# define DODIFF 7 /* internal call to diff program */
# define CREATE 8 /* create new files */
# define NO_SDOT 0001 /* no s. on front of args */
# define REALUSER 0002 /* protected (e.g., admin) */
/* modes for the "clean", "info", "check" ops */
# define CLEANC 0 /* clean command */
# define INFOC 1 /* info command */
# define CHECKC 2 /* check command */
# define TELLC 3 /* give list of files being edited */
** Description of commands known to this program.
** First argument puts the command into a class. Second arg is
** info regarding treatment of this command. Third arg is a
** list of flags this command accepts from macros, etc. Fourth
** arg is the pathname of the implementing program, or the
** macro definition, or the arg to a sub-algorithm.
struct sccsprog SccsProg
[] =
"admin", PROG
, REALUSER
, PROGPATH(admin
),
"chghist", PROG
, 0, PROGPATH(rmdel
),
"comb", PROG
, 0, PROGPATH(comb
),
"delta", PROG
, 0, PROGPATH(delta
),
"get", PROG
, 0, PROGPATH(get
),
"help", PROG
, NO_SDOT
, PROGPATH(help
),
"prs", PROG
, 0, PROGPATH(prs
),
"prt", PROG
, 0, PROGPATH(prt
),
"rmdel", PROG
, REALUSER
, PROGPATH(rmdel
),
"val", PROG
, 0, PROGPATH(val
),
"what", PROG
, NO_SDOT
, PROGPATH(what
),
"sccsdiff", SHELL
, REALUSER
, PROGPATH(sccsdiff
),
"edit", CMACRO
, NO_SDOT
, "get -e",
"delget", CMACRO
, NO_SDOT
, "delta:mysrp/get:ixbeskcl -t",
"deledit", CMACRO
, NO_SDOT
, "delta:mysrp -n/get:ixbskcl -e -t -g",
"fix", FIX
, NO_SDOT
, NULL
,
"clean", CLEAN
, REALUSER
|NO_SDOT
, (char *) CLEANC
,
"info", CLEAN
, REALUSER
|NO_SDOT
, (char *) INFOC
,
"check", CLEAN
, REALUSER
|NO_SDOT
, (char *) CHECKC
,
"tell", CLEAN
, REALUSER
|NO_SDOT
, (char *) TELLC
,
"unedit", UNEDIT
, NO_SDOT
, NULL
,
"diffs", DIFFS
, NO_SDOT
|REALUSER
, NULL
,
"-diff", DODIFF
, NO_SDOT
|REALUSER
, PROGPATH(bdiff
),
"print", CMACRO
, 0, "prt -e/get -p -m -s",
"branch", CMACRO
, NO_SDOT
,
"get:ixrc -e -b/delta: -s -n -ybranch-place-holder/get:pl -e -t -g",
"create", CREATE
, NO_SDOT
, NULL
,
/* one line from a p-file */
char *p_osid
; /* old SID */
char *p_nsid
; /* new SID */
char *p_user
; /* user who did edit */
char *p_date
; /* date of get */
char *p_time
; /* time of get */
char *p_aux
; /* extra info at end */
char *SccsPath
= SCCSPATH
; /* pathname of SCCS files */
char *SccsDir
= SCCSDIR
; /* directory to begin search from */
char MyName
[] = MYNAME
; /* name used in messages */
int OutFile
= -1; /* override output file for commands */
bool RealUser
; /* if set, running as real user */
bool Debug
; /* turn on tracing */
extern struct sccsprog
*lookup();
register struct passwd
*pw
;
extern struct passwd
*getpwnam();
/* pull "SccsDir" out of the environment (possibly) */
if (p
!= NULL
&& p
[0] != '\0')
usrerr("user %s does not exist", p
);
usrerr("project %s has no source!", p
);
** Detect and decode flags intended for this program.
fprintf(stderr
, "Usage: %s [flags] command [flags]\n", MyName
);
if (lookup(argv
[0]) == NULL
)
while ((p
= *++argv
) != NULL
)
case 'r': /* run as real user */
case 'p': /* path of sccs files */
if (SccsPath
[0] == '\0' && argv
[1] != NULL
)
case 'd': /* directory to search from */
if (SccsDir
[0] == '\0' && argv
[1] != NULL
)
usrerr("unknown option -%s", p
);
i
= command(argv
, FALSE
, "");
** COMMAND -- look up and perform a command
** This routine is the guts of this program. Given an
** argument vector, it looks up the "command" (argv[0])
** in the configuration table and does the necessary stuff.
** argv -- an argument vector to process.
** forkflag -- if set, fork before executing the command.
** editflag -- if set, only include flags listed in the
** sccsklets field of the command descriptor.
** arg0 -- a space-seperated list of arguments to insert
** zero -- command executed ok.
command(argv
, forkflag
, arg0
)
register struct sccsprog
*cmd
;
extern struct sccsprog
*lookup();
printf("command:\n\t\"%s\"\n", arg0
);
for (np
= argv
; *np
!= NULL
; np
++)
printf("\t\"%s\"\n", *np
);
** Copy from arg0 & if necessary at most one arg
for (p
= arg0
, q
= buf
; *p
!= '\0' && *p
!= '/'; )
while (*p
!= ' ' && *p
!= '\0' && *p
!= '/' && *p
!= ':')
while (*++p
!= '\0' && *p
!= '/' && *p
!= ' ')
** At this point, *ap is the command name.
usrerr("Unknown command \"%s\"", *ap
);
** Copy remaining arguments doing editing as appropriate.
for (; *argv
!= NULL
; argv
++)
if (p
[1] == '\0' || editchs
== NULL
|| index(editchs
, p
[1]) != NULL
)
if (!bitset(NO_SDOT
, cmd
->sccsflags
))
** Interpret operation associated with this command.
case SHELL
: /* call a shell file */
rval
= callprog("/bin/sh", cmd
->sccsflags
, ap
, forkflag
);
case PROG
: /* call an sccs prog */
rval
= callprog(cmd
->sccspath
, cmd
->sccsflags
, ap
, forkflag
);
case CMACRO
: /* command macro */
/* step through & execute each part of the macro */
for (p
= cmd
->sccspath
; *p
!= '\0'; p
++)
while (*p
!= '\0' && *p
!= '/')
rval
= command(&ap
[1], *p
!= '\0', q
);
case FIX
: /* fix a delta */
if (strncmp(ap
[1], "-r", 2) != 0)
usrerr("-r flag needed for fix command");
/* get the version with all changes */
rval
= command(&ap
[1], TRUE
, "get -k");
/* now remove that version from the s-file */
rval
= command(&ap
[1], TRUE
, "rmdel:r");
/* and edit the old version (but don't clobber new vers) */
rval
= command(&ap
[2], FALSE
, "get -e -g");
rval
= clean((int) cmd
->sccspath
, ap
);
for (argv
= np
= &ap
[1]; *argv
!= NULL
; argv
++)
/* get all the files that we unedited successfully */
rval
= command(&ap
[1], FALSE
, "get");
case DIFFS
: /* diff between s-file & edit file */
/* find the end of the flag arguments */
for (np
= &ap
[1]; *np
!= NULL
&& **np
== '-'; np
++)
/* for each file, do the diff */
/* messy, but we need a null terminated argv */
i
= dodiff(ap
, tail(*argv
));
case DODIFF
: /* internal diff call */
for (np
= ap
; *np
!= NULL
; np
++)
if ((*np
)[0] == '-' && (*np
)[1] == 'C')
/* insert "-" argument */
/* execute the diff program of choice */
execv(cmd
->sccspath
, argv
);
syserr("cannot exec %s", cmd
->sccspath
);
case CREATE
: /* create new sccs files */
/* skip over flag arguments */
for (np
= &ap
[1]; *np
!= NULL
&& **np
== '-'; np
++)
/* do an admin for each file */
sprintf(buf
, "-i%s", *np
);
rval
= command(ap
, TRUE
, "admin");
sprintf(buf
, ",%s", tail(*np
));
syserr("oper %d", cmd
->sccsoper
);
printf("command: rval=%d\n", rval
);
** LOOKUP -- look up an SCCS command name.
** name -- the name of the command to look up.
** ptr to command descriptor for this command.
** NULL if no such entry.
register struct sccsprog
*cmd
;
for (cmd
= SccsProg
; cmd
->sccsname
!= NULL
; cmd
++)
if (strcmp(cmd
->sccsname
, name
) == 0)
** CALLPROG -- call a program
** Used to call the SCCS programs.
** progpath -- pathname of the program to call.
** flags -- status flags from the command descriptors.
** argv -- an argument vector to pass to the program.
** forkflag -- if true, fork before calling, else just
** The exit status of the program.
** Nothing if forkflag == FALSE.
** Can exit if forkflag == FALSE.
callprog(progpath
, flags
, argv
, forkflag
)
for (i
= 0; argv
[i
] != NULL
; i
++)
printf("\t\"%s\"\n", argv
[i
]);
syserr("callprog: setting stdout w/o forking");
/* set protection as appropriate */
if (bitset(REALUSER
, flags
))
/* change standard input & output if needed */
/* call real SCCS program */
syserr("cannot execute %s", progpath
);
** MAKEFILE -- make filename of SCCS file
** If the name passed is already the name of an SCCS file,
** just return it. Otherwise, munge the name into the name
** of the actual SCCS file.
** There are cases when it is not clear what you want to
** do. For example, if SccsPath is an absolute pathname
** and the name given is also an absolute pathname, we go
** for SccsPath (& only use the last component of the name
** passed) -- this is important for security reasons (if
** sccs is being used as a setuid front end), but not
** particularly intuitive.
** name -- the file name to be munged.
** The pathname of the sccs file.
** See if the name can be used as-is.
if (SccsPath
[0] != '/' || name
[0] == '/' || strncmp(name
, "./", 2) == 0)
if (strncmp(p
, "s.", 2) == 0)
** Create the actual pathname.
/* first the directory part */
/* then the head of the pathname */
strncat(buf
, name
, p
- name
);
if (strncmp(p
, "s.", 2) != 0 && !isdir(buf
))
/* sorry, no; copy the SCCS pathname & the "s." */
/* and now the end of the name */
/* if i haven't changed it, why did I do all this? */
if (strcmp(buf
, name
) == 0)
return (stat(name
, &stbuf
) >= 0 && (stbuf
.st_mode
& S_IFMT
) == S_IFDIR
);
** ISDIR -- return true if the argument is a directory.
** name -- the pathname of the file to check.
** TRUE if 'name' is a directory, FALSE otherwise.
return (stat(name
, &stbuf
) >= 0 && (stbuf
.st_mode
& S_IFMT
) == S_IFDIR
);
** SAFEPATH -- determine whether a pathname is "safe"
** "Safe" pathnames only allow you to get deeper into the
** directory structure, i.e., full pathnames and ".." are
** p -- the name to check.
** TRUE -- if the path is safe.
** FALSE -- if the path is not safe.
** Prints a message if the path is not safe.
while (strncmp(p
, "../", 3) != 0 && strcmp(p
, "..") != 0)
printf("You may not use full pathnames or \"..\"\n");
** CLEAN -- clean out recreatable files
** Any file for which an "s." file exists but no "p." file
** exists in the current directory is purged.
** mode -- tells whether this came from a "clean", "info", or
** argv -- the rest of the argument vector.
** Removes files in the current directory.
** Prints information regarding files being edited.
** Exits if a "check" command.
extern struct pfile
*getpfent();
register struct pfile
*pf
;
for (ap
= argv
; *++ap
!= NULL
; )
else if (ap
[1] != NULL
&& ap
[1][0] != '-')
** Find and open the SCCS directory.
bufend
= &buf
[strlen(buf
)];
usrerr("cannot open %s", buf
);
** Scan the SCCS directory looking for s. files.
** gotedit tells whether we have tried to clean any
** files that are being edited.
while (fread((char *)&dir
, sizeof dir
, 1, dirfd
) != NULL
)
if (dir
.d_ino
== 0 || strncmp(dir
.d_name
, "s.", 2) != 0)
/* got an s. file -- see if the p. file exists */
strncpy(basefile
, &dir
.d_name
[2], sizeof dir
.d_name
- 2);
basefile
[sizeof dir
.d_name
- 2] = '\0';
** open and scan the p-file.
** 'gotpfent' tells if we have found a valid p-file
/* the file exists -- report it's contents */
while ((pf
= getpfent(pfp
)) != NULL
)
if (nobranch
&& isbranch(pf
->p_nsid
))
if (usernm
!= NULL
&& strcmp(usernm
, pf
->p_user
) != 0 && mode
!= CLEANC
)
printf("%s\n", basefile
);
printf("%12s: being edited: ", basefile
);
/* the s. file exists and no p. file exists -- unlink the g-file */
if (mode
== CLEANC
&& !gotpfent
)
strncpy(buf
, &dir
.d_name
[2], sizeof dir
.d_name
- 2);
buf
[sizeof dir
.d_name
- 2] = '\0';
/* cleanup & report results */
if (!gotedit
&& mode
== INFOC
)
printf("Nothing being edited");
printf(" by %s\n", usernm
);
** ISBRANCH -- is the SID a branch?
** sid -- the sid to check.
** TRUE if the sid represents a branch.
for (p
= sid
; *p
!= '\0'; p
++)
** UNEDIT -- unedit a file
** Checks to see that the current user is actually editting
** the file and arranges that s/he is not editting it.
** fn -- the name of the file to be unedited.
** TRUE -- if the file was successfully unedited.
** FALSE -- if the file was not unedited for some
** entries are removed from pfile.
static char tfn
[] = "/tmp/sccsXXXXX";
extern struct pfile
*getpfent();
/* make "s." filename & find the trailing component */
if (q
[1] != 's' || q
[2] != '.')
usrerr("bad file name \"%s\"", fn
);
/* turn "s." into "p." & try to open it */
printf("%12s: not being edited\n", fn
);
/* create temp file for editing p-file */
usrerr("cannot create \"%s\"", tfn
);
/* figure out who I am */
** Copy p-file to temp file, doing deletions as needed.
while ((pent
= getpfent(pfp
)) != NULL
)
if (strcmp(pent
->p_user
, myname
) == 0)
/* copy it back (perhaps it should be linked?) */
if (freopen(tfn
, "r", tfp
) == NULL
)
syserr("cannot reopen \"%s\"", tfn
);
if (freopen(pfn
, "w", pfp
) == NULL
)
usrerr("cannot create \"%s\"", pfn
);
while (fgets(buf
, sizeof buf
, tfp
) != NULL
)
/* it's empty -- remove it */
/* actually remove the g-file */
printf("%12s: removed\n", tail(fn
));
printf("%12s: not being edited by you\n", fn
);
** DODIFF -- diff an s-file against a g-file
** getv -- argv for the 'get' command.
** gfile -- name of the g-file to diff against.
for (ap
= getv
; *ap
!= NULL
; ap
++)
printf("sccs edit %s\n", gfile
);
printf("ed - %s << 'xxEOFxx'\n", gfile
);
printf("\n------- %s -------\n", gfile
);
/* create context for diff to run in */
syserr("dodiff: pipe failed");
syserr("dodiff: fork failed");
rval
= command(&getv
[1], TRUE
, "get:rcixt -s -k -p");
osig
= signal(SIGINT
, SIG_IGN
);
while (((i
= wait(&st
)) >= 0 && i
!= pid
) || errno
== EINTR
)
/* ignore result of diff */
if (close(pipev
[1]) < 0 || close(0) < 0 ||
dup(pipev
[0]) != 0 || close(pipev
[0]) < 0)
syserr("dodiff: magic failed");
command(&getv
[1], FALSE
, "-diff:elsfhbC");
printf("sccs delta %s\n", gfile
);
** TAIL -- return tail of filename.
** a pointer to the tail of the filename; e.g., given
** "cmd/ls.c", "ls.c" is returned.
for (p
= fn
; *p
!= 0; p
++)
if (*p
== '/' && p
[1] != '\0' && p
[1] != '/')
** GETPFENT -- get an entry from the p-file
** pfp -- p-file file pointer
** pointer to p-file struct for next entry
** Each call wipes out results of previous call.
extern char *nextfield();
if (fgets(buf
, sizeof buf
, pfp
) == NULL
)
ent
.p_nsid
= p
= nextfield(p
);
ent
.p_user
= p
= nextfield(p
);
ent
.p_date
= p
= nextfield(p
);
ent
.p_time
= p
= nextfield(p
);
ent
.p_aux
= p
= nextfield(p
);
if (p
== NULL
|| *p
== '\0')
while (*p
!= ' ' && *p
!= '\n' && *p
!= '\0')
if (*p
== '\n' || *p
== '\0')
** PUTPFENT -- output a p-file entry to a file
** pf -- the p-file entry
** f -- the file to put it on.
** pf is written onto file f.
register struct pfile
*pf
;
fprintf(f
, "%s %s %s %s %s", pf
->p_osid
, pf
->p_nsid
,
pf
->p_user
, pf
->p_date
, pf
->p_time
);
fprintf(f
, " %s", pf
->p_aux
);
** USRERR -- issue user-level error
** p1-p3 -- parameters to a printf.
fprintf(stderr
, "\n%s: ", MyName
);
fprintf(stderr
, f
, p1
, p2
, p3
);
** SYSERR -- print system-generated error.
** f -- format string to a printf.
** p1, p2, p3 -- parameters to f.
fprintf(stderr
, "\n%s SYSERR: ", MyName
);
fprintf(stderr
, f
, p1
, p2
, p3
);
** USERNAME -- return name of the current user
extern struct passwd
*getpwuid();
register struct passwd
*pw
;
syserr("who are you? (uid=%d)", getuid());
if (p
== NULL
|| p
[0] == '\0')