xref: /csrg-svn/usr.bin/sccs/sccs.c (revision 1435)
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.
82828Seric **
83828Seric **	Compilation Instructions:
84828Seric **		cc -O -n -s sccs.c
85828Seric **
86828Seric **	Author:
87828Seric **		Eric Allman, UCB/INGRES
881270Seric **		Copyright 1980 Regents of the University of California
89828Seric */
90155Seric 
91*1435Seric static char SccsId[] = "@(#)sccs.c	1.37 10/15/80";
921432Seric 
931270Seric /*******************  Configuration Information  ********************/
941270Seric 
95828Seric # ifdef CSVAX
96828Seric # define UIDUSER
971270Seric # define PROGPATH(name)	"/usr/local/name"
981270Seric # endif CSVAX
99828Seric 
1001432Seric # define SCCSPATH	"SCCS"	/* pathname in which to find s-files */
1011270Seric /* put #define SCCSDIR here */
102828Seric 
1031270Seric char	MyName[] = "sccs";	/* name used in messages */
1041270Seric 
1051432Seric # ifndef PROGPATH
1061432Seric # define PROGPATH(name)	"/usr/sccs/name"	/* place to find binaries */
1071432Seric # endif PROGPATH
1081432Seric 
1091270Seric /****************  End of Configuration Information  ****************/
1101432Seric 
111157Seric # define bitset(bit, word)	((bit) & (word))
112157Seric 
113157Seric typedef char	bool;
114200Seric # define TRUE	1
115200Seric # define FALSE	0
116157Seric 
117828Seric # ifdef UIDUSER
118828Seric # include <pwd.h>
119828Seric # endif UIDUSER
120828Seric 
121148Seric struct sccsprog
122148Seric {
123148Seric 	char	*sccsname;	/* name of SCCS routine */
124200Seric 	short	sccsoper;	/* opcode, see below */
125200Seric 	short	sccsflags;	/* flags, see below */
1261316Seric 	char	*sccsklets;	/* valid key-letters on macros */
127148Seric 	char	*sccspath;	/* pathname of binary implementing */
128148Seric };
129148Seric 
130200Seric /* values for sccsoper */
131200Seric # define PROG		0	/* call a program */
132201Seric # define CMACRO		1	/* command substitution macro */
133226Seric # define FIX		2	/* fix a delta */
134261Seric # define CLEAN		3	/* clean out recreatable files */
135396Seric # define UNEDIT		4	/* unedit a file */
1361431Seric # define SHELL		5	/* call a shell file (like PROG) */
1371433Seric # define DIFFS		6	/* diff between sccs & file out */
138200Seric 
139157Seric /* bits for sccsflags */
140200Seric # define NO_SDOT	0001	/* no s. on front of args */
141200Seric # define REALUSER	0002	/* protected (e.g., admin) */
142148Seric 
143819Seric /* modes for the "clean", "info", "check" ops */
144819Seric # define CLEANC		0	/* clean command */
145819Seric # define INFOC		1	/* info command */
146819Seric # define CHECKC		2	/* check command */
147819Seric 
1481432Seric /*
1491432Seric **  Description of commands known to this program.
1501432Seric **	First argument puts the command into a class.  Second arg is
1511432Seric **	info regarding treatment of this command.  Third arg is a
1521432Seric **	list of flags this command accepts from macros, etc.  Fourth
1531432Seric **	arg is the pathname of the implementing program, or the
1541432Seric **	macro definition, or the arg to a sub-algorithm.
1551432Seric */
156202Seric 
157148Seric struct sccsprog SccsProg[] =
158148Seric {
1591316Seric 	"admin",	PROG,	REALUSER,	"",		PROGPATH(admin),
1601316Seric 	"chghist",	PROG,	0,		"",		PROGPATH(rmdel),
1611316Seric 	"comb",		PROG,	0,		"",		PROGPATH(comb),
1621316Seric 	"delta",	PROG,	0,		"mysrp",	PROGPATH(delta),
1631317Seric 	"get",		PROG,	0,		"ixbeskcl",	PROGPATH(get),
1641316Seric 	"help",		PROG,	NO_SDOT,	"",		PROGPATH(help),
1651316Seric 	"prt",		PROG,	0,		"",		PROGPATH(prt),
1661316Seric 	"rmdel",	PROG,	REALUSER,	"",		PROGPATH(rmdel),
1671316Seric 	"what",		PROG,	NO_SDOT,	"",		PROGPATH(what),
1681431Seric 	"sccsdiff",	SHELL,	REALUSER,	"",		PROGPATH(sccsdiff),
1691317Seric 	"edit",		CMACRO,	NO_SDOT,	"ixbscl",	"get -e",
1701316Seric 	"delget",	CMACRO,	NO_SDOT,	"",		"delta/get -t",
1711316Seric 	"deledit",	CMACRO,	NO_SDOT,	"",		"delta/get -e -t",
1721316Seric 	"fix",		FIX,	NO_SDOT,	"",		NULL,
1731316Seric 	"clean",	CLEAN,	REALUSER,	"",		(char *) CLEANC,
1741316Seric 	"info",		CLEAN,	REALUSER,	"",		(char *) INFOC,
1751316Seric 	"check",	CLEAN,	REALUSER,	"",		(char *) CHECKC,
1761316Seric 	"unedit",	UNEDIT,	NO_SDOT,	"",		NULL,
1771433Seric 	"diffs",	DIFFS,	NO_SDOT|REALUSER, "",		NULL,
1781316Seric 	NULL,		-1,	0,		"",		NULL
179148Seric };
180148Seric 
1811432Seric /* one line from a p-file */
182396Seric struct pfile
183396Seric {
184396Seric 	char	*p_osid;	/* old SID */
185396Seric 	char	*p_nsid;	/* new SID */
186396Seric 	char	*p_user;	/* user who did edit */
187396Seric 	char	*p_date;	/* date of get */
188396Seric 	char	*p_time;	/* time of get */
189396Seric };
190396Seric 
1911270Seric char	*SccsPath = SCCSPATH;	/* pathname of SCCS files */
1921270Seric # ifdef SCCSDIR
1931270Seric char	*SccsDir = SCCSDIR;	/* directory to begin search from */
1941205Seric # else
1951270Seric char	*SccsDir = "";
1961205Seric # endif
1971433Seric int	OutFile = -1;		/* override output file for commands */
198157Seric bool	RealUser;		/* if set, running as real user */
199393Seric # ifdef DEBUG
200393Seric bool	Debug;			/* turn on tracing */
201393Seric # endif
2021432Seric 
203148Seric main(argc, argv)
204148Seric 	int argc;
205148Seric 	char **argv;
206148Seric {
207148Seric 	register char *p;
208262Seric 	extern struct sccsprog *lookup();
2091282Seric 	register int i;
210148Seric 
211148Seric 	/*
212148Seric 	**  Detect and decode flags intended for this program.
213148Seric 	*/
214148Seric 
215200Seric 	if (argc < 2)
216148Seric 	{
2171205Seric 		fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName);
218200Seric 		exit(EX_USAGE);
219200Seric 	}
220200Seric 	argv[argc] = NULL;
221200Seric 
222262Seric 	if (lookup(argv[0]) == NULL)
223200Seric 	{
224262Seric 		while ((p = *++argv) != NULL)
225148Seric 		{
226262Seric 			if (*p != '-')
227262Seric 				break;
228262Seric 			switch (*++p)
229262Seric 			{
230262Seric 			  case 'r':		/* run as real user */
231262Seric 				setuid(getuid());
232262Seric 				RealUser++;
233262Seric 				break;
234148Seric 
2351270Seric # ifndef SCCSDIR
236262Seric 			  case 'p':		/* path of sccs files */
237262Seric 				SccsPath = ++p;
238262Seric 				break;
239148Seric 
240588Seric 			  case 'd':		/* directory to search from */
241588Seric 				SccsDir = ++p;
242588Seric 				break;
2431205Seric # endif
244588Seric 
245393Seric # ifdef DEBUG
246393Seric 			  case 'T':		/* trace */
247393Seric 				Debug++;
248393Seric 				break;
249393Seric # endif
250393Seric 
251262Seric 			  default:
2521205Seric 				usrerr("unknown option -%s", p);
253262Seric 				break;
254262Seric 			}
255148Seric 		}
256262Seric 		if (SccsPath[0] == '\0')
257262Seric 			SccsPath = ".";
258148Seric 	}
259148Seric 
2601316Seric 	i = command(argv, FALSE, FALSE, "");
2611282Seric 	exit(i);
262200Seric }
2631432Seric 
2641432Seric /*
2651282Seric **  COMMAND -- look up and perform a command
2661282Seric **
2671282Seric **	This routine is the guts of this program.  Given an
2681282Seric **	argument vector, it looks up the "command" (argv[0])
2691282Seric **	in the configuration table and does the necessary stuff.
2701282Seric **
2711282Seric **	Parameters:
2721282Seric **		argv -- an argument vector to process.
2731282Seric **		forkflag -- if set, fork before executing the command.
2741316Seric **		editflag -- if set, only include flags listed in the
2751316Seric **			sccsklets field of the command descriptor.
2761316Seric **		arg0 -- a space-seperated list of arguments to insert
2771316Seric **			before argv.
2781282Seric **
2791282Seric **	Returns:
2801282Seric **		zero -- command executed ok.
2811282Seric **		else -- error status.
2821282Seric **
2831282Seric **	Side Effects:
2841282Seric **		none.
2851282Seric */
286157Seric 
2871316Seric command(argv, forkflag, editflag, arg0)
288200Seric 	char **argv;
289201Seric 	bool forkflag;
2901316Seric 	bool editflag;
2911316Seric 	char *arg0;
292200Seric {
293200Seric 	register struct sccsprog *cmd;
294200Seric 	register char *p;
295201Seric 	char buf[40];
296262Seric 	extern struct sccsprog *lookup();
2971316Seric 	char *nav[1000];
2981316Seric 	char **np;
2991431Seric 	register char **ap;
300585Seric 	register int i;
3011431Seric 	register char *q;
302585Seric 	extern bool unedit();
3031282Seric 	int rval = 0;
3041316Seric 	extern char *index();
3051316Seric 	extern char *makefile();
306*1435Seric 	extern char *tail();
307200Seric 
308393Seric # ifdef DEBUG
309393Seric 	if (Debug)
310393Seric 	{
3111316Seric 		printf("command:\n\t\"%s\"\n", arg0);
3121316Seric 		for (np = argv; *np != NULL; np++)
3131316Seric 			printf("\t\"%s\"\n", *np);
314393Seric 	}
315393Seric # endif
316393Seric 
317157Seric 	/*
3181316Seric 	**  Copy arguments.
3191316Seric 	**	Phase one -- from arg0 & if necessary argv[0].
3201316Seric 	*/
3211316Seric 
3221431Seric 	np = ap = &nav[1];
3231316Seric 	for (p = arg0, q = buf; *p != '\0' && *p != '/'; )
3241316Seric 	{
3251316Seric 		*np++ = q;
3261316Seric 		while (*p == ' ')
3271316Seric 			p++;
3281316Seric 		while (*p != ' ' && *p != '\0' && *p != '/')
3291316Seric 			*q++ = *p++;
3301316Seric 		*q++ = '\0';
3311316Seric 	}
3321316Seric 	*np = NULL;
3331431Seric 	if (*ap == NULL)
3341316Seric 		*np++ = *argv++;
3351316Seric 
3361316Seric 	/*
337148Seric 	**  Look up command.
3381431Seric 	**	At this point, *ap is the command name.
339148Seric 	*/
340148Seric 
3411431Seric 	cmd = lookup(*ap);
342262Seric 	if (cmd == NULL)
343148Seric 	{
3441431Seric 		usrerr("Unknown command \"%s\"", *ap);
3451282Seric 		return (EX_USAGE);
346148Seric 	}
347148Seric 
348148Seric 	/*
3491316Seric 	**  Copy remaining arguments doing editing as appropriate.
3501316Seric 	*/
3511316Seric 
3521316Seric 	for (; *argv != NULL; argv++)
3531316Seric 	{
3541316Seric 		p = *argv;
3551316Seric 		if (*p == '-')
3561316Seric 		{
3571316Seric 			if (p[1] == '\0' || !editflag || cmd->sccsklets == NULL ||
3581316Seric 			    index(cmd->sccsklets, p[1]) != NULL)
3591316Seric 				*np++ = p;
3601316Seric 		}
3611316Seric 		else
3621316Seric 		{
3631316Seric 			if (!bitset(NO_SDOT, cmd->sccsflags))
3641316Seric 				p = makefile(p);
3651316Seric 			if (p != NULL)
3661316Seric 				*np++ = p;
3671316Seric 		}
3681316Seric 	}
3691316Seric 	*np = NULL;
3701316Seric 
3711316Seric 	/*
372200Seric 	**  Interpret operation associated with this command.
373157Seric 	*/
374157Seric 
375200Seric 	switch (cmd->sccsoper)
376200Seric 	{
3771431Seric 	  case SHELL:		/* call a shell file */
3781431Seric 		*ap = cmd->sccspath;
3791431Seric 		*--ap = "sh";
3801431Seric 		rval = callprog("/bin/sh", cmd->sccsflags, ap, forkflag);
3811431Seric 		break;
3821431Seric 
383200Seric 	  case PROG:		/* call an sccs prog */
3841431Seric 		rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag);
385201Seric 		break;
386201Seric 
387201Seric 	  case CMACRO:		/* command macro */
388201Seric 		for (p = cmd->sccspath; *p != '\0'; p++)
389201Seric 		{
3901316Seric 			q = p;
3911316Seric 			while (*p != '\0' && *p != '/')
3921316Seric 				p++;
3931431Seric 			rval = command(&ap[1], *p != '\0', TRUE, q);
3941282Seric 			if (rval != 0)
3951282Seric 				break;
396201Seric 		}
3971282Seric 		break;
398157Seric 
399226Seric 	  case FIX:		/* fix a delta */
4001431Seric 		if (strncmp(ap[1], "-r", 2) != 0)
401226Seric 		{
4021205Seric 			usrerr("-r flag needed for fix command");
4031282Seric 			rval = EX_USAGE;
404226Seric 			break;
405226Seric 		}
4061431Seric 		rval = command(&ap[1], TRUE, TRUE, "get -k");
4071282Seric 		if (rval == 0)
4081431Seric 			rval = command(&ap[1], TRUE, TRUE, "rmdel");
4091282Seric 		if (rval == 0)
4101431Seric 			rval = command(&ap[2], FALSE, TRUE, "get -e -g");
4111282Seric 		break;
412226Seric 
413261Seric 	  case CLEAN:
4141282Seric 		rval = clean((int) cmd->sccspath);
415261Seric 		break;
416261Seric 
417396Seric 	  case UNEDIT:
4181431Seric 		for (argv = np = &ap[1]; *argv != NULL; argv++)
419585Seric 		{
4201316Seric 			if (unedit(*argv))
4211316Seric 				*np++ = *argv;
422585Seric 		}
4231316Seric 		*np = NULL;
424585Seric 		if (i > 0)
4251431Seric 			rval = command(&ap[1], FALSE, FALSE, "get");
426396Seric 		break;
427396Seric 
4281433Seric 	  case DIFFS:		/* diff between s-file & edit file */
4291433Seric 		/* find the end of the flag arguments */
4301433Seric 		for (np = &ap[1]; *np != NULL && **np == '-'; np++)
4311433Seric 			continue;
4321433Seric 		argv = np;
4331433Seric 
4341433Seric 		/* for each file, do the diff */
4351433Seric 		while (*np != NULL)
4361433Seric 		{
4371433Seric 			*argv = *np++;
4381433Seric 			p = *np;
4391433Seric 			*np = NULL;
440*1435Seric 			i = dodiff(ap, tail(*argv));
4411433Seric 			if (rval == 0)
4421433Seric 				rval = i;
4431433Seric 			*np = p;
4441433Seric 		}
4451433Seric 		break;
4461433Seric 
447200Seric 	  default:
4481205Seric 		syserr("oper %d", cmd->sccsoper);
449200Seric 		exit(EX_SOFTWARE);
450200Seric 	}
4511282Seric # ifdef DEBUG
4521282Seric 	if (Debug)
4531282Seric 		printf("command: rval=%d\n", rval);
4541282Seric # endif
4551282Seric 	return (rval);
456200Seric }
4571432Seric 
4581432Seric /*
459262Seric **  LOOKUP -- look up an SCCS command name.
460262Seric **
461262Seric **	Parameters:
462262Seric **		name -- the name of the command to look up.
463262Seric **
464262Seric **	Returns:
465262Seric **		ptr to command descriptor for this command.
466262Seric **		NULL if no such entry.
467262Seric **
468262Seric **	Side Effects:
469262Seric **		none.
470262Seric */
471200Seric 
472262Seric struct sccsprog *
473262Seric lookup(name)
474262Seric 	char *name;
475262Seric {
476262Seric 	register struct sccsprog *cmd;
477226Seric 
478262Seric 	for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
479262Seric 	{
480262Seric 		if (strcmp(cmd->sccsname, name) == 0)
481262Seric 			return (cmd);
482262Seric 	}
483262Seric 	return (NULL);
484262Seric }
4851432Seric 
4861432Seric /*
4871282Seric **  CALLPROG -- call a program
4881282Seric **
4891316Seric **	Used to call the SCCS programs.
4901282Seric **
4911282Seric **	Parameters:
4921282Seric **		progpath -- pathname of the program to call.
4931282Seric **		flags -- status flags from the command descriptors.
4941282Seric **		argv -- an argument vector to pass to the program.
4951282Seric **		forkflag -- if true, fork before calling, else just
4961282Seric **			exec.
4971282Seric **
4981282Seric **	Returns:
4991282Seric **		The exit status of the program.
5001282Seric **		Nothing if forkflag == FALSE.
5011282Seric **
5021282Seric **	Side Effects:
5031282Seric **		Can exit if forkflag == FALSE.
5041282Seric */
505226Seric 
506200Seric callprog(progpath, flags, argv, forkflag)
507200Seric 	char *progpath;
508200Seric 	short flags;
509200Seric 	char **argv;
510200Seric 	bool forkflag;
511200Seric {
512200Seric 	register int i;
513201Seric 	auto int st;
514200Seric 
5151316Seric # ifdef DEBUG
5161316Seric 	if (Debug)
5171316Seric 	{
5181316Seric 		printf("callprog:\n");
5191316Seric 		for (i = 0; argv[i] != NULL; i++)
5201316Seric 			printf("\t\"%s\"\n", argv[i]);
5211316Seric 	}
5221316Seric # endif
5231316Seric 
524200Seric 	if (*argv == NULL)
525200Seric 		return (-1);
526200Seric 
527157Seric 	/*
528226Seric 	**  Fork if appropriate.
529148Seric 	*/
530148Seric 
531200Seric 	if (forkflag)
532200Seric 	{
533393Seric # ifdef DEBUG
534393Seric 		if (Debug)
535393Seric 			printf("Forking\n");
536393Seric # endif
537200Seric 		i = fork();
538200Seric 		if (i < 0)
539200Seric 		{
5401205Seric 			syserr("cannot fork");
541200Seric 			exit(EX_OSERR);
542200Seric 		}
543200Seric 		else if (i > 0)
544201Seric 		{
545201Seric 			wait(&st);
5461282Seric 			if ((st & 0377) == 0)
5471282Seric 				st = (st >> 8) & 0377;
5481433Seric 			if (OutFile >= 0)
5491433Seric 			{
5501433Seric 				close(OutFile);
5511433Seric 				OutFile = -1;
5521433Seric 			}
553201Seric 			return (st);
554201Seric 		}
555200Seric 	}
5561433Seric 	else if (OutFile >= 0)
5571433Seric 	{
5581433Seric 		syserr("callprog: setting stdout w/o forking");
5591433Seric 		exit(EX_SOFTWARE);
5601433Seric 	}
561200Seric 
5621433Seric 	/* set protection as appropriate */
563200Seric 	if (bitset(REALUSER, flags))
564200Seric 		setuid(getuid());
5651433Seric 
5661433Seric 	/* change standard input & output if needed */
5671433Seric 	if (OutFile >= 0)
5681433Seric 	{
5691433Seric 		close(1);
5701433Seric 		dup(OutFile);
5711433Seric 		close(OutFile);
5721433Seric 	}
573226Seric 
5741433Seric 	/* call real SCCS program */
575226Seric 	execv(progpath, argv);
5761205Seric 	syserr("cannot execute %s", progpath);
577148Seric 	exit(EX_UNAVAILABLE);
578148Seric }
5791432Seric 
5801432Seric /*
581586Seric **  MAKEFILE -- make filename of SCCS file
582586Seric **
583586Seric **	If the name passed is already the name of an SCCS file,
584586Seric **	just return it.  Otherwise, munge the name into the name
585586Seric **	of the actual SCCS file.
586586Seric **
587586Seric **	There are cases when it is not clear what you want to
588586Seric **	do.  For example, if SccsPath is an absolute pathname
589586Seric **	and the name given is also an absolute pathname, we go
590586Seric **	for SccsPath (& only use the last component of the name
591586Seric **	passed) -- this is important for security reasons (if
592586Seric **	sccs is being used as a setuid front end), but not
593586Seric **	particularly intuitive.
594586Seric **
595586Seric **	Parameters:
596586Seric **		name -- the file name to be munged.
597586Seric **
598586Seric **	Returns:
599586Seric **		The pathname of the sccs file.
600586Seric **		NULL on error.
601586Seric **
602586Seric **	Side Effects:
603586Seric **		none.
604586Seric */
605148Seric 
606148Seric char *
607148Seric makefile(name)
608148Seric 	char *name;
609148Seric {
610148Seric 	register char *p;
611148Seric 	register char c;
612148Seric 	char buf[512];
613588Seric 	struct stat stbuf;
614148Seric 	extern char *malloc();
615586Seric 	extern char *rindex();
616588Seric 	extern bool safepath();
617587Seric 	extern bool isdir();
618587Seric 	register char *q;
619148Seric 
620586Seric 	p = rindex(name, '/');
621586Seric 	if (p == NULL)
622586Seric 		p = name;
623586Seric 	else
624586Seric 		p++;
625586Seric 
626148Seric 	/*
627588Seric 	**  Check to see that the path is "safe", i.e., that we
628588Seric 	**  are not letting some nasty person use the setuid part
629588Seric 	**  of this program to look at or munge some presumably
630588Seric 	**  hidden files.
631148Seric 	*/
632148Seric 
633588Seric 	if (SccsDir[0] == '/' && !safepath(name))
634588Seric 		return (NULL);
635586Seric 
636586Seric 	/*
637588Seric 	**  Create the base pathname.
638586Seric 	*/
639586Seric 
640588Seric 	if (SccsDir[0] != '\0' && name[0] != '/' && strncmp(name, "./", 2) != 0)
641148Seric 	{
642588Seric 		strcpy(buf, SccsDir);
643586Seric 		strcat(buf, "/");
644586Seric 	}
645586Seric 	else
646586Seric 		strcpy(buf, "");
647587Seric 	strncat(buf, name, p - name);
648587Seric 	q = &buf[strlen(buf)];
649587Seric 	strcpy(q, p);
650587Seric 	if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
651586Seric 	{
652588Seric 		strcpy(q, SccsPath);
653588Seric 		strcat(buf, "/s.");
654586Seric 		strcat(buf, p);
655586Seric 	}
656148Seric 
657588Seric 	if (strcmp(buf, name) == 0)
658588Seric 		p = name;
659588Seric 	else
660148Seric 	{
661588Seric 		p = malloc(strlen(buf) + 1);
662588Seric 		if (p == NULL)
663588Seric 		{
664588Seric 			perror("Sccs: no mem");
665588Seric 			exit(EX_OSERR);
666588Seric 		}
667588Seric 		strcpy(p, buf);
668148Seric 	}
669148Seric 	return (p);
670148Seric }
6711432Seric 
6721432Seric /*
673587Seric **  ISDIR -- return true if the argument is a directory.
674587Seric **
675587Seric **	Parameters:
676587Seric **		name -- the pathname of the file to check.
677587Seric **
678587Seric **	Returns:
679587Seric **		TRUE if 'name' is a directory, FALSE otherwise.
680587Seric **
681587Seric **	Side Effects:
682587Seric **		none.
683587Seric */
684587Seric 
685587Seric bool
686587Seric isdir(name)
687587Seric 	char *name;
688587Seric {
689587Seric 	struct stat stbuf;
690587Seric 
691587Seric 	return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
692587Seric }
6931432Seric 
6941432Seric /*
695586Seric **  SAFEPATH -- determine whether a pathname is "safe"
696586Seric **
697586Seric **	"Safe" pathnames only allow you to get deeper into the
698586Seric **	directory structure, i.e., full pathnames and ".." are
699586Seric **	not allowed.
700586Seric **
701586Seric **	Parameters:
702586Seric **		p -- the name to check.
703586Seric **
704586Seric **	Returns:
705586Seric **		TRUE -- if the path is safe.
706586Seric **		FALSE -- if the path is not safe.
707586Seric **
708586Seric **	Side Effects:
709586Seric **		Prints a message if the path is not safe.
710586Seric */
711586Seric 
712586Seric bool
713586Seric safepath(p)
714586Seric 	register char *p;
715586Seric {
716586Seric 	extern char *index();
717586Seric 
718586Seric 	if (*p != '/')
719586Seric 	{
720586Seric 		while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
721586Seric 		{
722586Seric 			p = index(p, '/');
723586Seric 			if (p == NULL)
724586Seric 				return (TRUE);
725586Seric 			p++;
726586Seric 		}
727586Seric 	}
728586Seric 
729586Seric 	printf("You may not use full pathnames or \"..\"\n");
730586Seric 	return (FALSE);
731586Seric }
7321432Seric 
7331432Seric /*
734261Seric **  CLEAN -- clean out recreatable files
735261Seric **
736261Seric **	Any file for which an "s." file exists but no "p." file
737261Seric **	exists in the current directory is purged.
738261Seric **
739261Seric **	Parameters:
740819Seric **		tells whether this came from a "clean", "info", or
741819Seric **		"check" command.
742261Seric **
743261Seric **	Returns:
744261Seric **		none.
745261Seric **
746261Seric **	Side Effects:
747819Seric **		Removes files in the current directory.
748819Seric **		Prints information regarding files being edited.
749819Seric **		Exits if a "check" command.
750261Seric */
751261Seric 
752819Seric clean(mode)
753819Seric 	int mode;
754261Seric {
755261Seric 	struct direct dir;
756261Seric 	struct stat stbuf;
757261Seric 	char buf[100];
758394Seric 	char pline[120];
759346Seric 	register FILE *dirfd;
760346Seric 	register char *basefile;
761351Seric 	bool gotedit;
762394Seric 	FILE *pfp;
763261Seric 
7641207Seric 	strcpy(buf, SccsDir);
7651207Seric 	if (buf[0] != '\0')
7661207Seric 		strcat(buf, "/");
7671207Seric 	strcat(buf, SccsPath);
7681207Seric 	dirfd = fopen(buf, "r");
769261Seric 	if (dirfd == NULL)
770261Seric 	{
7711207Seric 		usrerr("cannot open %s", buf);
7721282Seric 		return (EX_NOINPUT);
773261Seric 	}
774261Seric 
775261Seric 	/*
776261Seric 	**  Scan the SCCS directory looking for s. files.
777261Seric 	*/
778261Seric 
779351Seric 	gotedit = FALSE;
780261Seric 	while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
781261Seric 	{
782568Seric 		if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
783261Seric 			continue;
784261Seric 
785261Seric 		/* got an s. file -- see if the p. file exists */
7861207Seric 		strcpy(buf, SccsDir);
7871207Seric 		if (buf[0] != '\0')
7881207Seric 			strcat(buf, "/");
7891207Seric 		strcat(buf, SccsPath);
790261Seric 		strcat(buf, "/p.");
791346Seric 		basefile = &buf[strlen(buf)];
792568Seric 		strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
793346Seric 		basefile[sizeof dir.d_name - 2] = '\0';
794394Seric 		pfp = fopen(buf, "r");
795394Seric 		if (pfp != NULL)
796346Seric 		{
797394Seric 			while (fgets(pline, sizeof pline, pfp) != NULL)
798416Seric 				printf("%12s: being edited: %s", basefile, pline);
799394Seric 			fclose(pfp);
800351Seric 			gotedit = TRUE;
801261Seric 			continue;
802346Seric 		}
803261Seric 
804261Seric 		/* the s. file exists and no p. file exists -- unlink the g-file */
805819Seric 		if (mode == CLEANC)
806346Seric 		{
807568Seric 			strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
808346Seric 			buf[sizeof dir.d_name - 2] = '\0';
809346Seric 			unlink(buf);
810346Seric 		}
811261Seric 	}
812261Seric 
813261Seric 	fclose(dirfd);
814819Seric 	if (!gotedit && mode == INFOC)
815416Seric 		printf("Nothing being edited\n");
816819Seric 	if (mode == CHECKC)
817819Seric 		exit(gotedit);
8181282Seric 	return (EX_OK);
819261Seric }
8201432Seric 
8211432Seric /*
822396Seric **  UNEDIT -- unedit a file
823396Seric **
824396Seric **	Checks to see that the current user is actually editting
825396Seric **	the file and arranges that s/he is not editting it.
826396Seric **
827396Seric **	Parameters:
828416Seric **		fn -- the name of the file to be unedited.
829396Seric **
830396Seric **	Returns:
831585Seric **		TRUE -- if the file was successfully unedited.
832585Seric **		FALSE -- if the file was not unedited for some
833585Seric **			reason.
834396Seric **
835396Seric **	Side Effects:
836396Seric **		fn is removed
837396Seric **		entries are removed from pfile.
838396Seric */
839396Seric 
840585Seric bool
841396Seric unedit(fn)
842396Seric 	char *fn;
843396Seric {
844396Seric 	register FILE *pfp;
845396Seric 	char *pfn;
846396Seric 	static char tfn[] = "/tmp/sccsXXXXX";
847396Seric 	FILE *tfp;
848396Seric 	register char *p;
849396Seric 	register char *q;
850396Seric 	bool delete = FALSE;
851396Seric 	bool others = FALSE;
852396Seric 	char *myname;
853396Seric 	extern char *getlogin();
854396Seric 	struct pfile *pent;
855396Seric 	extern struct pfile *getpfile();
856396Seric 	char buf[120];
8571316Seric 	extern char *makefile();
858828Seric # ifdef UIDUSER
859828Seric 	struct passwd *pw;
860828Seric 	extern struct passwd *getpwuid();
861828Seric # endif UIDUSER
862396Seric 
863396Seric 	/* make "s." filename & find the trailing component */
864396Seric 	pfn = makefile(fn);
865586Seric 	if (pfn == NULL)
866586Seric 		return (FALSE);
867586Seric 	q = rindex(pfn, '/');
868586Seric 	if (q == NULL)
869586Seric 		q = &pfn[-1];
870586Seric 	if (q[1] != 's' || q[2] != '.')
871396Seric 	{
8721205Seric 		usrerr("bad file name \"%s\"", fn);
873585Seric 		return (FALSE);
874396Seric 	}
875396Seric 
876396Seric 	/* turn "s." into "p." */
877396Seric 	*++q = 'p';
878396Seric 
879396Seric 	pfp = fopen(pfn, "r");
880396Seric 	if (pfp == NULL)
881396Seric 	{
882416Seric 		printf("%12s: not being edited\n", fn);
883585Seric 		return (FALSE);
884396Seric 	}
885396Seric 
886396Seric 	/*
887396Seric 	**  Copy p-file to temp file, doing deletions as needed.
888396Seric 	*/
889396Seric 
890396Seric 	mktemp(tfn);
891396Seric 	tfp = fopen(tfn, "w");
892396Seric 	if (tfp == NULL)
893396Seric 	{
8941205Seric 		usrerr("cannot create \"%s\"", tfn);
895396Seric 		exit(EX_OSERR);
896396Seric 	}
897396Seric 
898828Seric # ifdef UIDUSER
899828Seric 	pw = getpwuid(getuid());
900828Seric 	if (pw == NULL)
901828Seric 	{
9021205Seric 		syserr("who are you? (uid=%d)", getuid());
903828Seric 		exit(EX_OSERR);
904828Seric 	}
905828Seric 	myname = pw->pw_name;
906828Seric # else
907396Seric 	myname = getlogin();
908828Seric # endif UIDUSER
909396Seric 	while ((pent = getpfile(pfp)) != NULL)
910396Seric 	{
911396Seric 		if (strcmp(pent->p_user, myname) == 0)
912396Seric 		{
913396Seric 			/* a match */
914396Seric 			delete++;
915396Seric 		}
916396Seric 		else
917396Seric 		{
918396Seric 			fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
919396Seric 			    pent->p_nsid, pent->p_user, pent->p_date,
920396Seric 			    pent->p_time);
921396Seric 			others++;
922396Seric 		}
923396Seric 	}
924396Seric 
925396Seric 	/* do final cleanup */
926396Seric 	if (others)
927396Seric 	{
928396Seric 		if (freopen(tfn, "r", tfp) == NULL)
929396Seric 		{
9301205Seric 			syserr("cannot reopen \"%s\"", tfn);
931396Seric 			exit(EX_OSERR);
932396Seric 		}
933396Seric 		if (freopen(pfn, "w", pfp) == NULL)
934396Seric 		{
9351205Seric 			usrerr("cannot create \"%s\"", pfn);
936585Seric 			return (FALSE);
937396Seric 		}
938396Seric 		while (fgets(buf, sizeof buf, tfp) != NULL)
939396Seric 			fputs(buf, pfp);
940396Seric 	}
941396Seric 	else
942396Seric 	{
943396Seric 		unlink(pfn);
944396Seric 	}
945396Seric 	fclose(tfp);
946396Seric 	fclose(pfp);
947396Seric 	unlink(tfn);
948396Seric 
949396Seric 	if (delete)
950396Seric 	{
951*1435Seric 		unlink(tail(fn));
952*1435Seric 		printf("%12s: removed\n", tail(fn));
953585Seric 		return (TRUE);
954396Seric 	}
955396Seric 	else
956396Seric 	{
957416Seric 		printf("%12s: not being edited by you\n", fn);
958585Seric 		return (FALSE);
959396Seric 	}
960396Seric }
9611432Seric 
9621432Seric /*
9631433Seric **  DODIFF -- diff an s-file against a g-file
9641433Seric **
9651433Seric **	Parameters:
9661433Seric **		getv -- argv for the 'get' command.
9671433Seric **		gfile -- name of the g-file to diff against.
9681433Seric **
9691433Seric **	Returns:
9701433Seric **		Result of get.
9711433Seric **
9721433Seric **	Side Effects:
9731433Seric **		none.
9741433Seric */
9751433Seric 
9761433Seric dodiff(getv, gfile)
9771433Seric 	char **getv;
9781433Seric 	char *gfile;
9791433Seric {
9801433Seric 	int pipev[2];
9811433Seric 	int rval;
9821433Seric 	register int i;
9831433Seric 	register int pid;
9841433Seric 	auto int st;
9851433Seric 	extern int errno;
9861433Seric 	int (*osig)();
9871433Seric 
9881433Seric 	if (pipe(pipev) < 0)
9891433Seric 	{
9901433Seric 		syserr("dodiff: pipe failed");
9911433Seric 		exit(EX_OSERR);
9921433Seric 	}
9931433Seric 	if ((pid = fork()) < 0)
9941433Seric 	{
9951433Seric 		syserr("dodiff: fork failed");
9961433Seric 		exit(EX_OSERR);
9971433Seric 	}
9981433Seric 	else if (pid > 0)
9991433Seric 	{
10001433Seric 		/* in parent; run get */
10011433Seric 		OutFile = pipev[1];
10021433Seric 		close(pipev[0]);
10031433Seric 		rval = command(&getv[1], TRUE, FALSE, "get -s -k -p");
10041433Seric 		osig = signal(SIGINT, SIG_IGN);
10051433Seric 		while (((i = wait(&st)) >= 0 && i != pid) || errno == EINTR)
10061433Seric 			errno = 0;
10071433Seric 		signal(SIGINT, osig);
10081433Seric 		/* ignore result of diff */
10091433Seric 	}
10101433Seric 	else
10111433Seric 	{
10121433Seric 		/* in child, run diff */
10131433Seric 		if (close(pipev[1]) < 0 || close(0) < 0 ||
10141433Seric 		    dup(pipev[0]) != 0 || close(pipev[0]) < 0)
10151433Seric 		{
10161433Seric 			syserr("dodiff: magic failed");
10171433Seric 			exit(EX_OSERR);
10181433Seric 		}
10191433Seric 		execl(PROGPATH(bdiff), "bdiff", "-", gfile, NULL);
10201433Seric # ifndef V6
10211433Seric 		execlp("bdiff", "bdiff", "-", gfile, NULL);
10221433Seric 		execlp("diff", "diff", "-", gfile, NULL);
10231433Seric # endif NOT V6
10241433Seric 		syserr("bdiff: cannot execute");
10251433Seric 		exit(EX_OSERR);
10261433Seric 	}
10271433Seric 	return (rval);
10281433Seric }
10291433Seric 
10301433Seric /*
1031*1435Seric **  TAIL -- return tail of filename.
1032*1435Seric **
1033*1435Seric **	Parameters:
1034*1435Seric **		fn -- the filename.
1035*1435Seric **
1036*1435Seric **	Returns:
1037*1435Seric **		a pointer to the tail of the filename; e.g., given
1038*1435Seric **		"cmd/ls.c", "ls.c" is returned.
1039*1435Seric **
1040*1435Seric **	Side Effects:
1041*1435Seric **		none.
1042*1435Seric */
1043*1435Seric 
1044*1435Seric char *
1045*1435Seric tail(fn)
1046*1435Seric 	register char *fn;
1047*1435Seric {
1048*1435Seric 	register char *p;
1049*1435Seric 
1050*1435Seric 	for (p = fn; *p != 0; p++)
1051*1435Seric 		if (*p == '/' && p[1] != '\0' && p[1] != '/')
1052*1435Seric 			fn = &p[1];
1053*1435Seric 	return (fn);
1054*1435Seric }
1055*1435Seric 
1056*1435Seric /*
1057396Seric **  GETPFILE -- get an entry from the p-file
1058396Seric **
1059396Seric **	Parameters:
1060396Seric **		pfp -- p-file file pointer
1061396Seric **
1062396Seric **	Returns:
1063396Seric **		pointer to p-file struct for next entry
1064396Seric **		NULL on EOF or error
1065396Seric **
1066396Seric **	Side Effects:
1067396Seric **		Each call wipes out results of previous call.
1068396Seric */
1069396Seric 
1070396Seric struct pfile *
1071396Seric getpfile(pfp)
1072396Seric 	FILE *pfp;
1073396Seric {
1074396Seric 	static struct pfile ent;
1075396Seric 	static char buf[120];
1076396Seric 	register char *p;
1077396Seric 	extern char *nextfield();
1078396Seric 
1079396Seric 	if (fgets(buf, sizeof buf, pfp) == NULL)
1080396Seric 		return (NULL);
1081396Seric 
1082396Seric 	ent.p_osid = p = buf;
1083396Seric 	ent.p_nsid = p = nextfield(p);
1084396Seric 	ent.p_user = p = nextfield(p);
1085396Seric 	ent.p_date = p = nextfield(p);
1086396Seric 	ent.p_time = p = nextfield(p);
1087396Seric 	if (p == NULL || nextfield(p) != NULL)
1088396Seric 		return (NULL);
1089396Seric 
1090396Seric 	return (&ent);
1091396Seric }
1092396Seric 
1093396Seric 
1094396Seric char *
1095396Seric nextfield(p)
1096396Seric 	register char *p;
1097396Seric {
1098396Seric 	if (p == NULL || *p == '\0')
1099396Seric 		return (NULL);
1100396Seric 	while (*p != ' ' && *p != '\n' && *p != '\0')
1101396Seric 		p++;
1102396Seric 	if (*p == '\n' || *p == '\0')
1103396Seric 	{
1104396Seric 		*p = '\0';
1105396Seric 		return (NULL);
1106396Seric 	}
1107396Seric 	*p++ = '\0';
1108396Seric 	return (p);
1109396Seric }
11101432Seric 
11111432Seric /*
11121205Seric **  USRERR -- issue user-level error
11131205Seric **
11141205Seric **	Parameters:
11151205Seric **		f -- format string.
11161205Seric **		p1-p3 -- parameters to a printf.
11171205Seric **
11181205Seric **	Returns:
11191205Seric **		-1
11201205Seric **
11211205Seric **	Side Effects:
11221205Seric **		none.
11231205Seric */
11241205Seric 
11251205Seric usrerr(f, p1, p2, p3)
11261205Seric 	char *f;
11271205Seric {
11281205Seric 	fprintf(stderr, "\n%s: ", MyName);
11291205Seric 	fprintf(stderr, f, p1, p2, p3);
11301205Seric 	fprintf(stderr, "\n");
11311205Seric 
11321205Seric 	return (-1);
11331205Seric }
11341432Seric 
11351432Seric /*
11361205Seric **  SYSERR -- print system-generated error.
11371205Seric **
11381205Seric **	Parameters:
11391205Seric **		f -- format string to a printf.
11401205Seric **		p1, p2, p3 -- parameters to f.
11411205Seric **
11421205Seric **	Returns:
11431205Seric **		never.
11441205Seric **
11451205Seric **	Side Effects:
11461205Seric **		none.
11471205Seric */
11481205Seric 
11491205Seric syserr(f, p1, p2, p3)
11501205Seric 	char *f;
11511205Seric {
11521205Seric 	extern int errno;
11531205Seric 
11541205Seric 	fprintf(stderr, "\n%s SYSERR: ", MyName);
11551205Seric 	fprintf(stderr, f, p1, p2, p3);
11561205Seric 	fprintf(stderr, "\n");
11571205Seric 	if (errno == 0)
11581205Seric 		exit(EX_SOFTWARE);
11591205Seric 	else
11601205Seric 	{
11611205Seric 		perror(0);
11621205Seric 		exit(EX_OSERR);
11631205Seric 	}
11641205Seric }
1165