xref: /csrg-svn/usr.bin/sccs/sccs.c (revision 1502)
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