xref: /csrg-svn/usr.bin/sccs/sccs.c (revision 1316)
1148Seric # include <stdio.h>
2148Seric # include <sys/types.h>
3148Seric # include <sys/stat.h>
4261Seric # include <sys/dir.h>
5148Seric # include <sysexits.h>
6202Seric # include <whoami.h>
7148Seric 
8828Seric /*
9828Seric **  SCCS.C -- human-oriented front end to the SCCS system.
10828Seric **
11828Seric **	Without trying to add any functionality to speak of, this
12828Seric **	program tries to make SCCS a little more accessible to human
13828Seric **	types.  The main thing it does is automatically put the
14828Seric **	string "SCCS/s." on the front of names.  Also, it has a
15828Seric **	couple of things that are designed to shorten frequent
16828Seric **	combinations, e.g., "delget" which expands to a "delta"
17828Seric **	and a "get".
18828Seric **
19828Seric **	This program can also function as a setuid front end.
20828Seric **	To do this, you should copy the source, renaming it to
21828Seric **	whatever you want, e.g., "syssccs".  Change any defaults
22828Seric **	in the program (e.g., syssccs might default -d to
23828Seric **	"/usr/src/sys").  Then recompile and put the result
24828Seric **	as setuid to whomever you want.  In this mode, sccs
25828Seric **	knows to not run setuid for certain programs in order
26828Seric **	to preserve security, and so forth.
27828Seric **
28828Seric **	Usage:
29828Seric **		sccs [flags] command [args]
30828Seric **
31828Seric **	Flags:
32828Seric **		-d<dir>		<dir> represents a directory to search
33828Seric **				out of.  It should be a full pathname
34828Seric **				for general usage.  E.g., if <dir> is
35828Seric **				"/usr/src/sys", then a reference to the
36828Seric **				file "dev/bio.c" becomes a reference to
37828Seric **				"/usr/src/sys/dev/bio.c".
38828Seric **		-p<path>	prepends <path> to the final component
39828Seric **				of the pathname.  By default, this is
40828Seric **				"SCCS".  For example, in the -d example
41828Seric **				above, the path then gets modified to
42828Seric **				"/usr/src/sys/dev/SCCS/s.bio.c".  In
43828Seric **				more common usage (without the -d flag),
44828Seric **				"prog.c" would get modified to
45828Seric **				"SCCS/s.prog.c".  In both cases, the
46828Seric **				"s." gets automatically prepended.
47828Seric **		-r		run as the real user.
48828Seric **
49828Seric **	Commands:
50828Seric **		admin,
51828Seric **		get,
52828Seric **		delta,
53828Seric **		rmdel,
54828Seric **		chghist,
55828Seric **		etc.		Straight out of SCCS; only difference
56828Seric **				is that pathnames get modified as
57828Seric **				described above.
58828Seric **		edit		Macro for "get -e".
59828Seric **		unedit		Removes a file being edited, knowing
60828Seric **				about p-files, etc.
61828Seric **		delget		Macro for "delta" followed by "get".
62828Seric **		deledit		Macro for "delta" followed by "get -e".
63828Seric **		info		Tell what files being edited.
64828Seric **		clean		Remove all files that can be
65828Seric **				regenerated from SCCS files.
661205Seric **		check		Like info, but return exit status, for
67828Seric **				use in makefiles.
68828Seric **		fix		Remove a top delta & reedit, but save
69828Seric **				the previous changes in that delta.
70828Seric **
71828Seric **	Compilation Flags:
72828Seric **		UIDUSER -- determine who the user is by looking at the
73828Seric **			uid rather than the login name -- for machines
74828Seric **			where SCCS gets the user in this way.
751270Seric **		SCCSDIR -- if defined, forces the -d flag to take on
761205Seric **			this value.  This is so that the setuid
771205Seric **			aspects of this program cannot be abused.
781270Seric **			This flag also disables the -p flag.
791270Seric **		SCCSPATH -- the default for the -p flag.
80828Seric **
81828Seric **	Compilation Instructions:
82828Seric **		cc -O -n -s sccs.c
83828Seric **
84828Seric **	Author:
85828Seric **		Eric Allman, UCB/INGRES
861270Seric **		Copyright 1980 Regents of the University of California
87828Seric */
88155Seric 
891270Seric /*******************  Configuration Information  ********************/
901270Seric 
91828Seric # ifdef CSVAX
92828Seric # define UIDUSER
931270Seric # define PROGPATH(name)	"/usr/local/name"
941270Seric # endif CSVAX
95828Seric 
961270Seric # define SCCSPATH	"SCCS"
971270Seric /* put #define SCCSDIR here */
98828Seric 
991270Seric char	MyName[] = "sccs";	/* name used in messages */
1001270Seric 
1011270Seric /****************  End of Configuration Information  ****************/
1021270Seric 
103*1316Seric static char SccsId[] = "@(#)sccs.c	1.32 10/09/80";
1041270Seric 
105157Seric # define bitset(bit, word)	((bit) & (word))
106157Seric 
107157Seric typedef char	bool;
108200Seric # define TRUE	1
109200Seric # define FALSE	0
110157Seric 
111828Seric # ifdef UIDUSER
112828Seric # include <pwd.h>
113828Seric # endif UIDUSER
114828Seric 
115148Seric struct sccsprog
116148Seric {
117148Seric 	char	*sccsname;	/* name of SCCS routine */
118200Seric 	short	sccsoper;	/* opcode, see below */
119200Seric 	short	sccsflags;	/* flags, see below */
120*1316Seric 	char	*sccsklets;	/* valid key-letters on macros */
121148Seric 	char	*sccspath;	/* pathname of binary implementing */
122148Seric };
123148Seric 
124200Seric /* values for sccsoper */
125200Seric # define PROG		0	/* call a program */
126201Seric # define CMACRO		1	/* command substitution macro */
127226Seric # define FIX		2	/* fix a delta */
128261Seric # define CLEAN		3	/* clean out recreatable files */
129396Seric # define UNEDIT		4	/* unedit a file */
130200Seric 
131157Seric /* bits for sccsflags */
132200Seric # define NO_SDOT	0001	/* no s. on front of args */
133200Seric # define REALUSER	0002	/* protected (e.g., admin) */
134148Seric 
135819Seric /* modes for the "clean", "info", "check" ops */
136819Seric # define CLEANC		0	/* clean command */
137819Seric # define INFOC		1	/* info command */
138819Seric # define CHECKC		2	/* check command */
139819Seric 
140202Seric # ifndef PROGPATH
141202Seric # define PROGPATH(name)	"/usr/sccs/name"
142202Seric # endif PROGPATH
143202Seric 
144148Seric struct sccsprog SccsProg[] =
145148Seric {
146*1316Seric 	"admin",	PROG,	REALUSER,	"",		PROGPATH(admin),
147*1316Seric 	"chghist",	PROG,	0,		"",		PROGPATH(rmdel),
148*1316Seric 	"comb",		PROG,	0,		"",		PROGPATH(comb),
149*1316Seric 	"delta",	PROG,	0,		"mysrp",	PROGPATH(delta),
150*1316Seric 	"get",		PROG,	0,		"ixbeskc",	PROGPATH(get),
151*1316Seric 	"help",		PROG,	NO_SDOT,	"",		PROGPATH(help),
152*1316Seric 	"prt",		PROG,	0,		"",		PROGPATH(prt),
153*1316Seric 	"rmdel",	PROG,	REALUSER,	"",		PROGPATH(rmdel),
154*1316Seric 	"what",		PROG,	NO_SDOT,	"",		PROGPATH(what),
155*1316Seric 	"edit",		CMACRO,	NO_SDOT,	"ixbsc",	"get -e",
156*1316Seric 	"delget",	CMACRO,	NO_SDOT,	"",		"delta/get -t",
157*1316Seric 	"deledit",	CMACRO,	NO_SDOT,	"",		"delta/get -e -t",
158*1316Seric 	"fix",		FIX,	NO_SDOT,	"",		NULL,
159*1316Seric 	"clean",	CLEAN,	REALUSER,	"",		(char *) CLEANC,
160*1316Seric 	"info",		CLEAN,	REALUSER,	"",		(char *) INFOC,
161*1316Seric 	"check",	CLEAN,	REALUSER,	"",		(char *) CHECKC,
162*1316Seric 	"unedit",	UNEDIT,	NO_SDOT,	"",		NULL,
163*1316Seric 	NULL,		-1,	0,		"",		NULL
164148Seric };
165148Seric 
166396Seric struct pfile
167396Seric {
168396Seric 	char	*p_osid;	/* old SID */
169396Seric 	char	*p_nsid;	/* new SID */
170396Seric 	char	*p_user;	/* user who did edit */
171396Seric 	char	*p_date;	/* date of get */
172396Seric 	char	*p_time;	/* time of get */
173396Seric };
174396Seric 
1751270Seric char	*SccsPath = SCCSPATH;	/* pathname of SCCS files */
1761270Seric # ifdef SCCSDIR
1771270Seric char	*SccsDir = SCCSDIR;	/* directory to begin search from */
1781205Seric # else
1791270Seric char	*SccsDir = "";
1801205Seric # endif
181157Seric bool	RealUser;		/* if set, running as real user */
182393Seric # ifdef DEBUG
183393Seric bool	Debug;			/* turn on tracing */
184393Seric # endif
185148Seric 
186148Seric main(argc, argv)
187148Seric 	int argc;
188148Seric 	char **argv;
189148Seric {
190148Seric 	register char *p;
191262Seric 	extern struct sccsprog *lookup();
1921282Seric 	register int i;
193148Seric 
194148Seric 	/*
195148Seric 	**  Detect and decode flags intended for this program.
196148Seric 	*/
197148Seric 
198200Seric 	if (argc < 2)
199148Seric 	{
2001205Seric 		fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName);
201200Seric 		exit(EX_USAGE);
202200Seric 	}
203200Seric 	argv[argc] = NULL;
204200Seric 
205262Seric 	if (lookup(argv[0]) == NULL)
206200Seric 	{
207262Seric 		while ((p = *++argv) != NULL)
208148Seric 		{
209262Seric 			if (*p != '-')
210262Seric 				break;
211262Seric 			switch (*++p)
212262Seric 			{
213262Seric 			  case 'r':		/* run as real user */
214262Seric 				setuid(getuid());
215262Seric 				RealUser++;
216262Seric 				break;
217148Seric 
2181270Seric # ifndef SCCSDIR
219262Seric 			  case 'p':		/* path of sccs files */
220262Seric 				SccsPath = ++p;
221262Seric 				break;
222148Seric 
223588Seric 			  case 'd':		/* directory to search from */
224588Seric 				SccsDir = ++p;
225588Seric 				break;
2261205Seric # endif
227588Seric 
228393Seric # ifdef DEBUG
229393Seric 			  case 'T':		/* trace */
230393Seric 				Debug++;
231393Seric 				break;
232393Seric # endif
233393Seric 
234262Seric 			  default:
2351205Seric 				usrerr("unknown option -%s", p);
236262Seric 				break;
237262Seric 			}
238148Seric 		}
239262Seric 		if (SccsPath[0] == '\0')
240262Seric 			SccsPath = ".";
241148Seric 	}
242148Seric 
243*1316Seric 	i = command(argv, FALSE, FALSE, "");
2441282Seric 	exit(i);
245200Seric }
2461282Seric /*
2471282Seric **  COMMAND -- look up and perform a command
2481282Seric **
2491282Seric **	This routine is the guts of this program.  Given an
2501282Seric **	argument vector, it looks up the "command" (argv[0])
2511282Seric **	in the configuration table and does the necessary stuff.
2521282Seric **
2531282Seric **	Parameters:
2541282Seric **		argv -- an argument vector to process.
2551282Seric **		forkflag -- if set, fork before executing the command.
256*1316Seric **		editflag -- if set, only include flags listed in the
257*1316Seric **			sccsklets field of the command descriptor.
258*1316Seric **		arg0 -- a space-seperated list of arguments to insert
259*1316Seric **			before argv.
2601282Seric **
2611282Seric **	Returns:
2621282Seric **		zero -- command executed ok.
2631282Seric **		else -- error status.
2641282Seric **
2651282Seric **	Side Effects:
2661282Seric **		none.
2671282Seric */
268157Seric 
269*1316Seric command(argv, forkflag, editflag, arg0)
270200Seric 	char **argv;
271201Seric 	bool forkflag;
272*1316Seric 	bool editflag;
273*1316Seric 	char *arg0;
274200Seric {
275200Seric 	register struct sccsprog *cmd;
276200Seric 	register char *p;
277201Seric 	register char *q;
278201Seric 	char buf[40];
279262Seric 	extern struct sccsprog *lookup();
280*1316Seric 	char *nav[1000];
281*1316Seric 	char **np;
282*1316Seric 	char **ap;
283585Seric 	register int i;
284585Seric 	extern bool unedit();
2851282Seric 	int rval = 0;
286*1316Seric 	extern char *index();
287*1316Seric 	extern char *makefile();
288200Seric 
289393Seric # ifdef DEBUG
290393Seric 	if (Debug)
291393Seric 	{
292*1316Seric 		printf("command:\n\t\"%s\"\n", arg0);
293*1316Seric 		for (np = argv; *np != NULL; np++)
294*1316Seric 			printf("\t\"%s\"\n", *np);
295393Seric 	}
296393Seric # endif
297393Seric 
298157Seric 	/*
299*1316Seric 	**  Copy arguments.
300*1316Seric 	**	Phase one -- from arg0 & if necessary argv[0].
301*1316Seric 	*/
302*1316Seric 
303*1316Seric 	np = nav;
304*1316Seric 	for (p = arg0, q = buf; *p != '\0' && *p != '/'; )
305*1316Seric 	{
306*1316Seric 		*np++ = q;
307*1316Seric 		while (*p == ' ')
308*1316Seric 			p++;
309*1316Seric 		while (*p != ' ' && *p != '\0' && *p != '/')
310*1316Seric 			*q++ = *p++;
311*1316Seric 		*q++ = '\0';
312*1316Seric 	}
313*1316Seric 	*np = NULL;
314*1316Seric 	if (nav[0] == NULL)
315*1316Seric 		*np++ = *argv++;
316*1316Seric 
317*1316Seric 	/*
318148Seric 	**  Look up command.
319*1316Seric 	**	At this point, nav[0] is the command name.
320148Seric 	*/
321148Seric 
322*1316Seric 	cmd = lookup(nav[0]);
323262Seric 	if (cmd == NULL)
324148Seric 	{
3251205Seric 		usrerr("Unknown command \"%s\"", argv[0]);
3261282Seric 		return (EX_USAGE);
327148Seric 	}
328148Seric 
329148Seric 	/*
330*1316Seric 	**  Copy remaining arguments doing editing as appropriate.
331*1316Seric 	*/
332*1316Seric 
333*1316Seric 	for (; *argv != NULL; argv++)
334*1316Seric 	{
335*1316Seric 		p = *argv;
336*1316Seric 		if (*p == '-')
337*1316Seric 		{
338*1316Seric 			if (p[1] == '\0' || !editflag || cmd->sccsklets == NULL ||
339*1316Seric 			    index(cmd->sccsklets, p[1]) != NULL)
340*1316Seric 				*np++ = p;
341*1316Seric 		}
342*1316Seric 		else
343*1316Seric 		{
344*1316Seric 			if (!bitset(NO_SDOT, cmd->sccsflags))
345*1316Seric 				p = makefile(p);
346*1316Seric 			if (p != NULL)
347*1316Seric 				*np++ = p;
348*1316Seric 		}
349*1316Seric 	}
350*1316Seric 	*np = NULL;
351*1316Seric 
352*1316Seric 	/*
353200Seric 	**  Interpret operation associated with this command.
354157Seric 	*/
355157Seric 
356200Seric 	switch (cmd->sccsoper)
357200Seric 	{
358200Seric 	  case PROG:		/* call an sccs prog */
359*1316Seric 		rval = callprog(cmd->sccspath, cmd->sccsflags, nav, forkflag);
360201Seric 		break;
361201Seric 
362201Seric 	  case CMACRO:		/* command macro */
363201Seric 		for (p = cmd->sccspath; *p != '\0'; p++)
364201Seric 		{
365*1316Seric 			q = p;
366*1316Seric 			while (*p != '\0' && *p != '/')
367*1316Seric 				p++;
368*1316Seric 			rval = command(&nav[1], *p != '\0', TRUE, q);
3691282Seric 			if (rval != 0)
3701282Seric 				break;
371201Seric 		}
3721282Seric 		break;
373157Seric 
374226Seric 	  case FIX:		/* fix a delta */
375*1316Seric 		if (strncmp(nav[1], "-r", 2) != 0)
376226Seric 		{
3771205Seric 			usrerr("-r flag needed for fix command");
3781282Seric 			rval = EX_USAGE;
379226Seric 			break;
380226Seric 		}
381*1316Seric 		rval = command(&nav[1], TRUE, TRUE, "get -k");
3821282Seric 		if (rval == 0)
383*1316Seric 			rval = command(&nav[1], TRUE, TRUE, "rmdel");
3841282Seric 		if (rval == 0)
385*1316Seric 			rval = command(&nav[2], FALSE, TRUE, "get -e -g");
3861282Seric 		break;
387226Seric 
388261Seric 	  case CLEAN:
3891282Seric 		rval = clean((int) cmd->sccspath);
390261Seric 		break;
391261Seric 
392396Seric 	  case UNEDIT:
393*1316Seric 		for (argv = np = &nav[1]; *argv != NULL; argv++)
394585Seric 		{
395*1316Seric 			if (unedit(*argv))
396*1316Seric 				*np++ = *argv;
397585Seric 		}
398*1316Seric 		*np = NULL;
399585Seric 		if (i > 0)
400*1316Seric 			rval = command(&nav[1], FALSE, FALSE, "get");
401396Seric 		break;
402396Seric 
403200Seric 	  default:
4041205Seric 		syserr("oper %d", cmd->sccsoper);
405200Seric 		exit(EX_SOFTWARE);
406200Seric 	}
4071282Seric # ifdef DEBUG
4081282Seric 	if (Debug)
4091282Seric 		printf("command: rval=%d\n", rval);
4101282Seric # endif
4111282Seric 	return (rval);
412200Seric }
413262Seric /*
414262Seric **  LOOKUP -- look up an SCCS command name.
415262Seric **
416262Seric **	Parameters:
417262Seric **		name -- the name of the command to look up.
418262Seric **
419262Seric **	Returns:
420262Seric **		ptr to command descriptor for this command.
421262Seric **		NULL if no such entry.
422262Seric **
423262Seric **	Side Effects:
424262Seric **		none.
425262Seric */
426200Seric 
427262Seric struct sccsprog *
428262Seric lookup(name)
429262Seric 	char *name;
430262Seric {
431262Seric 	register struct sccsprog *cmd;
432226Seric 
433262Seric 	for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
434262Seric 	{
435262Seric 		if (strcmp(cmd->sccsname, name) == 0)
436262Seric 			return (cmd);
437262Seric 	}
438262Seric 	return (NULL);
439262Seric }
4401282Seric /*
4411282Seric **  CALLPROG -- call a program
4421282Seric **
443*1316Seric **	Used to call the SCCS programs.
4441282Seric **
4451282Seric **	Parameters:
4461282Seric **		progpath -- pathname of the program to call.
4471282Seric **		flags -- status flags from the command descriptors.
4481282Seric **		argv -- an argument vector to pass to the program.
4491282Seric **		forkflag -- if true, fork before calling, else just
4501282Seric **			exec.
4511282Seric **
4521282Seric **	Returns:
4531282Seric **		The exit status of the program.
4541282Seric **		Nothing if forkflag == FALSE.
4551282Seric **
4561282Seric **	Side Effects:
4571282Seric **		Can exit if forkflag == FALSE.
4581282Seric */
459226Seric 
460200Seric callprog(progpath, flags, argv, forkflag)
461200Seric 	char *progpath;
462200Seric 	short flags;
463200Seric 	char **argv;
464200Seric 	bool forkflag;
465200Seric {
466200Seric 	register int i;
467201Seric 	auto int st;
468200Seric 
469*1316Seric # ifdef DEBUG
470*1316Seric 	if (Debug)
471*1316Seric 	{
472*1316Seric 		printf("callprog:\n");
473*1316Seric 		for (i = 0; argv[i] != NULL; i++)
474*1316Seric 			printf("\t\"%s\"\n", argv[i]);
475*1316Seric 	}
476*1316Seric # endif
477*1316Seric 
478200Seric 	if (*argv == NULL)
479200Seric 		return (-1);
480200Seric 
481157Seric 	/*
482226Seric 	**  Fork if appropriate.
483148Seric 	*/
484148Seric 
485200Seric 	if (forkflag)
486200Seric 	{
487393Seric # ifdef DEBUG
488393Seric 		if (Debug)
489393Seric 			printf("Forking\n");
490393Seric # endif
491200Seric 		i = fork();
492200Seric 		if (i < 0)
493200Seric 		{
4941205Seric 			syserr("cannot fork");
495200Seric 			exit(EX_OSERR);
496200Seric 		}
497200Seric 		else if (i > 0)
498201Seric 		{
499201Seric 			wait(&st);
5001282Seric 			if ((st & 0377) == 0)
5011282Seric 				st = (st >> 8) & 0377;
502201Seric 			return (st);
503201Seric 		}
504200Seric 	}
505200Seric 
506200Seric 	/*
507200Seric 	**  Set protection as appropriate.
508200Seric 	*/
509200Seric 
510200Seric 	if (bitset(REALUSER, flags))
511200Seric 		setuid(getuid());
512226Seric 
513200Seric 	/*
514226Seric 	**  Call real SCCS program.
515200Seric 	*/
516200Seric 
517226Seric 	execv(progpath, argv);
5181205Seric 	syserr("cannot execute %s", progpath);
519148Seric 	exit(EX_UNAVAILABLE);
520148Seric }
521586Seric /*
522586Seric **  MAKEFILE -- make filename of SCCS file
523586Seric **
524586Seric **	If the name passed is already the name of an SCCS file,
525586Seric **	just return it.  Otherwise, munge the name into the name
526586Seric **	of the actual SCCS file.
527586Seric **
528586Seric **	There are cases when it is not clear what you want to
529586Seric **	do.  For example, if SccsPath is an absolute pathname
530586Seric **	and the name given is also an absolute pathname, we go
531586Seric **	for SccsPath (& only use the last component of the name
532586Seric **	passed) -- this is important for security reasons (if
533586Seric **	sccs is being used as a setuid front end), but not
534586Seric **	particularly intuitive.
535586Seric **
536586Seric **	Parameters:
537586Seric **		name -- the file name to be munged.
538586Seric **
539586Seric **	Returns:
540586Seric **		The pathname of the sccs file.
541586Seric **		NULL on error.
542586Seric **
543586Seric **	Side Effects:
544586Seric **		none.
545586Seric */
546148Seric 
547148Seric char *
548148Seric makefile(name)
549148Seric 	char *name;
550148Seric {
551148Seric 	register char *p;
552148Seric 	register char c;
553148Seric 	char buf[512];
554588Seric 	struct stat stbuf;
555148Seric 	extern char *malloc();
556586Seric 	extern char *rindex();
557588Seric 	extern bool safepath();
558587Seric 	extern bool isdir();
559587Seric 	register char *q;
560148Seric 
561586Seric 	p = rindex(name, '/');
562586Seric 	if (p == NULL)
563586Seric 		p = name;
564586Seric 	else
565586Seric 		p++;
566586Seric 
567148Seric 	/*
568588Seric 	**  Check to see that the path is "safe", i.e., that we
569588Seric 	**  are not letting some nasty person use the setuid part
570588Seric 	**  of this program to look at or munge some presumably
571588Seric 	**  hidden files.
572148Seric 	*/
573148Seric 
574588Seric 	if (SccsDir[0] == '/' && !safepath(name))
575588Seric 		return (NULL);
576586Seric 
577586Seric 	/*
578588Seric 	**  Create the base pathname.
579586Seric 	*/
580586Seric 
581588Seric 	if (SccsDir[0] != '\0' && name[0] != '/' && strncmp(name, "./", 2) != 0)
582148Seric 	{
583588Seric 		strcpy(buf, SccsDir);
584586Seric 		strcat(buf, "/");
585586Seric 	}
586586Seric 	else
587586Seric 		strcpy(buf, "");
588587Seric 	strncat(buf, name, p - name);
589587Seric 	q = &buf[strlen(buf)];
590587Seric 	strcpy(q, p);
591587Seric 	if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
592586Seric 	{
593588Seric 		strcpy(q, SccsPath);
594588Seric 		strcat(buf, "/s.");
595586Seric 		strcat(buf, p);
596586Seric 	}
597148Seric 
598588Seric 	if (strcmp(buf, name) == 0)
599588Seric 		p = name;
600588Seric 	else
601148Seric 	{
602588Seric 		p = malloc(strlen(buf) + 1);
603588Seric 		if (p == NULL)
604588Seric 		{
605588Seric 			perror("Sccs: no mem");
606588Seric 			exit(EX_OSERR);
607588Seric 		}
608588Seric 		strcpy(p, buf);
609148Seric 	}
610148Seric 	return (p);
611148Seric }
612261Seric /*
613587Seric **  ISDIR -- return true if the argument is a directory.
614587Seric **
615587Seric **	Parameters:
616587Seric **		name -- the pathname of the file to check.
617587Seric **
618587Seric **	Returns:
619587Seric **		TRUE if 'name' is a directory, FALSE otherwise.
620587Seric **
621587Seric **	Side Effects:
622587Seric **		none.
623587Seric */
624587Seric 
625587Seric bool
626587Seric isdir(name)
627587Seric 	char *name;
628587Seric {
629587Seric 	struct stat stbuf;
630587Seric 
631587Seric 	return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
632587Seric }
633587Seric /*
634586Seric **  SAFEPATH -- determine whether a pathname is "safe"
635586Seric **
636586Seric **	"Safe" pathnames only allow you to get deeper into the
637586Seric **	directory structure, i.e., full pathnames and ".." are
638586Seric **	not allowed.
639586Seric **
640586Seric **	Parameters:
641586Seric **		p -- the name to check.
642586Seric **
643586Seric **	Returns:
644586Seric **		TRUE -- if the path is safe.
645586Seric **		FALSE -- if the path is not safe.
646586Seric **
647586Seric **	Side Effects:
648586Seric **		Prints a message if the path is not safe.
649586Seric */
650586Seric 
651586Seric bool
652586Seric safepath(p)
653586Seric 	register char *p;
654586Seric {
655586Seric 	extern char *index();
656586Seric 
657586Seric 	if (*p != '/')
658586Seric 	{
659586Seric 		while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
660586Seric 		{
661586Seric 			p = index(p, '/');
662586Seric 			if (p == NULL)
663586Seric 				return (TRUE);
664586Seric 			p++;
665586Seric 		}
666586Seric 	}
667586Seric 
668586Seric 	printf("You may not use full pathnames or \"..\"\n");
669586Seric 	return (FALSE);
670586Seric }
671586Seric /*
672261Seric **  CLEAN -- clean out recreatable files
673261Seric **
674261Seric **	Any file for which an "s." file exists but no "p." file
675261Seric **	exists in the current directory is purged.
676261Seric **
677261Seric **	Parameters:
678819Seric **		tells whether this came from a "clean", "info", or
679819Seric **		"check" command.
680261Seric **
681261Seric **	Returns:
682261Seric **		none.
683261Seric **
684261Seric **	Side Effects:
685819Seric **		Removes files in the current directory.
686819Seric **		Prints information regarding files being edited.
687819Seric **		Exits if a "check" command.
688261Seric */
689261Seric 
690819Seric clean(mode)
691819Seric 	int mode;
692261Seric {
693261Seric 	struct direct dir;
694261Seric 	struct stat stbuf;
695261Seric 	char buf[100];
696394Seric 	char pline[120];
697346Seric 	register FILE *dirfd;
698346Seric 	register char *basefile;
699351Seric 	bool gotedit;
700394Seric 	FILE *pfp;
701261Seric 
7021207Seric 	strcpy(buf, SccsDir);
7031207Seric 	if (buf[0] != '\0')
7041207Seric 		strcat(buf, "/");
7051207Seric 	strcat(buf, SccsPath);
7061207Seric 	dirfd = fopen(buf, "r");
707261Seric 	if (dirfd == NULL)
708261Seric 	{
7091207Seric 		usrerr("cannot open %s", buf);
7101282Seric 		return (EX_NOINPUT);
711261Seric 	}
712261Seric 
713261Seric 	/*
714261Seric 	**  Scan the SCCS directory looking for s. files.
715261Seric 	*/
716261Seric 
717351Seric 	gotedit = FALSE;
718261Seric 	while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
719261Seric 	{
720568Seric 		if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
721261Seric 			continue;
722261Seric 
723261Seric 		/* got an s. file -- see if the p. file exists */
7241207Seric 		strcpy(buf, SccsDir);
7251207Seric 		if (buf[0] != '\0')
7261207Seric 			strcat(buf, "/");
7271207Seric 		strcat(buf, SccsPath);
728261Seric 		strcat(buf, "/p.");
729346Seric 		basefile = &buf[strlen(buf)];
730568Seric 		strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
731346Seric 		basefile[sizeof dir.d_name - 2] = '\0';
732394Seric 		pfp = fopen(buf, "r");
733394Seric 		if (pfp != NULL)
734346Seric 		{
735394Seric 			while (fgets(pline, sizeof pline, pfp) != NULL)
736416Seric 				printf("%12s: being edited: %s", basefile, pline);
737394Seric 			fclose(pfp);
738351Seric 			gotedit = TRUE;
739261Seric 			continue;
740346Seric 		}
741261Seric 
742261Seric 		/* the s. file exists and no p. file exists -- unlink the g-file */
743819Seric 		if (mode == CLEANC)
744346Seric 		{
745568Seric 			strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
746346Seric 			buf[sizeof dir.d_name - 2] = '\0';
747346Seric 			unlink(buf);
748346Seric 		}
749261Seric 	}
750261Seric 
751261Seric 	fclose(dirfd);
752819Seric 	if (!gotedit && mode == INFOC)
753416Seric 		printf("Nothing being edited\n");
754819Seric 	if (mode == CHECKC)
755819Seric 		exit(gotedit);
7561282Seric 	return (EX_OK);
757261Seric }
758396Seric /*
759396Seric **  UNEDIT -- unedit a file
760396Seric **
761396Seric **	Checks to see that the current user is actually editting
762396Seric **	the file and arranges that s/he is not editting it.
763396Seric **
764396Seric **	Parameters:
765416Seric **		fn -- the name of the file to be unedited.
766396Seric **
767396Seric **	Returns:
768585Seric **		TRUE -- if the file was successfully unedited.
769585Seric **		FALSE -- if the file was not unedited for some
770585Seric **			reason.
771396Seric **
772396Seric **	Side Effects:
773396Seric **		fn is removed
774396Seric **		entries are removed from pfile.
775396Seric */
776396Seric 
777585Seric bool
778396Seric unedit(fn)
779396Seric 	char *fn;
780396Seric {
781396Seric 	register FILE *pfp;
782396Seric 	char *pfn;
783396Seric 	static char tfn[] = "/tmp/sccsXXXXX";
784396Seric 	FILE *tfp;
785396Seric 	register char *p;
786396Seric 	register char *q;
787396Seric 	bool delete = FALSE;
788396Seric 	bool others = FALSE;
789396Seric 	char *myname;
790396Seric 	extern char *getlogin();
791396Seric 	struct pfile *pent;
792396Seric 	extern struct pfile *getpfile();
793396Seric 	char buf[120];
794*1316Seric 	extern char *makefile();
795828Seric # ifdef UIDUSER
796828Seric 	struct passwd *pw;
797828Seric 	extern struct passwd *getpwuid();
798828Seric # endif UIDUSER
799396Seric 
800396Seric 	/* make "s." filename & find the trailing component */
801396Seric 	pfn = makefile(fn);
802586Seric 	if (pfn == NULL)
803586Seric 		return (FALSE);
804586Seric 	q = rindex(pfn, '/');
805586Seric 	if (q == NULL)
806586Seric 		q = &pfn[-1];
807586Seric 	if (q[1] != 's' || q[2] != '.')
808396Seric 	{
8091205Seric 		usrerr("bad file name \"%s\"", fn);
810585Seric 		return (FALSE);
811396Seric 	}
812396Seric 
813396Seric 	/* turn "s." into "p." */
814396Seric 	*++q = 'p';
815396Seric 
816396Seric 	pfp = fopen(pfn, "r");
817396Seric 	if (pfp == NULL)
818396Seric 	{
819416Seric 		printf("%12s: not being edited\n", fn);
820585Seric 		return (FALSE);
821396Seric 	}
822396Seric 
823396Seric 	/*
824396Seric 	**  Copy p-file to temp file, doing deletions as needed.
825396Seric 	*/
826396Seric 
827396Seric 	mktemp(tfn);
828396Seric 	tfp = fopen(tfn, "w");
829396Seric 	if (tfp == NULL)
830396Seric 	{
8311205Seric 		usrerr("cannot create \"%s\"", tfn);
832396Seric 		exit(EX_OSERR);
833396Seric 	}
834396Seric 
835828Seric # ifdef UIDUSER
836828Seric 	pw = getpwuid(getuid());
837828Seric 	if (pw == NULL)
838828Seric 	{
8391205Seric 		syserr("who are you? (uid=%d)", getuid());
840828Seric 		exit(EX_OSERR);
841828Seric 	}
842828Seric 	myname = pw->pw_name;
843828Seric # else
844396Seric 	myname = getlogin();
845828Seric # endif UIDUSER
846396Seric 	while ((pent = getpfile(pfp)) != NULL)
847396Seric 	{
848396Seric 		if (strcmp(pent->p_user, myname) == 0)
849396Seric 		{
850396Seric 			/* a match */
851396Seric 			delete++;
852396Seric 		}
853396Seric 		else
854396Seric 		{
855396Seric 			fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
856396Seric 			    pent->p_nsid, pent->p_user, pent->p_date,
857396Seric 			    pent->p_time);
858396Seric 			others++;
859396Seric 		}
860396Seric 	}
861396Seric 
862396Seric 	/* do final cleanup */
863396Seric 	if (others)
864396Seric 	{
865396Seric 		if (freopen(tfn, "r", tfp) == NULL)
866396Seric 		{
8671205Seric 			syserr("cannot reopen \"%s\"", tfn);
868396Seric 			exit(EX_OSERR);
869396Seric 		}
870396Seric 		if (freopen(pfn, "w", pfp) == NULL)
871396Seric 		{
8721205Seric 			usrerr("cannot create \"%s\"", pfn);
873585Seric 			return (FALSE);
874396Seric 		}
875396Seric 		while (fgets(buf, sizeof buf, tfp) != NULL)
876396Seric 			fputs(buf, pfp);
877396Seric 	}
878396Seric 	else
879396Seric 	{
880396Seric 		unlink(pfn);
881396Seric 	}
882396Seric 	fclose(tfp);
883396Seric 	fclose(pfp);
884396Seric 	unlink(tfn);
885396Seric 
886396Seric 	if (delete)
887396Seric 	{
888396Seric 		unlink(fn);
889396Seric 		printf("%12s: removed\n", fn);
890585Seric 		return (TRUE);
891396Seric 	}
892396Seric 	else
893396Seric 	{
894416Seric 		printf("%12s: not being edited by you\n", fn);
895585Seric 		return (FALSE);
896396Seric 	}
897396Seric }
898396Seric /*
899396Seric **  GETPFILE -- get an entry from the p-file
900396Seric **
901396Seric **	Parameters:
902396Seric **		pfp -- p-file file pointer
903396Seric **
904396Seric **	Returns:
905396Seric **		pointer to p-file struct for next entry
906396Seric **		NULL on EOF or error
907396Seric **
908396Seric **	Side Effects:
909396Seric **		Each call wipes out results of previous call.
910396Seric */
911396Seric 
912396Seric struct pfile *
913396Seric getpfile(pfp)
914396Seric 	FILE *pfp;
915396Seric {
916396Seric 	static struct pfile ent;
917396Seric 	static char buf[120];
918396Seric 	register char *p;
919396Seric 	extern char *nextfield();
920396Seric 
921396Seric 	if (fgets(buf, sizeof buf, pfp) == NULL)
922396Seric 		return (NULL);
923396Seric 
924396Seric 	ent.p_osid = p = buf;
925396Seric 	ent.p_nsid = p = nextfield(p);
926396Seric 	ent.p_user = p = nextfield(p);
927396Seric 	ent.p_date = p = nextfield(p);
928396Seric 	ent.p_time = p = nextfield(p);
929396Seric 	if (p == NULL || nextfield(p) != NULL)
930396Seric 		return (NULL);
931396Seric 
932396Seric 	return (&ent);
933396Seric }
934396Seric 
935396Seric 
936396Seric char *
937396Seric nextfield(p)
938396Seric 	register char *p;
939396Seric {
940396Seric 	if (p == NULL || *p == '\0')
941396Seric 		return (NULL);
942396Seric 	while (*p != ' ' && *p != '\n' && *p != '\0')
943396Seric 		p++;
944396Seric 	if (*p == '\n' || *p == '\0')
945396Seric 	{
946396Seric 		*p = '\0';
947396Seric 		return (NULL);
948396Seric 	}
949396Seric 	*p++ = '\0';
950396Seric 	return (p);
951396Seric }
9521205Seric /*
9531205Seric **  USRERR -- issue user-level error
9541205Seric **
9551205Seric **	Parameters:
9561205Seric **		f -- format string.
9571205Seric **		p1-p3 -- parameters to a printf.
9581205Seric **
9591205Seric **	Returns:
9601205Seric **		-1
9611205Seric **
9621205Seric **	Side Effects:
9631205Seric **		none.
9641205Seric */
9651205Seric 
9661205Seric usrerr(f, p1, p2, p3)
9671205Seric 	char *f;
9681205Seric {
9691205Seric 	fprintf(stderr, "\n%s: ", MyName);
9701205Seric 	fprintf(stderr, f, p1, p2, p3);
9711205Seric 	fprintf(stderr, "\n");
9721205Seric 
9731205Seric 	return (-1);
9741205Seric }
9751205Seric /*
9761205Seric **  SYSERR -- print system-generated error.
9771205Seric **
9781205Seric **	Parameters:
9791205Seric **		f -- format string to a printf.
9801205Seric **		p1, p2, p3 -- parameters to f.
9811205Seric **
9821205Seric **	Returns:
9831205Seric **		never.
9841205Seric **
9851205Seric **	Side Effects:
9861205Seric **		none.
9871205Seric */
9881205Seric 
9891205Seric syserr(f, p1, p2, p3)
9901205Seric 	char *f;
9911205Seric {
9921205Seric 	extern int errno;
9931205Seric 
9941205Seric 	fprintf(stderr, "\n%s SYSERR: ", MyName);
9951205Seric 	fprintf(stderr, f, p1, p2, p3);
9961205Seric 	fprintf(stderr, "\n");
9971205Seric 	if (errno == 0)
9981205Seric 		exit(EX_SOFTWARE);
9991205Seric 	else
10001205Seric 	{
10011205Seric 		perror(0);
10021205Seric 		exit(EX_OSERR);
10031205Seric 	}
10041205Seric }
1005