xref: /csrg-svn/usr.bin/sccs/sccs.c (revision 1618)
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*1618Seric static char SccsId[] = "@(#)sccs.c	1.43 10/25/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 
107*1618Seric # ifdef INGVAX
108*1618Seric # define PROGPATH(name)	"/usr/local/name"
109*1618Seric # endif INGVAX
110*1618Seric 
1111437Seric /* end of berkeley systems defines */
1121437Seric 
1131437Seric # ifndef SCCSPATH
1141432Seric # define SCCSPATH	"SCCS"	/* pathname in which to find s-files */
1151437Seric # endif NOT SCCSPATH
116828Seric 
1171437Seric # ifndef MYNAME
1181437Seric # define MYNAME		"sccs"	/* name used for printing errors */
1191437Seric # endif NOT MYNAME
1201270Seric 
1211432Seric # ifndef PROGPATH
1221432Seric # define PROGPATH(name)	"/usr/sccs/name"	/* place to find binaries */
1231432Seric # endif PROGPATH
1241432Seric 
1251270Seric /****************  End of Configuration Information  ****************/
1261432Seric 
127157Seric typedef char	bool;
128200Seric # define TRUE	1
129200Seric # define FALSE	0
130157Seric 
1311438Seric # define bitset(bit, word)	((bool) ((bit) & (word)))
1321438Seric 
133828Seric # ifdef UIDUSER
134828Seric # include <pwd.h>
135828Seric # endif UIDUSER
136828Seric 
137148Seric struct sccsprog
138148Seric {
139148Seric 	char	*sccsname;	/* name of SCCS routine */
140200Seric 	short	sccsoper;	/* opcode, see below */
141200Seric 	short	sccsflags;	/* flags, see below */
1421316Seric 	char	*sccsklets;	/* valid key-letters on macros */
143148Seric 	char	*sccspath;	/* pathname of binary implementing */
144148Seric };
145148Seric 
146200Seric /* values for sccsoper */
147200Seric # define PROG		0	/* call a program */
148201Seric # define CMACRO		1	/* command substitution macro */
149226Seric # define FIX		2	/* fix a delta */
150261Seric # define CLEAN		3	/* clean out recreatable files */
151396Seric # define UNEDIT		4	/* unedit a file */
1521431Seric # define SHELL		5	/* call a shell file (like PROG) */
1531433Seric # define DIFFS		6	/* diff between sccs & file out */
154200Seric 
155157Seric /* bits for sccsflags */
156200Seric # define NO_SDOT	0001	/* no s. on front of args */
157200Seric # define REALUSER	0002	/* protected (e.g., admin) */
158148Seric 
159819Seric /* modes for the "clean", "info", "check" ops */
160819Seric # define CLEANC		0	/* clean command */
161819Seric # define INFOC		1	/* info command */
162819Seric # define CHECKC		2	/* check command */
163819Seric 
1641432Seric /*
1651432Seric **  Description of commands known to this program.
1661432Seric **	First argument puts the command into a class.  Second arg is
1671432Seric **	info regarding treatment of this command.  Third arg is a
1681432Seric **	list of flags this command accepts from macros, etc.  Fourth
1691432Seric **	arg is the pathname of the implementing program, or the
1701432Seric **	macro definition, or the arg to a sub-algorithm.
1711432Seric */
172202Seric 
173148Seric struct sccsprog SccsProg[] =
174148Seric {
1751316Seric 	"admin",	PROG,	REALUSER,	"",		PROGPATH(admin),
1761316Seric 	"chghist",	PROG,	0,		"",		PROGPATH(rmdel),
1771316Seric 	"comb",		PROG,	0,		"",		PROGPATH(comb),
1781316Seric 	"delta",	PROG,	0,		"mysrp",	PROGPATH(delta),
1791317Seric 	"get",		PROG,	0,		"ixbeskcl",	PROGPATH(get),
1801316Seric 	"help",		PROG,	NO_SDOT,	"",		PROGPATH(help),
1811316Seric 	"prt",		PROG,	0,		"",		PROGPATH(prt),
1821505Seric 	"rmdel",	PROG,	REALUSER,	"r",		PROGPATH(rmdel),
1831316Seric 	"what",		PROG,	NO_SDOT,	"",		PROGPATH(what),
1841431Seric 	"sccsdiff",	SHELL,	REALUSER,	"",		PROGPATH(sccsdiff),
1851317Seric 	"edit",		CMACRO,	NO_SDOT,	"ixbscl",	"get -e",
1861316Seric 	"delget",	CMACRO,	NO_SDOT,	"",		"delta/get -t",
1871316Seric 	"deledit",	CMACRO,	NO_SDOT,	"",		"delta/get -e -t",
1881316Seric 	"fix",		FIX,	NO_SDOT,	"",		NULL,
1891316Seric 	"clean",	CLEAN,	REALUSER,	"",		(char *) CLEANC,
1901316Seric 	"info",		CLEAN,	REALUSER,	"",		(char *) INFOC,
1911316Seric 	"check",	CLEAN,	REALUSER,	"",		(char *) CHECKC,
1921316Seric 	"unedit",	UNEDIT,	NO_SDOT,	"",		NULL,
1931433Seric 	"diffs",	DIFFS,	NO_SDOT|REALUSER, "",		NULL,
1941316Seric 	NULL,		-1,	0,		"",		NULL
195148Seric };
196148Seric 
1971432Seric /* one line from a p-file */
198396Seric struct pfile
199396Seric {
200396Seric 	char	*p_osid;	/* old SID */
201396Seric 	char	*p_nsid;	/* new SID */
202396Seric 	char	*p_user;	/* user who did edit */
203396Seric 	char	*p_date;	/* date of get */
204396Seric 	char	*p_time;	/* time of get */
205396Seric };
206396Seric 
2071270Seric char	*SccsPath = SCCSPATH;	/* pathname of SCCS files */
2081270Seric # ifdef SCCSDIR
2091270Seric char	*SccsDir = SCCSDIR;	/* directory to begin search from */
2101205Seric # else
2111270Seric char	*SccsDir = "";
2121205Seric # endif
2131437Seric char	MyName[] = MYNAME;	/* name used in messages */
2141433Seric int	OutFile = -1;		/* override output file for commands */
215157Seric bool	RealUser;		/* if set, running as real user */
216393Seric # ifdef DEBUG
217393Seric bool	Debug;			/* turn on tracing */
218393Seric # endif
2191432Seric 
220148Seric main(argc, argv)
221148Seric 	int argc;
222148Seric 	char **argv;
223148Seric {
224148Seric 	register char *p;
225262Seric 	extern struct sccsprog *lookup();
2261282Seric 	register int i;
227148Seric 
228148Seric 	/*
229148Seric 	**  Detect and decode flags intended for this program.
230148Seric 	*/
231148Seric 
232200Seric 	if (argc < 2)
233148Seric 	{
2341205Seric 		fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName);
235200Seric 		exit(EX_USAGE);
236200Seric 	}
237200Seric 	argv[argc] = NULL;
238200Seric 
239262Seric 	if (lookup(argv[0]) == NULL)
240200Seric 	{
241262Seric 		while ((p = *++argv) != NULL)
242148Seric 		{
243262Seric 			if (*p != '-')
244262Seric 				break;
245262Seric 			switch (*++p)
246262Seric 			{
247262Seric 			  case 'r':		/* run as real user */
248262Seric 				setuid(getuid());
249262Seric 				RealUser++;
250262Seric 				break;
251148Seric 
2521270Seric # ifndef SCCSDIR
253262Seric 			  case 'p':		/* path of sccs files */
254262Seric 				SccsPath = ++p;
255262Seric 				break;
256148Seric 
257588Seric 			  case 'd':		/* directory to search from */
258588Seric 				SccsDir = ++p;
259588Seric 				break;
2601205Seric # endif
261588Seric 
262393Seric # ifdef DEBUG
263393Seric 			  case 'T':		/* trace */
264393Seric 				Debug++;
265393Seric 				break;
266393Seric # endif
267393Seric 
268262Seric 			  default:
2691205Seric 				usrerr("unknown option -%s", p);
270262Seric 				break;
271262Seric 			}
272148Seric 		}
273262Seric 		if (SccsPath[0] == '\0')
274262Seric 			SccsPath = ".";
275148Seric 	}
276148Seric 
2771316Seric 	i = command(argv, FALSE, FALSE, "");
2781282Seric 	exit(i);
279200Seric }
2801432Seric 
2811432Seric /*
2821282Seric **  COMMAND -- look up and perform a command
2831282Seric **
2841282Seric **	This routine is the guts of this program.  Given an
2851282Seric **	argument vector, it looks up the "command" (argv[0])
2861282Seric **	in the configuration table and does the necessary stuff.
2871282Seric **
2881282Seric **	Parameters:
2891282Seric **		argv -- an argument vector to process.
2901282Seric **		forkflag -- if set, fork before executing the command.
2911316Seric **		editflag -- if set, only include flags listed in the
2921316Seric **			sccsklets field of the command descriptor.
2931316Seric **		arg0 -- a space-seperated list of arguments to insert
2941316Seric **			before argv.
2951282Seric **
2961282Seric **	Returns:
2971282Seric **		zero -- command executed ok.
2981282Seric **		else -- error status.
2991282Seric **
3001282Seric **	Side Effects:
3011282Seric **		none.
3021282Seric */
303157Seric 
3041316Seric command(argv, forkflag, editflag, arg0)
305200Seric 	char **argv;
306201Seric 	bool forkflag;
3071316Seric 	bool editflag;
3081316Seric 	char *arg0;
309200Seric {
310200Seric 	register struct sccsprog *cmd;
311200Seric 	register char *p;
312201Seric 	char buf[40];
313262Seric 	extern struct sccsprog *lookup();
3141316Seric 	char *nav[1000];
3151316Seric 	char **np;
3161431Seric 	register char **ap;
317585Seric 	register int i;
3181431Seric 	register char *q;
319585Seric 	extern bool unedit();
3201282Seric 	int rval = 0;
3211316Seric 	extern char *index();
3221316Seric 	extern char *makefile();
3231435Seric 	extern char *tail();
324200Seric 
325393Seric # ifdef DEBUG
326393Seric 	if (Debug)
327393Seric 	{
3281316Seric 		printf("command:\n\t\"%s\"\n", arg0);
3291316Seric 		for (np = argv; *np != NULL; np++)
3301316Seric 			printf("\t\"%s\"\n", *np);
331393Seric 	}
332393Seric # endif
333393Seric 
334157Seric 	/*
3351316Seric 	**  Copy arguments.
3361438Seric 	**	Copy from arg0 & if necessary at most one arg
3371438Seric 	**	from argv[0].
3381316Seric 	*/
3391316Seric 
3401431Seric 	np = ap = &nav[1];
3411316Seric 	for (p = arg0, q = buf; *p != '\0' && *p != '/'; )
3421316Seric 	{
3431316Seric 		*np++ = q;
3441316Seric 		while (*p == ' ')
3451316Seric 			p++;
3461316Seric 		while (*p != ' ' && *p != '\0' && *p != '/')
3471316Seric 			*q++ = *p++;
3481316Seric 		*q++ = '\0';
3491316Seric 	}
3501316Seric 	*np = NULL;
3511431Seric 	if (*ap == NULL)
3521316Seric 		*np++ = *argv++;
3531316Seric 
3541316Seric 	/*
355148Seric 	**  Look up command.
3561431Seric 	**	At this point, *ap is the command name.
357148Seric 	*/
358148Seric 
3591431Seric 	cmd = lookup(*ap);
360262Seric 	if (cmd == NULL)
361148Seric 	{
3621431Seric 		usrerr("Unknown command \"%s\"", *ap);
3631282Seric 		return (EX_USAGE);
364148Seric 	}
365148Seric 
366148Seric 	/*
3671316Seric 	**  Copy remaining arguments doing editing as appropriate.
3681316Seric 	*/
3691316Seric 
3701316Seric 	for (; *argv != NULL; argv++)
3711316Seric 	{
3721316Seric 		p = *argv;
3731316Seric 		if (*p == '-')
3741316Seric 		{
3751316Seric 			if (p[1] == '\0' || !editflag || cmd->sccsklets == NULL ||
3761316Seric 			    index(cmd->sccsklets, p[1]) != NULL)
3771316Seric 				*np++ = p;
3781316Seric 		}
3791316Seric 		else
3801316Seric 		{
3811316Seric 			if (!bitset(NO_SDOT, cmd->sccsflags))
3821316Seric 				p = makefile(p);
3831316Seric 			if (p != NULL)
3841316Seric 				*np++ = p;
3851316Seric 		}
3861316Seric 	}
3871316Seric 	*np = NULL;
3881316Seric 
3891316Seric 	/*
390200Seric 	**  Interpret operation associated with this command.
391157Seric 	*/
392157Seric 
393200Seric 	switch (cmd->sccsoper)
394200Seric 	{
3951431Seric 	  case SHELL:		/* call a shell file */
3961431Seric 		*ap = cmd->sccspath;
3971431Seric 		*--ap = "sh";
3981431Seric 		rval = callprog("/bin/sh", cmd->sccsflags, ap, forkflag);
3991431Seric 		break;
4001431Seric 
401200Seric 	  case PROG:		/* call an sccs prog */
4021431Seric 		rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag);
403201Seric 		break;
404201Seric 
405201Seric 	  case CMACRO:		/* command macro */
4061438Seric 		/* step through & execute each part of the macro */
407201Seric 		for (p = cmd->sccspath; *p != '\0'; p++)
408201Seric 		{
4091316Seric 			q = p;
4101316Seric 			while (*p != '\0' && *p != '/')
4111316Seric 				p++;
4121431Seric 			rval = command(&ap[1], *p != '\0', TRUE, q);
4131282Seric 			if (rval != 0)
4141282Seric 				break;
415201Seric 		}
4161282Seric 		break;
417157Seric 
418226Seric 	  case FIX:		/* fix a delta */
4191431Seric 		if (strncmp(ap[1], "-r", 2) != 0)
420226Seric 		{
4211205Seric 			usrerr("-r flag needed for fix command");
4221282Seric 			rval = EX_USAGE;
423226Seric 			break;
424226Seric 		}
4251438Seric 
4261438Seric 		/* get the version with all changes */
4271431Seric 		rval = command(&ap[1], TRUE, TRUE, "get -k");
4281438Seric 
4291438Seric 		/* now remove that version from the s-file */
4301282Seric 		if (rval == 0)
4311431Seric 			rval = command(&ap[1], TRUE, TRUE, "rmdel");
4321438Seric 
4331438Seric 		/* and edit the old version (but don't clobber new vers) */
4341282Seric 		if (rval == 0)
4351431Seric 			rval = command(&ap[2], FALSE, TRUE, "get -e -g");
4361282Seric 		break;
437226Seric 
438261Seric 	  case CLEAN:
4391282Seric 		rval = clean((int) cmd->sccspath);
440261Seric 		break;
441261Seric 
442396Seric 	  case UNEDIT:
4431431Seric 		for (argv = np = &ap[1]; *argv != NULL; argv++)
444585Seric 		{
4451316Seric 			if (unedit(*argv))
4461316Seric 				*np++ = *argv;
447585Seric 		}
4481316Seric 		*np = NULL;
4491438Seric 
4501438Seric 		/* get all the files that we unedited successfully */
451585Seric 		if (i > 0)
4521431Seric 			rval = command(&ap[1], FALSE, FALSE, "get");
453396Seric 		break;
454396Seric 
4551433Seric 	  case DIFFS:		/* diff between s-file & edit file */
4561433Seric 		/* find the end of the flag arguments */
4571433Seric 		for (np = &ap[1]; *np != NULL && **np == '-'; np++)
4581433Seric 			continue;
4591433Seric 		argv = np;
4601433Seric 
4611433Seric 		/* for each file, do the diff */
4621502Seric 		p = argv[1];
4631433Seric 		while (*np != NULL)
4641433Seric 		{
4651438Seric 			/* messy, but we need a null terminated argv */
4661433Seric 			*argv = *np++;
4671502Seric 			argv[1] = NULL;
4681435Seric 			i = dodiff(ap, tail(*argv));
4691433Seric 			if (rval == 0)
4701433Seric 				rval = i;
4711502Seric 			argv[1] = p;
4721433Seric 		}
4731433Seric 		break;
4741433Seric 
475200Seric 	  default:
4761205Seric 		syserr("oper %d", cmd->sccsoper);
477200Seric 		exit(EX_SOFTWARE);
478200Seric 	}
4791282Seric # ifdef DEBUG
4801282Seric 	if (Debug)
4811282Seric 		printf("command: rval=%d\n", rval);
4821282Seric # endif
4831282Seric 	return (rval);
484200Seric }
4851432Seric 
4861432Seric /*
487262Seric **  LOOKUP -- look up an SCCS command name.
488262Seric **
489262Seric **	Parameters:
490262Seric **		name -- the name of the command to look up.
491262Seric **
492262Seric **	Returns:
493262Seric **		ptr to command descriptor for this command.
494262Seric **		NULL if no such entry.
495262Seric **
496262Seric **	Side Effects:
497262Seric **		none.
498262Seric */
499200Seric 
500262Seric struct sccsprog *
501262Seric lookup(name)
502262Seric 	char *name;
503262Seric {
504262Seric 	register struct sccsprog *cmd;
505226Seric 
506262Seric 	for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
507262Seric 	{
508262Seric 		if (strcmp(cmd->sccsname, name) == 0)
509262Seric 			return (cmd);
510262Seric 	}
511262Seric 	return (NULL);
512262Seric }
5131432Seric 
5141432Seric /*
5151282Seric **  CALLPROG -- call a program
5161282Seric **
5171316Seric **	Used to call the SCCS programs.
5181282Seric **
5191282Seric **	Parameters:
5201282Seric **		progpath -- pathname of the program to call.
5211282Seric **		flags -- status flags from the command descriptors.
5221282Seric **		argv -- an argument vector to pass to the program.
5231282Seric **		forkflag -- if true, fork before calling, else just
5241282Seric **			exec.
5251282Seric **
5261282Seric **	Returns:
5271282Seric **		The exit status of the program.
5281282Seric **		Nothing if forkflag == FALSE.
5291282Seric **
5301282Seric **	Side Effects:
5311282Seric **		Can exit if forkflag == FALSE.
5321282Seric */
533226Seric 
534200Seric callprog(progpath, flags, argv, forkflag)
535200Seric 	char *progpath;
536200Seric 	short flags;
537200Seric 	char **argv;
538200Seric 	bool forkflag;
539200Seric {
540200Seric 	register int i;
541201Seric 	auto int st;
542200Seric 
5431316Seric # ifdef DEBUG
5441316Seric 	if (Debug)
5451316Seric 	{
5461316Seric 		printf("callprog:\n");
5471316Seric 		for (i = 0; argv[i] != NULL; i++)
5481316Seric 			printf("\t\"%s\"\n", argv[i]);
5491316Seric 	}
5501316Seric # endif
5511316Seric 
552200Seric 	if (*argv == NULL)
553200Seric 		return (-1);
554200Seric 
555157Seric 	/*
556226Seric 	**  Fork if appropriate.
557148Seric 	*/
558148Seric 
559200Seric 	if (forkflag)
560200Seric 	{
561393Seric # ifdef DEBUG
562393Seric 		if (Debug)
563393Seric 			printf("Forking\n");
564393Seric # endif
565200Seric 		i = fork();
566200Seric 		if (i < 0)
567200Seric 		{
5681205Seric 			syserr("cannot fork");
569200Seric 			exit(EX_OSERR);
570200Seric 		}
571200Seric 		else if (i > 0)
572201Seric 		{
573201Seric 			wait(&st);
5741282Seric 			if ((st & 0377) == 0)
5751282Seric 				st = (st >> 8) & 0377;
5761433Seric 			if (OutFile >= 0)
5771433Seric 			{
5781433Seric 				close(OutFile);
5791433Seric 				OutFile = -1;
5801433Seric 			}
581201Seric 			return (st);
582201Seric 		}
583200Seric 	}
5841433Seric 	else if (OutFile >= 0)
5851433Seric 	{
5861433Seric 		syserr("callprog: setting stdout w/o forking");
5871433Seric 		exit(EX_SOFTWARE);
5881433Seric 	}
589200Seric 
5901433Seric 	/* set protection as appropriate */
591200Seric 	if (bitset(REALUSER, flags))
592200Seric 		setuid(getuid());
5931433Seric 
5941433Seric 	/* change standard input & output if needed */
5951433Seric 	if (OutFile >= 0)
5961433Seric 	{
5971433Seric 		close(1);
5981433Seric 		dup(OutFile);
5991433Seric 		close(OutFile);
6001433Seric 	}
601226Seric 
6021433Seric 	/* call real SCCS program */
603226Seric 	execv(progpath, argv);
6041205Seric 	syserr("cannot execute %s", progpath);
605148Seric 	exit(EX_UNAVAILABLE);
606148Seric }
6071432Seric 
6081432Seric /*
609586Seric **  MAKEFILE -- make filename of SCCS file
610586Seric **
611586Seric **	If the name passed is already the name of an SCCS file,
612586Seric **	just return it.  Otherwise, munge the name into the name
613586Seric **	of the actual SCCS file.
614586Seric **
615586Seric **	There are cases when it is not clear what you want to
616586Seric **	do.  For example, if SccsPath is an absolute pathname
617586Seric **	and the name given is also an absolute pathname, we go
618586Seric **	for SccsPath (& only use the last component of the name
619586Seric **	passed) -- this is important for security reasons (if
620586Seric **	sccs is being used as a setuid front end), but not
621586Seric **	particularly intuitive.
622586Seric **
623586Seric **	Parameters:
624586Seric **		name -- the file name to be munged.
625586Seric **
626586Seric **	Returns:
627586Seric **		The pathname of the sccs file.
628586Seric **		NULL on error.
629586Seric **
630586Seric **	Side Effects:
631586Seric **		none.
632586Seric */
633148Seric 
634148Seric char *
635148Seric makefile(name)
636148Seric 	char *name;
637148Seric {
638148Seric 	register char *p;
639148Seric 	register char c;
640148Seric 	char buf[512];
641588Seric 	struct stat stbuf;
642148Seric 	extern char *malloc();
643586Seric 	extern char *rindex();
644588Seric 	extern bool safepath();
645587Seric 	extern bool isdir();
646587Seric 	register char *q;
647148Seric 
648586Seric 	p = rindex(name, '/');
649586Seric 	if (p == NULL)
650586Seric 		p = name;
651586Seric 	else
652586Seric 		p++;
653586Seric 
654148Seric 	/*
655588Seric 	**  Check to see that the path is "safe", i.e., that we
656588Seric 	**  are not letting some nasty person use the setuid part
657588Seric 	**  of this program to look at or munge some presumably
658588Seric 	**  hidden files.
659148Seric 	*/
660148Seric 
661588Seric 	if (SccsDir[0] == '/' && !safepath(name))
662588Seric 		return (NULL);
663586Seric 
664586Seric 	/*
665588Seric 	**  Create the base pathname.
666586Seric 	*/
667586Seric 
6681438Seric 	/* first the directory part */
669588Seric 	if (SccsDir[0] != '\0' && name[0] != '/' && strncmp(name, "./", 2) != 0)
670148Seric 	{
671588Seric 		strcpy(buf, SccsDir);
672586Seric 		strcat(buf, "/");
673586Seric 	}
674586Seric 	else
675586Seric 		strcpy(buf, "");
6761438Seric 
6771438Seric 	/* then the head of the pathname */
678587Seric 	strncat(buf, name, p - name);
679587Seric 	q = &buf[strlen(buf)];
6801438Seric 
6811438Seric 	/* now copy the final part of the name, in case useful */
682587Seric 	strcpy(q, p);
6831438Seric 
6841438Seric 	/* so is it useful? */
685587Seric 	if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
686586Seric 	{
6871438Seric 		/* sorry, no; copy the SCCS pathname & the "s." */
688588Seric 		strcpy(q, SccsPath);
689588Seric 		strcat(buf, "/s.");
6901438Seric 
6911438Seric 		/* and now the end of the name */
692586Seric 		strcat(buf, p);
693586Seric 	}
694148Seric 
6951438Seric 	/* if i haven't changed it, why did I do all this? */
696588Seric 	if (strcmp(buf, name) == 0)
697588Seric 		p = name;
698588Seric 	else
699148Seric 	{
7001438Seric 		/* but if I have, squirrel it away */
701588Seric 		p = malloc(strlen(buf) + 1);
702588Seric 		if (p == NULL)
703588Seric 		{
704588Seric 			perror("Sccs: no mem");
705588Seric 			exit(EX_OSERR);
706588Seric 		}
707588Seric 		strcpy(p, buf);
708148Seric 	}
7091438Seric 
710148Seric 	return (p);
711148Seric }
7121432Seric 
7131432Seric /*
714587Seric **  ISDIR -- return true if the argument is a directory.
715587Seric **
716587Seric **	Parameters:
717587Seric **		name -- the pathname of the file to check.
718587Seric **
719587Seric **	Returns:
720587Seric **		TRUE if 'name' is a directory, FALSE otherwise.
721587Seric **
722587Seric **	Side Effects:
723587Seric **		none.
724587Seric */
725587Seric 
726587Seric bool
727587Seric isdir(name)
728587Seric 	char *name;
729587Seric {
730587Seric 	struct stat stbuf;
731587Seric 
732587Seric 	return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
733587Seric }
7341432Seric 
7351432Seric /*
736586Seric **  SAFEPATH -- determine whether a pathname is "safe"
737586Seric **
738586Seric **	"Safe" pathnames only allow you to get deeper into the
739586Seric **	directory structure, i.e., full pathnames and ".." are
740586Seric **	not allowed.
741586Seric **
742586Seric **	Parameters:
743586Seric **		p -- the name to check.
744586Seric **
745586Seric **	Returns:
746586Seric **		TRUE -- if the path is safe.
747586Seric **		FALSE -- if the path is not safe.
748586Seric **
749586Seric **	Side Effects:
750586Seric **		Prints a message if the path is not safe.
751586Seric */
752586Seric 
753586Seric bool
754586Seric safepath(p)
755586Seric 	register char *p;
756586Seric {
757586Seric 	extern char *index();
758586Seric 
759586Seric 	if (*p != '/')
760586Seric 	{
761586Seric 		while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
762586Seric 		{
763586Seric 			p = index(p, '/');
764586Seric 			if (p == NULL)
765586Seric 				return (TRUE);
766586Seric 			p++;
767586Seric 		}
768586Seric 	}
769586Seric 
770586Seric 	printf("You may not use full pathnames or \"..\"\n");
771586Seric 	return (FALSE);
772586Seric }
7731432Seric 
7741432Seric /*
775261Seric **  CLEAN -- clean out recreatable files
776261Seric **
777261Seric **	Any file for which an "s." file exists but no "p." file
778261Seric **	exists in the current directory is purged.
779261Seric **
780261Seric **	Parameters:
781819Seric **		tells whether this came from a "clean", "info", or
782819Seric **		"check" command.
783261Seric **
784261Seric **	Returns:
785261Seric **		none.
786261Seric **
787261Seric **	Side Effects:
788819Seric **		Removes files in the current directory.
789819Seric **		Prints information regarding files being edited.
790819Seric **		Exits if a "check" command.
791261Seric */
792261Seric 
793819Seric clean(mode)
794819Seric 	int mode;
795261Seric {
796261Seric 	struct direct dir;
797261Seric 	struct stat stbuf;
798261Seric 	char buf[100];
799394Seric 	char pline[120];
800346Seric 	register FILE *dirfd;
801346Seric 	register char *basefile;
802351Seric 	bool gotedit;
803394Seric 	FILE *pfp;
804261Seric 
8051438Seric 	/*
8061438Seric 	**  Find and open the SCCS directory.
8071438Seric 	*/
8081438Seric 
8091207Seric 	strcpy(buf, SccsDir);
8101207Seric 	if (buf[0] != '\0')
8111207Seric 		strcat(buf, "/");
8121207Seric 	strcat(buf, SccsPath);
8131438Seric 
8141207Seric 	dirfd = fopen(buf, "r");
815261Seric 	if (dirfd == NULL)
816261Seric 	{
8171207Seric 		usrerr("cannot open %s", buf);
8181282Seric 		return (EX_NOINPUT);
819261Seric 	}
820261Seric 
821261Seric 	/*
822261Seric 	**  Scan the SCCS directory looking for s. files.
8231438Seric 	**	gotedit tells whether we have tried to clean any
8241438Seric 	**		files that are being edited.
825261Seric 	*/
826261Seric 
827351Seric 	gotedit = FALSE;
828261Seric 	while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
829261Seric 	{
830568Seric 		if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
831261Seric 			continue;
832261Seric 
833261Seric 		/* got an s. file -- see if the p. file exists */
8341207Seric 		strcpy(buf, SccsDir);
8351207Seric 		if (buf[0] != '\0')
8361207Seric 			strcat(buf, "/");
8371207Seric 		strcat(buf, SccsPath);
838261Seric 		strcat(buf, "/p.");
839346Seric 		basefile = &buf[strlen(buf)];
840568Seric 		strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
841346Seric 		basefile[sizeof dir.d_name - 2] = '\0';
842394Seric 		pfp = fopen(buf, "r");
843394Seric 		if (pfp != NULL)
844346Seric 		{
8451438Seric 			/* the file exists -- report it's contents */
846394Seric 			while (fgets(pline, sizeof pline, pfp) != NULL)
847416Seric 				printf("%12s: being edited: %s", basefile, pline);
848394Seric 			fclose(pfp);
849351Seric 			gotedit = TRUE;
850261Seric 			continue;
851346Seric 		}
852261Seric 
853261Seric 		/* the s. file exists and no p. file exists -- unlink the g-file */
854819Seric 		if (mode == CLEANC)
855346Seric 		{
856568Seric 			strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
857346Seric 			buf[sizeof dir.d_name - 2] = '\0';
858346Seric 			unlink(buf);
859346Seric 		}
860261Seric 	}
861261Seric 
8621438Seric 	/* cleanup & report results */
863261Seric 	fclose(dirfd);
864819Seric 	if (!gotedit && mode == INFOC)
865416Seric 		printf("Nothing being edited\n");
866819Seric 	if (mode == CHECKC)
867819Seric 		exit(gotedit);
8681282Seric 	return (EX_OK);
869261Seric }
8701432Seric 
8711432Seric /*
872396Seric **  UNEDIT -- unedit a file
873396Seric **
874396Seric **	Checks to see that the current user is actually editting
875396Seric **	the file and arranges that s/he is not editting it.
876396Seric **
877396Seric **	Parameters:
878416Seric **		fn -- the name of the file to be unedited.
879396Seric **
880396Seric **	Returns:
881585Seric **		TRUE -- if the file was successfully unedited.
882585Seric **		FALSE -- if the file was not unedited for some
883585Seric **			reason.
884396Seric **
885396Seric **	Side Effects:
886396Seric **		fn is removed
887396Seric **		entries are removed from pfile.
888396Seric */
889396Seric 
890585Seric bool
891396Seric unedit(fn)
892396Seric 	char *fn;
893396Seric {
894396Seric 	register FILE *pfp;
895396Seric 	char *pfn;
896396Seric 	static char tfn[] = "/tmp/sccsXXXXX";
897396Seric 	FILE *tfp;
898396Seric 	register char *p;
899396Seric 	register char *q;
900396Seric 	bool delete = FALSE;
901396Seric 	bool others = FALSE;
902396Seric 	char *myname;
903396Seric 	extern char *getlogin();
904396Seric 	struct pfile *pent;
905396Seric 	extern struct pfile *getpfile();
906396Seric 	char buf[120];
9071316Seric 	extern char *makefile();
908828Seric # ifdef UIDUSER
909828Seric 	struct passwd *pw;
910828Seric 	extern struct passwd *getpwuid();
911828Seric # endif UIDUSER
912396Seric 
913396Seric 	/* make "s." filename & find the trailing component */
914396Seric 	pfn = makefile(fn);
915586Seric 	if (pfn == NULL)
916586Seric 		return (FALSE);
917586Seric 	q = rindex(pfn, '/');
918586Seric 	if (q == NULL)
919586Seric 		q = &pfn[-1];
920586Seric 	if (q[1] != 's' || q[2] != '.')
921396Seric 	{
9221205Seric 		usrerr("bad file name \"%s\"", fn);
923585Seric 		return (FALSE);
924396Seric 	}
925396Seric 
9261438Seric 	/* turn "s." into "p." & try to open it */
927396Seric 	*++q = 'p';
928396Seric 
929396Seric 	pfp = fopen(pfn, "r");
930396Seric 	if (pfp == NULL)
931396Seric 	{
932416Seric 		printf("%12s: not being edited\n", fn);
933585Seric 		return (FALSE);
934396Seric 	}
935396Seric 
9361438Seric 	/* create temp file for editing p-file */
937396Seric 	mktemp(tfn);
938396Seric 	tfp = fopen(tfn, "w");
939396Seric 	if (tfp == NULL)
940396Seric 	{
9411205Seric 		usrerr("cannot create \"%s\"", tfn);
942396Seric 		exit(EX_OSERR);
943396Seric 	}
944396Seric 
9451438Seric 	/* figure out who I am */
946828Seric # ifdef UIDUSER
947828Seric 	pw = getpwuid(getuid());
948828Seric 	if (pw == NULL)
949828Seric 	{
9501205Seric 		syserr("who are you? (uid=%d)", getuid());
951828Seric 		exit(EX_OSERR);
952828Seric 	}
953828Seric 	myname = pw->pw_name;
954828Seric # else
955396Seric 	myname = getlogin();
956828Seric # endif UIDUSER
9571438Seric 
9581438Seric 	/*
9591438Seric 	**  Copy p-file to temp file, doing deletions as needed.
9601438Seric 	*/
9611438Seric 
962396Seric 	while ((pent = getpfile(pfp)) != NULL)
963396Seric 	{
964396Seric 		if (strcmp(pent->p_user, myname) == 0)
965396Seric 		{
966396Seric 			/* a match */
967396Seric 			delete++;
968396Seric 		}
969396Seric 		else
970396Seric 		{
9711438Seric 			/* output it again */
972396Seric 			fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
973396Seric 			    pent->p_nsid, pent->p_user, pent->p_date,
974396Seric 			    pent->p_time);
975396Seric 			others++;
976396Seric 		}
977396Seric 	}
978396Seric 
979396Seric 	/* do final cleanup */
980396Seric 	if (others)
981396Seric 	{
9821438Seric 		/* copy it back (perhaps it should be linked?) */
983396Seric 		if (freopen(tfn, "r", tfp) == NULL)
984396Seric 		{
9851205Seric 			syserr("cannot reopen \"%s\"", tfn);
986396Seric 			exit(EX_OSERR);
987396Seric 		}
988396Seric 		if (freopen(pfn, "w", pfp) == NULL)
989396Seric 		{
9901205Seric 			usrerr("cannot create \"%s\"", pfn);
991585Seric 			return (FALSE);
992396Seric 		}
993396Seric 		while (fgets(buf, sizeof buf, tfp) != NULL)
994396Seric 			fputs(buf, pfp);
995396Seric 	}
996396Seric 	else
997396Seric 	{
9981438Seric 		/* it's empty -- remove it */
999396Seric 		unlink(pfn);
1000396Seric 	}
1001396Seric 	fclose(tfp);
1002396Seric 	fclose(pfp);
1003396Seric 	unlink(tfn);
1004396Seric 
10051438Seric 	/* actually remove the g-file */
1006396Seric 	if (delete)
1007396Seric 	{
10081435Seric 		unlink(tail(fn));
10091435Seric 		printf("%12s: removed\n", tail(fn));
1010585Seric 		return (TRUE);
1011396Seric 	}
1012396Seric 	else
1013396Seric 	{
1014416Seric 		printf("%12s: not being edited by you\n", fn);
1015585Seric 		return (FALSE);
1016396Seric 	}
1017396Seric }
10181432Seric 
10191432Seric /*
10201433Seric **  DODIFF -- diff an s-file against a g-file
10211433Seric **
10221433Seric **	Parameters:
10231433Seric **		getv -- argv for the 'get' command.
10241433Seric **		gfile -- name of the g-file to diff against.
10251433Seric **
10261433Seric **	Returns:
10271433Seric **		Result of get.
10281433Seric **
10291433Seric **	Side Effects:
10301433Seric **		none.
10311433Seric */
10321433Seric 
10331433Seric dodiff(getv, gfile)
10341433Seric 	char **getv;
10351433Seric 	char *gfile;
10361433Seric {
10371433Seric 	int pipev[2];
10381433Seric 	int rval;
10391433Seric 	register int i;
10401433Seric 	register int pid;
10411433Seric 	auto int st;
10421433Seric 	extern int errno;
10431433Seric 	int (*osig)();
10441433Seric 
10451501Seric 	printf("\n------- %s -------\n", gfile);
10461501Seric 
10471438Seric 	/* create context for diff to run in */
10481433Seric 	if (pipe(pipev) < 0)
10491433Seric 	{
10501433Seric 		syserr("dodiff: pipe failed");
10511433Seric 		exit(EX_OSERR);
10521433Seric 	}
10531433Seric 	if ((pid = fork()) < 0)
10541433Seric 	{
10551433Seric 		syserr("dodiff: fork failed");
10561433Seric 		exit(EX_OSERR);
10571433Seric 	}
10581433Seric 	else if (pid > 0)
10591433Seric 	{
10601433Seric 		/* in parent; run get */
10611433Seric 		OutFile = pipev[1];
10621433Seric 		close(pipev[0]);
10631433Seric 		rval = command(&getv[1], TRUE, FALSE, "get -s -k -p");
10641433Seric 		osig = signal(SIGINT, SIG_IGN);
10651433Seric 		while (((i = wait(&st)) >= 0 && i != pid) || errno == EINTR)
10661433Seric 			errno = 0;
10671433Seric 		signal(SIGINT, osig);
10681433Seric 		/* ignore result of diff */
10691433Seric 	}
10701433Seric 	else
10711433Seric 	{
10721433Seric 		/* in child, run diff */
10731433Seric 		if (close(pipev[1]) < 0 || close(0) < 0 ||
10741433Seric 		    dup(pipev[0]) != 0 || close(pipev[0]) < 0)
10751433Seric 		{
10761433Seric 			syserr("dodiff: magic failed");
10771433Seric 			exit(EX_OSERR);
10781433Seric 		}
10791433Seric 		execl(PROGPATH(bdiff), "bdiff", "-", gfile, NULL);
10801433Seric # ifndef V6
10811433Seric 		execlp("bdiff", "bdiff", "-", gfile, NULL);
10821433Seric 		execlp("diff", "diff", "-", gfile, NULL);
10831433Seric # endif NOT V6
10841433Seric 		syserr("bdiff: cannot execute");
10851433Seric 		exit(EX_OSERR);
10861433Seric 	}
10871433Seric 	return (rval);
10881433Seric }
10891433Seric 
10901433Seric /*
10911435Seric **  TAIL -- return tail of filename.
10921435Seric **
10931435Seric **	Parameters:
10941435Seric **		fn -- the filename.
10951435Seric **
10961435Seric **	Returns:
10971435Seric **		a pointer to the tail of the filename; e.g., given
10981435Seric **		"cmd/ls.c", "ls.c" is returned.
10991435Seric **
11001435Seric **	Side Effects:
11011435Seric **		none.
11021435Seric */
11031435Seric 
11041435Seric char *
11051435Seric tail(fn)
11061435Seric 	register char *fn;
11071435Seric {
11081435Seric 	register char *p;
11091435Seric 
11101435Seric 	for (p = fn; *p != 0; p++)
11111435Seric 		if (*p == '/' && p[1] != '\0' && p[1] != '/')
11121435Seric 			fn = &p[1];
11131435Seric 	return (fn);
11141435Seric }
11151435Seric 
11161435Seric /*
1117396Seric **  GETPFILE -- get an entry from the p-file
1118396Seric **
1119396Seric **	Parameters:
1120396Seric **		pfp -- p-file file pointer
1121396Seric **
1122396Seric **	Returns:
1123396Seric **		pointer to p-file struct for next entry
1124396Seric **		NULL on EOF or error
1125396Seric **
1126396Seric **	Side Effects:
1127396Seric **		Each call wipes out results of previous call.
1128396Seric */
1129396Seric 
1130396Seric struct pfile *
1131396Seric getpfile(pfp)
1132396Seric 	FILE *pfp;
1133396Seric {
1134396Seric 	static struct pfile ent;
1135396Seric 	static char buf[120];
1136396Seric 	register char *p;
1137396Seric 	extern char *nextfield();
1138396Seric 
1139396Seric 	if (fgets(buf, sizeof buf, pfp) == NULL)
1140396Seric 		return (NULL);
1141396Seric 
1142396Seric 	ent.p_osid = p = buf;
1143396Seric 	ent.p_nsid = p = nextfield(p);
1144396Seric 	ent.p_user = p = nextfield(p);
1145396Seric 	ent.p_date = p = nextfield(p);
1146396Seric 	ent.p_time = p = nextfield(p);
1147396Seric 	if (p == NULL || nextfield(p) != NULL)
1148396Seric 		return (NULL);
1149396Seric 
1150396Seric 	return (&ent);
1151396Seric }
1152396Seric 
1153396Seric 
1154396Seric char *
1155396Seric nextfield(p)
1156396Seric 	register char *p;
1157396Seric {
1158396Seric 	if (p == NULL || *p == '\0')
1159396Seric 		return (NULL);
1160396Seric 	while (*p != ' ' && *p != '\n' && *p != '\0')
1161396Seric 		p++;
1162396Seric 	if (*p == '\n' || *p == '\0')
1163396Seric 	{
1164396Seric 		*p = '\0';
1165396Seric 		return (NULL);
1166396Seric 	}
1167396Seric 	*p++ = '\0';
1168396Seric 	return (p);
1169396Seric }
11701432Seric 
11711432Seric /*
11721205Seric **  USRERR -- issue user-level error
11731205Seric **
11741205Seric **	Parameters:
11751205Seric **		f -- format string.
11761205Seric **		p1-p3 -- parameters to a printf.
11771205Seric **
11781205Seric **	Returns:
11791205Seric **		-1
11801205Seric **
11811205Seric **	Side Effects:
11821205Seric **		none.
11831205Seric */
11841205Seric 
11851205Seric usrerr(f, p1, p2, p3)
11861205Seric 	char *f;
11871205Seric {
11881205Seric 	fprintf(stderr, "\n%s: ", MyName);
11891205Seric 	fprintf(stderr, f, p1, p2, p3);
11901205Seric 	fprintf(stderr, "\n");
11911205Seric 
11921205Seric 	return (-1);
11931205Seric }
11941432Seric 
11951432Seric /*
11961205Seric **  SYSERR -- print system-generated error.
11971205Seric **
11981205Seric **	Parameters:
11991205Seric **		f -- format string to a printf.
12001205Seric **		p1, p2, p3 -- parameters to f.
12011205Seric **
12021205Seric **	Returns:
12031205Seric **		never.
12041205Seric **
12051205Seric **	Side Effects:
12061205Seric **		none.
12071205Seric */
12081205Seric 
12091205Seric syserr(f, p1, p2, p3)
12101205Seric 	char *f;
12111205Seric {
12121205Seric 	extern int errno;
12131205Seric 
12141205Seric 	fprintf(stderr, "\n%s SYSERR: ", MyName);
12151205Seric 	fprintf(stderr, f, p1, p2, p3);
12161205Seric 	fprintf(stderr, "\n");
12171205Seric 	if (errno == 0)
12181205Seric 		exit(EX_SOFTWARE);
12191205Seric 	else
12201205Seric 	{
12211205Seric 		perror(0);
12221205Seric 		exit(EX_OSERR);
12231205Seric 	}
12241205Seric }
1225