xref: /original-bsd/usr.bin/sccs/sccs.c (revision d25e1985)
1 # include <stdio.h>
2 # include <sys/types.h>
3 # include <sys/stat.h>
4 # include <sys/dir.h>
5 # include <sysexits.h>
6 # include <whoami.h>
7 
8 /*
9 **  SCCS.C -- human-oriented front end to the SCCS system.
10 **
11 **	Without trying to add any functionality to speak of, this
12 **	program tries to make SCCS a little more accessible to human
13 **	types.  The main thing it does is automatically put the
14 **	string "SCCS/s." on the front of names.  Also, it has a
15 **	couple of things that are designed to shorten frequent
16 **	combinations, e.g., "delget" which expands to a "delta"
17 **	and a "get".
18 **
19 **	This program can also function as a setuid front end.
20 **	To do this, you should copy the source, renaming it to
21 **	whatever you want, e.g., "syssccs".  Change any defaults
22 **	in the program (e.g., syssccs might default -d to
23 **	"/usr/src/sys").  Then recompile and put the result
24 **	as setuid to whomever you want.  In this mode, sccs
25 **	knows to not run setuid for certain programs in order
26 **	to preserve security, and so forth.
27 **
28 **	Usage:
29 **		sccs [flags] command [args]
30 **
31 **	Flags:
32 **		-d<dir>		<dir> represents a directory to search
33 **				out of.  It should be a full pathname
34 **				for general usage.  E.g., if <dir> is
35 **				"/usr/src/sys", then a reference to the
36 **				file "dev/bio.c" becomes a reference to
37 **				"/usr/src/sys/dev/bio.c".
38 **		-p<path>	prepends <path> to the final component
39 **				of the pathname.  By default, this is
40 **				"SCCS".  For example, in the -d example
41 **				above, the path then gets modified to
42 **				"/usr/src/sys/dev/SCCS/s.bio.c".  In
43 **				more common usage (without the -d flag),
44 **				"prog.c" would get modified to
45 **				"SCCS/s.prog.c".  In both cases, the
46 **				"s." gets automatically prepended.
47 **		-r		run as the real user.
48 **
49 **	Commands:
50 **		admin,
51 **		get,
52 **		delta,
53 **		rmdel,
54 **		chghist,
55 **		etc.		Straight out of SCCS; only difference
56 **				is that pathnames get modified as
57 **				described above.
58 **		edit		Macro for "get -e".
59 **		unedit		Removes a file being edited, knowing
60 **				about p-files, etc.
61 **		delget		Macro for "delta" followed by "get".
62 **		deledit		Macro for "delta" followed by "get -e".
63 **		info		Tell what files being edited.
64 **		clean		Remove all files that can be
65 **				regenerated from SCCS files.
66 **		status		Like info, but return exit status, for
67 **				use in makefiles.
68 **		fix		Remove a top delta & reedit, but save
69 **				the previous changes in that delta.
70 **
71 **	Compilation Flags:
72 **		UIDUSER -- determine who the user is by looking at the
73 **			uid rather than the login name -- for machines
74 **			where SCCS gets the user in this way.
75 **
76 **	Compilation Instructions:
77 **		cc -O -n -s sccs.c
78 **
79 **	Author:
80 **		Eric Allman, UCB/INGRES
81 */
82 
83 # ifdef CSVAX
84 # define UIDUSER
85 # endif
86 
87 static char SccsId[] = "@(#)sccs.c	1.26 09/02/80";
88 
89 # define bitset(bit, word)	((bit) & (word))
90 
91 typedef char	bool;
92 # define TRUE	1
93 # define FALSE	0
94 
95 # ifdef UIDUSER
96 # include <pwd.h>
97 # endif UIDUSER
98 
99 struct sccsprog
100 {
101 	char	*sccsname;	/* name of SCCS routine */
102 	short	sccsoper;	/* opcode, see below */
103 	short	sccsflags;	/* flags, see below */
104 	char	*sccspath;	/* pathname of binary implementing */
105 };
106 
107 /* values for sccsoper */
108 # define PROG		0	/* call a program */
109 # define CMACRO		1	/* command substitution macro */
110 # define FIX		2	/* fix a delta */
111 # define CLEAN		3	/* clean out recreatable files */
112 # define UNEDIT		4	/* unedit a file */
113 
114 /* bits for sccsflags */
115 # define NO_SDOT	0001	/* no s. on front of args */
116 # define REALUSER	0002	/* protected (e.g., admin) */
117 
118 /* modes for the "clean", "info", "check" ops */
119 # define CLEANC		0	/* clean command */
120 # define INFOC		1	/* info command */
121 # define CHECKC		2	/* check command */
122 
123 # ifdef CSVAX
124 # define PROGPATH(name)	"/usr/local/name"
125 # endif CSVAX
126 
127 # ifndef PROGPATH
128 # define PROGPATH(name)	"/usr/sccs/name"
129 # endif PROGPATH
130 
131 struct sccsprog SccsProg[] =
132 {
133 	"admin",	PROG,	REALUSER,		PROGPATH(admin),
134 	"chghist",	PROG,	0,			PROGPATH(rmdel),
135 	"comb",		PROG,	0,			PROGPATH(comb),
136 	"delta",	PROG,	0,			PROGPATH(delta),
137 	"get",		PROG,	0,			PROGPATH(get),
138 	"help",		PROG,	NO_SDOT,		PROGPATH(help),
139 	"prt",		PROG,	0,			PROGPATH(prt),
140 	"rmdel",	PROG,	REALUSER,		PROGPATH(rmdel),
141 	"what",		PROG,	NO_SDOT,		PROGPATH(what),
142 	"edit",		CMACRO,	0,			"get -e",
143 	"delget",	CMACRO,	0,			"delta/get",
144 	"deledit",	CMACRO,	0,			"delta/get -e",
145 	"fix",		FIX,	0,			NULL,
146 	"clean",	CLEAN,	REALUSER,		(char *) CLEANC,
147 	"info",		CLEAN,	REALUSER,		(char *) INFOC,
148 	"check",	CLEAN,	REALUSER,		(char *) CHECKC,
149 	"unedit",	UNEDIT,	0,			NULL,
150 	NULL,		-1,	0,			NULL
151 };
152 
153 struct pfile
154 {
155 	char	*p_osid;	/* old SID */
156 	char	*p_nsid;	/* new SID */
157 	char	*p_user;	/* user who did edit */
158 	char	*p_date;	/* date of get */
159 	char	*p_time;	/* time of get */
160 };
161 
162 char	*SccsPath = "SCCS";	/* pathname of SCCS files */
163 char	*SccsDir = "";		/* directory to begin search from */
164 bool	RealUser;		/* if set, running as real user */
165 # ifdef DEBUG
166 bool	Debug;			/* turn on tracing */
167 # endif
168 
169 main(argc, argv)
170 	int argc;
171 	char **argv;
172 {
173 	register char *p;
174 	extern struct sccsprog *lookup();
175 
176 	/*
177 	**  Detect and decode flags intended for this program.
178 	*/
179 
180 	if (argc < 2)
181 	{
182 		fprintf(stderr, "Usage: sccs [flags] command [flags]\n");
183 		exit(EX_USAGE);
184 	}
185 	argv[argc] = NULL;
186 
187 	if (lookup(argv[0]) == NULL)
188 	{
189 		while ((p = *++argv) != NULL)
190 		{
191 			if (*p != '-')
192 				break;
193 			switch (*++p)
194 			{
195 			  case 'r':		/* run as real user */
196 				setuid(getuid());
197 				RealUser++;
198 				break;
199 
200 			  case 'p':		/* path of sccs files */
201 				SccsPath = ++p;
202 				break;
203 
204 			  case 'd':		/* directory to search from */
205 				SccsDir = ++p;
206 				break;
207 
208 # ifdef DEBUG
209 			  case 'T':		/* trace */
210 				Debug++;
211 				break;
212 # endif
213 
214 			  default:
215 				fprintf(stderr, "Sccs: unknown option -%s\n", p);
216 				break;
217 			}
218 		}
219 		if (SccsPath[0] == '\0')
220 			SccsPath = ".";
221 	}
222 
223 	command(argv, FALSE);
224 	exit(EX_OK);
225 }
226 
227 command(argv, forkflag)
228 	char **argv;
229 	bool forkflag;
230 {
231 	register struct sccsprog *cmd;
232 	register char *p;
233 	register char *q;
234 	char buf[40];
235 	extern struct sccsprog *lookup();
236 	char *nav[200];
237 	char **avp;
238 	register int i;
239 	extern bool unedit();
240 
241 # ifdef DEBUG
242 	if (Debug)
243 	{
244 		printf("command:\n");
245 		for (avp = argv; *avp != NULL; avp++)
246 			printf("    \"%s\"\n", *avp);
247 	}
248 # endif
249 
250 	/*
251 	**  Look up command.
252 	**	At this point, argv points to the command name.
253 	*/
254 
255 	cmd = lookup(argv[0]);
256 	if (cmd == NULL)
257 	{
258 		fprintf(stderr, "Sccs: Unknown command \"%s\"\n", argv[0]);
259 		exit(EX_USAGE);
260 	}
261 
262 	/*
263 	**  Interpret operation associated with this command.
264 	*/
265 
266 	switch (cmd->sccsoper)
267 	{
268 	  case PROG:		/* call an sccs prog */
269 		callprog(cmd->sccspath, cmd->sccsflags, argv, forkflag);
270 		break;
271 
272 	  case CMACRO:		/* command macro */
273 		for (p = cmd->sccspath; *p != '\0'; p++)
274 		{
275 			avp = nav;
276 			*avp++ = buf;
277 			for (q = buf; *p != '/' && *p != '\0'; p++, q++)
278 			{
279 				if (*p == ' ')
280 				{
281 					*q = '\0';
282 					*avp++ = &q[1];
283 				}
284 				else
285 					*q = *p;
286 			}
287 			*q = '\0';
288 			*avp = NULL;
289 			xcommand(&argv[1], *p != '\0', nav[0], nav[1], nav[2],
290 				 nav[3], nav[4], nav[5], nav[6]);
291 		}
292 		fprintf(stderr, "Sccs internal error: CMACRO\n");
293 		exit(EX_SOFTWARE);
294 
295 	  case FIX:		/* fix a delta */
296 		if (strncmp(argv[1], "-r", 2) != 0)
297 		{
298 			fprintf(stderr, "Sccs: -r flag needed for fix command\n");
299 			break;
300 		}
301 		xcommand(&argv[1], TRUE, "get", "-k", NULL);
302 		xcommand(&argv[1], TRUE, "rmdel", NULL);
303 		xcommand(&argv[2], FALSE, "get", "-e", "-g", NULL);
304 		fprintf(stderr, "Sccs internal error: FIX\n");
305 		exit(EX_SOFTWARE);
306 
307 	  case CLEAN:
308 		clean((int) cmd->sccspath);
309 		break;
310 
311 	  case UNEDIT:
312 		i = 0;
313 		for (avp = &argv[1]; *avp != NULL; avp++)
314 		{
315 			if (unedit(*avp))
316 				nav[i++] = *avp;
317 		}
318 		nav[i] = NULL;
319 		if (i > 0)
320 			xcommand(nav, FALSE, "get", NULL);
321 		break;
322 
323 	  default:
324 		fprintf(stderr, "Sccs internal error: oper %d\n", cmd->sccsoper);
325 		exit(EX_SOFTWARE);
326 	}
327 }
328 /*
329 **  LOOKUP -- look up an SCCS command name.
330 **
331 **	Parameters:
332 **		name -- the name of the command to look up.
333 **
334 **	Returns:
335 **		ptr to command descriptor for this command.
336 **		NULL if no such entry.
337 **
338 **	Side Effects:
339 **		none.
340 */
341 
342 struct sccsprog *
343 lookup(name)
344 	char *name;
345 {
346 	register struct sccsprog *cmd;
347 
348 	for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
349 	{
350 		if (strcmp(cmd->sccsname, name) == 0)
351 			return (cmd);
352 	}
353 	return (NULL);
354 }
355 
356 
357 xcommand(argv, forkflag, arg0)
358 	char **argv;
359 	bool forkflag;
360 	char *arg0;
361 {
362 	register char **av;
363 	char *newargv[1000];
364 	register char **np;
365 
366 	np = newargv;
367 	for (av = &arg0; *av != NULL; av++)
368 		*np++ = *av;
369 	for (av = argv; *av != NULL; av++)
370 		*np++ = *av;
371 	*np = NULL;
372 	command(newargv, forkflag);
373 }
374 
375 callprog(progpath, flags, argv, forkflag)
376 	char *progpath;
377 	short flags;
378 	char **argv;
379 	bool forkflag;
380 {
381 	register char *p;
382 	register char **av;
383 	extern char *makefile();
384 	register int i;
385 	auto int st;
386 	register char **nav;
387 
388 	if (*argv == NULL)
389 		return (-1);
390 
391 	/*
392 	**  Fork if appropriate.
393 	*/
394 
395 	if (forkflag)
396 	{
397 # ifdef DEBUG
398 		if (Debug)
399 			printf("Forking\n");
400 # endif
401 		i = fork();
402 		if (i < 0)
403 		{
404 			fprintf(stderr, "Sccs: cannot fork");
405 			exit(EX_OSERR);
406 		}
407 		else if (i > 0)
408 		{
409 			wait(&st);
410 			return (st);
411 		}
412 	}
413 
414 	/*
415 	**  Build new argument vector.
416 	*/
417 
418 	/* copy program filename arguments and flags */
419 	nav = &argv[1];
420 	av = argv;
421 	while ((p = *++av) != NULL)
422 	{
423 		if (!bitset(NO_SDOT, flags) && *p != '-')
424 			*nav = makefile(p);
425 		else
426 			*nav = p;
427 		if (*nav != NULL)
428 			nav++;
429 	}
430 	*nav = NULL;
431 
432 	/*
433 	**  Set protection as appropriate.
434 	*/
435 
436 	if (bitset(REALUSER, flags))
437 		setuid(getuid());
438 
439 	/*
440 	**  Call real SCCS program.
441 	*/
442 
443 	execv(progpath, argv);
444 	fprintf(stderr, "Sccs: cannot execute ");
445 	perror(progpath);
446 	exit(EX_UNAVAILABLE);
447 }
448 /*
449 **  MAKEFILE -- make filename of SCCS file
450 **
451 **	If the name passed is already the name of an SCCS file,
452 **	just return it.  Otherwise, munge the name into the name
453 **	of the actual SCCS file.
454 **
455 **	There are cases when it is not clear what you want to
456 **	do.  For example, if SccsPath is an absolute pathname
457 **	and the name given is also an absolute pathname, we go
458 **	for SccsPath (& only use the last component of the name
459 **	passed) -- this is important for security reasons (if
460 **	sccs is being used as a setuid front end), but not
461 **	particularly intuitive.
462 **
463 **	Parameters:
464 **		name -- the file name to be munged.
465 **
466 **	Returns:
467 **		The pathname of the sccs file.
468 **		NULL on error.
469 **
470 **	Side Effects:
471 **		none.
472 */
473 
474 char *
475 makefile(name)
476 	char *name;
477 {
478 	register char *p;
479 	register char c;
480 	char buf[512];
481 	struct stat stbuf;
482 	extern char *malloc();
483 	extern char *rindex();
484 	extern bool safepath();
485 	extern bool isdir();
486 	register char *q;
487 
488 	p = rindex(name, '/');
489 	if (p == NULL)
490 		p = name;
491 	else
492 		p++;
493 
494 	/*
495 	**  Check to see that the path is "safe", i.e., that we
496 	**  are not letting some nasty person use the setuid part
497 	**  of this program to look at or munge some presumably
498 	**  hidden files.
499 	*/
500 
501 	if (SccsDir[0] == '/' && !safepath(name))
502 		return (NULL);
503 
504 	/*
505 	**  Create the base pathname.
506 	*/
507 
508 	if (SccsDir[0] != '\0' && name[0] != '/' && strncmp(name, "./", 2) != 0)
509 	{
510 		strcpy(buf, SccsDir);
511 		strcat(buf, "/");
512 	}
513 	else
514 		strcpy(buf, "");
515 	strncat(buf, name, p - name);
516 	q = &buf[strlen(buf)];
517 	strcpy(q, p);
518 	if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
519 	{
520 		strcpy(q, SccsPath);
521 		strcat(buf, "/s.");
522 		strcat(buf, p);
523 	}
524 
525 	if (strcmp(buf, name) == 0)
526 		p = name;
527 	else
528 	{
529 		p = malloc(strlen(buf) + 1);
530 		if (p == NULL)
531 		{
532 			perror("Sccs: no mem");
533 			exit(EX_OSERR);
534 		}
535 		strcpy(p, buf);
536 	}
537 	return (p);
538 }
539 /*
540 **  ISDIR -- return true if the argument is a directory.
541 **
542 **	Parameters:
543 **		name -- the pathname of the file to check.
544 **
545 **	Returns:
546 **		TRUE if 'name' is a directory, FALSE otherwise.
547 **
548 **	Side Effects:
549 **		none.
550 */
551 
552 bool
553 isdir(name)
554 	char *name;
555 {
556 	struct stat stbuf;
557 
558 	return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
559 }
560 /*
561 **  SAFEPATH -- determine whether a pathname is "safe"
562 **
563 **	"Safe" pathnames only allow you to get deeper into the
564 **	directory structure, i.e., full pathnames and ".." are
565 **	not allowed.
566 **
567 **	Parameters:
568 **		p -- the name to check.
569 **
570 **	Returns:
571 **		TRUE -- if the path is safe.
572 **		FALSE -- if the path is not safe.
573 **
574 **	Side Effects:
575 **		Prints a message if the path is not safe.
576 */
577 
578 bool
579 safepath(p)
580 	register char *p;
581 {
582 	extern char *index();
583 
584 	if (*p != '/')
585 	{
586 		while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
587 		{
588 			p = index(p, '/');
589 			if (p == NULL)
590 				return (TRUE);
591 			p++;
592 		}
593 	}
594 
595 	printf("You may not use full pathnames or \"..\"\n");
596 	return (FALSE);
597 }
598 /*
599 **  CLEAN -- clean out recreatable files
600 **
601 **	Any file for which an "s." file exists but no "p." file
602 **	exists in the current directory is purged.
603 **
604 **	Parameters:
605 **		tells whether this came from a "clean", "info", or
606 **		"check" command.
607 **
608 **	Returns:
609 **		none.
610 **
611 **	Side Effects:
612 **		Removes files in the current directory.
613 **		Prints information regarding files being edited.
614 **		Exits if a "check" command.
615 */
616 
617 clean(mode)
618 	int mode;
619 {
620 	struct direct dir;
621 	struct stat stbuf;
622 	char buf[100];
623 	char pline[120];
624 	register FILE *dirfd;
625 	register char *basefile;
626 	bool gotedit;
627 	FILE *pfp;
628 
629 	dirfd = fopen(SccsPath, "r");
630 	if (dirfd == NULL)
631 	{
632 		fprintf(stderr, "Sccs: cannot open %s\n", SccsPath);
633 		return;
634 	}
635 
636 	/*
637 	**  Scan the SCCS directory looking for s. files.
638 	*/
639 
640 	gotedit = FALSE;
641 	while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
642 	{
643 		if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
644 			continue;
645 
646 		/* got an s. file -- see if the p. file exists */
647 		strcpy(buf, SccsPath);
648 		strcat(buf, "/p.");
649 		basefile = &buf[strlen(buf)];
650 		strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
651 		basefile[sizeof dir.d_name - 2] = '\0';
652 		pfp = fopen(buf, "r");
653 		if (pfp != NULL)
654 		{
655 			while (fgets(pline, sizeof pline, pfp) != NULL)
656 				printf("%12s: being edited: %s", basefile, pline);
657 			fclose(pfp);
658 			gotedit = TRUE;
659 			continue;
660 		}
661 
662 		/* the s. file exists and no p. file exists -- unlink the g-file */
663 		if (mode == CLEANC)
664 		{
665 			strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
666 			buf[sizeof dir.d_name - 2] = '\0';
667 			unlink(buf);
668 		}
669 	}
670 
671 	fclose(dirfd);
672 	if (!gotedit && mode == INFOC)
673 		printf("Nothing being edited\n");
674 	if (mode == CHECKC)
675 		exit(gotedit);
676 }
677 /*
678 **  UNEDIT -- unedit a file
679 **
680 **	Checks to see that the current user is actually editting
681 **	the file and arranges that s/he is not editting it.
682 **
683 **	Parameters:
684 **		fn -- the name of the file to be unedited.
685 **
686 **	Returns:
687 **		TRUE -- if the file was successfully unedited.
688 **		FALSE -- if the file was not unedited for some
689 **			reason.
690 **
691 **	Side Effects:
692 **		fn is removed
693 **		entries are removed from pfile.
694 */
695 
696 bool
697 unedit(fn)
698 	char *fn;
699 {
700 	register FILE *pfp;
701 	char *pfn;
702 	static char tfn[] = "/tmp/sccsXXXXX";
703 	FILE *tfp;
704 	register char *p;
705 	register char *q;
706 	bool delete = FALSE;
707 	bool others = FALSE;
708 	char *myname;
709 	extern char *getlogin();
710 	struct pfile *pent;
711 	extern struct pfile *getpfile();
712 	char buf[120];
713 # ifdef UIDUSER
714 	struct passwd *pw;
715 	extern struct passwd *getpwuid();
716 # endif UIDUSER
717 
718 	/* make "s." filename & find the trailing component */
719 	pfn = makefile(fn);
720 	if (pfn == NULL)
721 		return (FALSE);
722 	q = rindex(pfn, '/');
723 	if (q == NULL)
724 		q = &pfn[-1];
725 	if (q[1] != 's' || q[2] != '.')
726 	{
727 		fprintf(stderr, "Sccs: bad file name \"%s\"\n", fn);
728 		return (FALSE);
729 	}
730 
731 	/* turn "s." into "p." */
732 	*++q = 'p';
733 
734 	pfp = fopen(pfn, "r");
735 	if (pfp == NULL)
736 	{
737 		printf("%12s: not being edited\n", fn);
738 		return (FALSE);
739 	}
740 
741 	/*
742 	**  Copy p-file to temp file, doing deletions as needed.
743 	*/
744 
745 	mktemp(tfn);
746 	tfp = fopen(tfn, "w");
747 	if (tfp == NULL)
748 	{
749 		fprintf(stderr, "Sccs: cannot create \"%s\"\n", tfn);
750 		exit(EX_OSERR);
751 	}
752 
753 # ifdef UIDUSER
754 	pw = getpwuid(getuid());
755 	if (pw == NULL)
756 	{
757 		fprintf(stderr, "Sccs: who are you?\n");
758 		exit(EX_OSERR);
759 	}
760 	myname = pw->pw_name;
761 # else
762 	myname = getlogin();
763 # endif UIDUSER
764 	while ((pent = getpfile(pfp)) != NULL)
765 	{
766 		if (strcmp(pent->p_user, myname) == 0)
767 		{
768 			/* a match */
769 			delete++;
770 		}
771 		else
772 		{
773 			fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
774 			    pent->p_nsid, pent->p_user, pent->p_date,
775 			    pent->p_time);
776 			others++;
777 		}
778 	}
779 
780 	/* do final cleanup */
781 	if (others)
782 	{
783 		if (freopen(tfn, "r", tfp) == NULL)
784 		{
785 			fprintf(stderr, "Sccs: cannot reopen \"%s\"\n", tfn);
786 			exit(EX_OSERR);
787 		}
788 		if (freopen(pfn, "w", pfp) == NULL)
789 		{
790 			fprintf(stderr, "Sccs: cannot create \"%s\"\n", pfn);
791 			return (FALSE);
792 		}
793 		while (fgets(buf, sizeof buf, tfp) != NULL)
794 			fputs(buf, pfp);
795 	}
796 	else
797 	{
798 		unlink(pfn);
799 	}
800 	fclose(tfp);
801 	fclose(pfp);
802 	unlink(tfn);
803 
804 	if (delete)
805 	{
806 		unlink(fn);
807 		printf("%12s: removed\n", fn);
808 		return (TRUE);
809 	}
810 	else
811 	{
812 		printf("%12s: not being edited by you\n", fn);
813 		return (FALSE);
814 	}
815 }
816 /*
817 **  GETPFILE -- get an entry from the p-file
818 **
819 **	Parameters:
820 **		pfp -- p-file file pointer
821 **
822 **	Returns:
823 **		pointer to p-file struct for next entry
824 **		NULL on EOF or error
825 **
826 **	Side Effects:
827 **		Each call wipes out results of previous call.
828 */
829 
830 struct pfile *
831 getpfile(pfp)
832 	FILE *pfp;
833 {
834 	static struct pfile ent;
835 	static char buf[120];
836 	register char *p;
837 	extern char *nextfield();
838 
839 	if (fgets(buf, sizeof buf, pfp) == NULL)
840 		return (NULL);
841 
842 	ent.p_osid = p = buf;
843 	ent.p_nsid = p = nextfield(p);
844 	ent.p_user = p = nextfield(p);
845 	ent.p_date = p = nextfield(p);
846 	ent.p_time = p = nextfield(p);
847 	if (p == NULL || nextfield(p) != NULL)
848 		return (NULL);
849 
850 	return (&ent);
851 }
852 
853 
854 char *
855 nextfield(p)
856 	register char *p;
857 {
858 	if (p == NULL || *p == '\0')
859 		return (NULL);
860 	while (*p != ' ' && *p != '\n' && *p != '\0')
861 		p++;
862 	if (*p == '\n' || *p == '\0')
863 	{
864 		*p = '\0';
865 		return (NULL);
866 	}
867 	*p++ = '\0';
868 	return (p);
869 }
870