release 4
[unix-history] / usr / src / usr.bin / sccs / sccs.c
... / ...
CommitLineData
1# include <stdio.h>
2# include <sys/types.h>
3# include <sys/stat.h>
4# include <sys/dir.h>
5# include <errno.h>
6# include <signal.h>
7# include <sysexits.h>
8# include <whoami.h>
9# include <pwd.h>
10
11/*
12** SCCS.C -- human-oriented front end to the SCCS system.
13**
14** Without trying to add any functionality to speak of, this
15** program tries to make SCCS a little more accessible to human
16** types. The main thing it does is automatically put the
17** string "SCCS/s." on the front of names. Also, it has a
18** couple of things that are designed to shorten frequent
19** combinations, e.g., "delget" which expands to a "delta"
20** and a "get".
21**
22** This program can also function as a setuid front end.
23** To do this, you should copy the source, renaming it to
24** whatever you want, e.g., "syssccs". Change any defaults
25** in the program (e.g., syssccs might default -d to
26** "/usr/src/sys"). Then recompile and put the result
27** as setuid to whomever you want. In this mode, sccs
28** knows to not run setuid for certain programs in order
29** to preserve security, and so forth.
30**
31** Usage:
32** sccs [flags] command [args]
33**
34** Flags:
35** -d<dir> <dir> represents a directory to search
36** out of. It should be a full pathname
37** for general usage. E.g., if <dir> is
38** "/usr/src/sys", then a reference to the
39** file "dev/bio.c" becomes a reference to
40** "/usr/src/sys/dev/bio.c".
41** -p<path> prepends <path> to the final component
42** of the pathname. By default, this is
43** "SCCS". For example, in the -d example
44** above, the path then gets modified to
45** "/usr/src/sys/dev/SCCS/s.bio.c". In
46** more common usage (without the -d flag),
47** "prog.c" would get modified to
48** "SCCS/s.prog.c". In both cases, the
49** "s." gets automatically prepended.
50** -r run as the real user.
51**
52** Commands:
53** admin,
54** get,
55** delta,
56** rmdel,
57** chghist,
58** etc. Straight out of SCCS; only difference
59** is that pathnames get modified as
60** described above.
61** edit Macro for "get -e".
62** unedit Removes a file being edited, knowing
63** about p-files, etc.
64** delget Macro for "delta" followed by "get".
65** deledit Macro for "delta" followed by "get -e".
66** info Tell what files being edited.
67** clean Remove all files that can be
68** regenerated from SCCS files.
69** check Like info, but return exit status, for
70** use in makefiles.
71** fix Remove a top delta & reedit, but save
72** the previous changes in that delta.
73**
74** Compilation Flags:
75** UIDUSER -- determine who the user is by looking at the
76** uid rather than the login name -- for machines
77** where SCCS gets the user in this way.
78** SCCSDIR -- if defined, forces the -d flag to take on
79** this value. This is so that the setuid
80** aspects of this program cannot be abused.
81** This flag also disables the -p flag.
82** SCCSPATH -- the default for the -p flag.
83** MYNAME -- the title this program should print when it
84** gives error messages.
85**
86** Compilation Instructions:
87** cc -O -n -s sccs.c
88** The flags listed above can be -D defined to simplify
89** recompilation for variant versions.
90**
91** Author:
92** Eric Allman, UCB/INGRES
93** Copyright 1980 Regents of the University of California
94*/
95
96static char SccsId[] = "@(#)sccs.c 1.58 %G%";
97\f
98/******************* Configuration Information ********************/
99
100/* special defines for local berkeley systems */
101# include <whoami.h>
102
103# ifdef CSVAX
104# define UIDUSER
105# define PROGPATH(name) "/usr/local/name"
106# endif CSVAX
107
108# ifdef INGVAX
109# define PROGPATH(name) "/usr/local/name"
110# endif INGVAX
111
112# ifdef CORY
113# define PROGPATH(name) "/usr/eecs/bin/name"
114# endif CORY
115
116/* end of berkeley systems defines */
117
118# ifndef SCCSPATH
119# define SCCSPATH "SCCS" /* pathname in which to find s-files */
120# endif NOT SCCSPATH
121
122# ifndef MYNAME
123# define MYNAME "sccs" /* name used for printing errors */
124# endif NOT MYNAME
125
126# ifndef PROGPATH
127# define PROGPATH(name) "/usr/sccs/name" /* place to find binaries */
128# endif PROGPATH
129
130/**************** End of Configuration Information ****************/
131\f
132typedef char bool;
133# define TRUE 1
134# define FALSE 0
135
136# define bitset(bit, word) ((bool) ((bit) & (word)))
137
138struct sccsprog
139{
140 char *sccsname; /* name of SCCS routine */
141 short sccsoper; /* opcode, see below */
142 short sccsflags; /* flags, see below */
143 char *sccspath; /* pathname of binary implementing */
144};
145
146/* values for sccsoper */
147# define PROG 0 /* call a program */
148# define CMACRO 1 /* command substitution macro */
149# define FIX 2 /* fix a delta */
150# define CLEAN 3 /* clean out recreatable files */
151# define UNEDIT 4 /* unedit a file */
152# define SHELL 5 /* call a shell file (like PROG) */
153# define DIFFS 6 /* diff between sccs & file out */
154# define DODIFF 7 /* internal call to diff program */
155
156/* bits for sccsflags */
157# define NO_SDOT 0001 /* no s. on front of args */
158# define REALUSER 0002 /* protected (e.g., admin) */
159
160/* modes for the "clean", "info", "check" ops */
161# define CLEANC 0 /* clean command */
162# define INFOC 1 /* info command */
163# define CHECKC 2 /* check command */
164# define TELLC 3 /* give list of files being edited */
165
166/*
167** Description of commands known to this program.
168** First argument puts the command into a class. Second arg is
169** info regarding treatment of this command. Third arg is a
170** list of flags this command accepts from macros, etc. Fourth
171** arg is the pathname of the implementing program, or the
172** macro definition, or the arg to a sub-algorithm.
173*/
174
175struct sccsprog SccsProg[] =
176{
177 "admin", PROG, REALUSER, PROGPATH(admin),
178 "chghist", PROG, 0, PROGPATH(rmdel),
179 "comb", PROG, 0, PROGPATH(comb),
180 "delta", PROG, 0, PROGPATH(delta),
181 "get", PROG, 0, PROGPATH(get),
182 "help", PROG, NO_SDOT, PROGPATH(help),
183 "prs", PROG, 0, PROGPATH(prs),
184 "prt", PROG, 0, PROGPATH(prt),
185 "rmdel", PROG, REALUSER, PROGPATH(rmdel),
186 "val", PROG, 0, PROGPATH(val),
187 "what", PROG, NO_SDOT, PROGPATH(what),
188 "sccsdiff", SHELL, REALUSER, PROGPATH(sccsdiff),
189 "edit", CMACRO, NO_SDOT, "get -e",
190 "delget", CMACRO, NO_SDOT, "delta:mysrp/get:ixbeskcl -t",
191 "deledit", CMACRO, NO_SDOT, "delta:mysrp -n/get:ixbskcl -e -t -g",
192 "fix", FIX, NO_SDOT, NULL,
193 "clean", CLEAN, REALUSER|NO_SDOT, (char *) CLEANC,
194 "info", CLEAN, REALUSER|NO_SDOT, (char *) INFOC,
195 "check", CLEAN, REALUSER|NO_SDOT, (char *) CHECKC,
196 "tell", CLEAN, REALUSER|NO_SDOT, (char *) TELLC,
197 "unedit", UNEDIT, NO_SDOT, NULL,
198 "diffs", DIFFS, NO_SDOT|REALUSER, NULL,
199 "-diff", DODIFF, NO_SDOT|REALUSER, PROGPATH(bdiff),
200 "print", CMACRO, 0, "prt -e/get -p -m -s",
201 NULL, -1, 0, NULL
202};
203
204/* one line from a p-file */
205struct pfile
206{
207 char *p_osid; /* old SID */
208 char *p_nsid; /* new SID */
209 char *p_user; /* user who did edit */
210 char *p_date; /* date of get */
211 char *p_time; /* time of get */
212};
213
214char *SccsPath = SCCSPATH; /* pathname of SCCS files */
215# ifdef SCCSDIR
216char *SccsDir = SCCSDIR; /* directory to begin search from */
217# else
218char *SccsDir = "";
219# endif
220char MyName[] = MYNAME; /* name used in messages */
221int OutFile = -1; /* override output file for commands */
222bool RealUser; /* if set, running as real user */
223# ifdef DEBUG
224bool Debug; /* turn on tracing */
225# endif
226# ifndef V6
227extern char *getenv();
228# endif V6
229\f
230main(argc, argv)
231 int argc;
232 char **argv;
233{
234 register char *p;
235 extern struct sccsprog *lookup();
236 register int i;
237# ifndef V6
238# ifndef SCCSDIR
239 register struct passwd *pw;
240 extern struct passwd *getpwnam();
241 char buf[100];
242
243 /* pull "SccsDir" out of the environment (possibly) */
244 p = getenv("PROJECT");
245 if (p != NULL && p[0] != '\0')
246 {
247 if (p[0] == '/')
248 SccsDir = p;
249 else
250 {
251 pw = getpwnam(p);
252 if (pw == NULL)
253 {
254 usrerr("user %s does not exist", p);
255 exit(EX_USAGE);
256 }
257 strcpy(buf, pw->pw_dir);
258 strcat(buf, "/src");
259 if (access(buf, 0) < 0)
260 {
261 strcpy(buf, pw->pw_dir);
262 strcat(buf, "/source");
263 if (access(buf, 0) < 0)
264 {
265 usrerr("project %s has no source!", p);
266 exit(EX_USAGE);
267 }
268 }
269 SccsDir = buf;
270 }
271 }
272# endif SCCSDIR
273# endif V6
274
275 /*
276 ** Detect and decode flags intended for this program.
277 */
278
279 if (argc < 2)
280 {
281 fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName);
282 exit(EX_USAGE);
283 }
284 argv[argc] = NULL;
285
286 if (lookup(argv[0]) == NULL)
287 {
288 while ((p = *++argv) != NULL)
289 {
290 if (*p != '-')
291 break;
292 switch (*++p)
293 {
294 case 'r': /* run as real user */
295 setuid(getuid());
296 RealUser++;
297 break;
298
299# ifndef SCCSDIR
300 case 'p': /* path of sccs files */
301 SccsPath = ++p;
302 break;
303
304 case 'd': /* directory to search from */
305 SccsDir = ++p;
306 break;
307# endif
308
309# ifdef DEBUG
310 case 'T': /* trace */
311 Debug++;
312 break;
313# endif
314
315 default:
316 usrerr("unknown option -%s", p);
317 break;
318 }
319 }
320 if (SccsPath[0] == '\0')
321 SccsPath = ".";
322 }
323
324 i = command(argv, FALSE, "");
325 exit(i);
326}
327\f
328/*
329** COMMAND -- look up and perform a command
330**
331** This routine is the guts of this program. Given an
332** argument vector, it looks up the "command" (argv[0])
333** in the configuration table and does the necessary stuff.
334**
335** Parameters:
336** argv -- an argument vector to process.
337** forkflag -- if set, fork before executing the command.
338** editflag -- if set, only include flags listed in the
339** sccsklets field of the command descriptor.
340** arg0 -- a space-seperated list of arguments to insert
341** before argv.
342**
343** Returns:
344** zero -- command executed ok.
345** else -- error status.
346**
347** Side Effects:
348** none.
349*/
350
351command(argv, forkflag, arg0)
352 char **argv;
353 bool forkflag;
354 char *arg0;
355{
356 register struct sccsprog *cmd;
357 register char *p;
358 char buf[40];
359 extern struct sccsprog *lookup();
360 char *nav[1000];
361 char **np;
362 register char **ap;
363 register int i;
364 register char *q;
365 extern bool unedit();
366 int rval = 0;
367 extern char *index();
368 extern char *makefile();
369 char *editchs;
370 extern char *tail();
371
372# ifdef DEBUG
373 if (Debug)
374 {
375 printf("command:\n\t\"%s\"\n", arg0);
376 for (np = argv; *np != NULL; np++)
377 printf("\t\"%s\"\n", *np);
378 }
379# endif
380
381 /*
382 ** Copy arguments.
383 ** Copy from arg0 & if necessary at most one arg
384 ** from argv[0].
385 */
386
387 np = ap = &nav[1];
388 editchs = NULL;
389 for (p = arg0, q = buf; *p != '\0' && *p != '/'; )
390 {
391 *np++ = q;
392 while (*p == ' ')
393 p++;
394 while (*p != ' ' && *p != '\0' && *p != '/' && *p != ':')
395 *q++ = *p++;
396 *q++ = '\0';
397 if (*p == ':')
398 {
399 editchs = q;
400 while (*++p != '\0' && *p != '/' && *p != ' ')
401 *q++ = *p;
402 *q++ = '\0';
403 }
404 }
405 *np = NULL;
406 if (*ap == NULL)
407 *np++ = *argv++;
408
409 /*
410 ** Look up command.
411 ** At this point, *ap is the command name.
412 */
413
414 cmd = lookup(*ap);
415 if (cmd == NULL)
416 {
417 usrerr("Unknown command \"%s\"", *ap);
418 return (EX_USAGE);
419 }
420
421 /*
422 ** Copy remaining arguments doing editing as appropriate.
423 */
424
425 for (; *argv != NULL; argv++)
426 {
427 p = *argv;
428 if (*p == '-')
429 {
430 if (p[1] == '\0' || editchs == NULL || index(editchs, p[1]) != NULL)
431 *np++ = p;
432 }
433 else
434 {
435 if (!bitset(NO_SDOT, cmd->sccsflags))
436 p = makefile(p);
437 if (p != NULL)
438 *np++ = p;
439 }
440 }
441 *np = NULL;
442
443 /*
444 ** Interpret operation associated with this command.
445 */
446
447 switch (cmd->sccsoper)
448 {
449 case SHELL: /* call a shell file */
450 *ap = cmd->sccspath;
451 *--ap = "sh";
452 rval = callprog("/bin/sh", cmd->sccsflags, ap, forkflag);
453 break;
454
455 case PROG: /* call an sccs prog */
456 rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag);
457 break;
458
459 case CMACRO: /* command macro */
460 /* step through & execute each part of the macro */
461 for (p = cmd->sccspath; *p != '\0'; p++)
462 {
463 q = p;
464 while (*p != '\0' && *p != '/')
465 p++;
466 rval = command(&ap[1], *p != '\0', q);
467 if (rval != 0)
468 break;
469 }
470 break;
471
472 case FIX: /* fix a delta */
473 if (strncmp(ap[1], "-r", 2) != 0)
474 {
475 usrerr("-r flag needed for fix command");
476 rval = EX_USAGE;
477 break;
478 }
479
480 /* get the version with all changes */
481 rval = command(&ap[1], TRUE, "get -k");
482
483 /* now remove that version from the s-file */
484 if (rval == 0)
485 rval = command(&ap[1], TRUE, "rmdel:r");
486
487 /* and edit the old version (but don't clobber new vers) */
488 if (rval == 0)
489 rval = command(&ap[2], FALSE, "get -e -g");
490 break;
491
492 case CLEAN:
493 rval = clean((int) cmd->sccspath, ap);
494 break;
495
496 case UNEDIT:
497 for (argv = np = &ap[1]; *argv != NULL; argv++)
498 {
499 if (unedit(*argv))
500 *np++ = *argv;
501 }
502 *np = NULL;
503
504 /* get all the files that we unedited successfully */
505 if (np > &ap[1])
506 rval = command(&ap[1], FALSE, "get");
507 break;
508
509 case DIFFS: /* diff between s-file & edit file */
510 /* find the end of the flag arguments */
511 for (np = &ap[1]; *np != NULL && **np == '-'; np++)
512 continue;
513 argv = np;
514
515 /* for each file, do the diff */
516 p = argv[1];
517 while (*np != NULL)
518 {
519 /* messy, but we need a null terminated argv */
520 *argv = *np++;
521 argv[1] = NULL;
522 i = dodiff(ap, tail(*argv));
523 if (rval == 0)
524 rval = i;
525 argv[1] = p;
526 }
527 break;
528
529 case DODIFF: /* internal diff call */
530 setuid(getuid());
531 for (np = ap; *np != NULL; np++)
532 {
533 if ((*np)[0] == '-' && (*np)[1] == 'C')
534 (*np)[1] = 'c';
535 }
536
537 /* insert "-" argument */
538 np[1] = NULL;
539 np[0] = np[-1];
540 np[-1] = "-";
541
542 /* execute the diff program of choice */
543# ifndef V6
544 execvp("diff", ap);
545# endif
546 execv(cmd->sccspath, argv);
547 syserr("cannot exec %s", cmd->sccspath);
548 exit(EX_OSERR);
549
550 default:
551 syserr("oper %d", cmd->sccsoper);
552 exit(EX_SOFTWARE);
553 }
554# ifdef DEBUG
555 if (Debug)
556 printf("command: rval=%d\n", rval);
557# endif
558 return (rval);
559}
560\f
561/*
562** LOOKUP -- look up an SCCS command name.
563**
564** Parameters:
565** name -- the name of the command to look up.
566**
567** Returns:
568** ptr to command descriptor for this command.
569** NULL if no such entry.
570**
571** Side Effects:
572** none.
573*/
574
575struct sccsprog *
576lookup(name)
577 char *name;
578{
579 register struct sccsprog *cmd;
580
581 for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
582 {
583 if (strcmp(cmd->sccsname, name) == 0)
584 return (cmd);
585 }
586 return (NULL);
587}
588\f
589/*
590** CALLPROG -- call a program
591**
592** Used to call the SCCS programs.
593**
594** Parameters:
595** progpath -- pathname of the program to call.
596** flags -- status flags from the command descriptors.
597** argv -- an argument vector to pass to the program.
598** forkflag -- if true, fork before calling, else just
599** exec.
600**
601** Returns:
602** The exit status of the program.
603** Nothing if forkflag == FALSE.
604**
605** Side Effects:
606** Can exit if forkflag == FALSE.
607*/
608
609callprog(progpath, flags, argv, forkflag)
610 char *progpath;
611 short flags;
612 char **argv;
613 bool forkflag;
614{
615 register int i;
616 auto int st;
617
618# ifdef DEBUG
619 if (Debug)
620 {
621 printf("callprog:\n");
622 for (i = 0; argv[i] != NULL; i++)
623 printf("\t\"%s\"\n", argv[i]);
624 }
625# endif
626
627 if (*argv == NULL)
628 return (-1);
629
630 /*
631 ** Fork if appropriate.
632 */
633
634 if (forkflag)
635 {
636# ifdef DEBUG
637 if (Debug)
638 printf("Forking\n");
639# endif
640 i = fork();
641 if (i < 0)
642 {
643 syserr("cannot fork");
644 exit(EX_OSERR);
645 }
646 else if (i > 0)
647 {
648 wait(&st);
649 if ((st & 0377) == 0)
650 st = (st >> 8) & 0377;
651 if (OutFile >= 0)
652 {
653 close(OutFile);
654 OutFile = -1;
655 }
656 return (st);
657 }
658 }
659 else if (OutFile >= 0)
660 {
661 syserr("callprog: setting stdout w/o forking");
662 exit(EX_SOFTWARE);
663 }
664
665 /* set protection as appropriate */
666 if (bitset(REALUSER, flags))
667 setuid(getuid());
668
669 /* change standard input & output if needed */
670 if (OutFile >= 0)
671 {
672 close(1);
673 dup(OutFile);
674 close(OutFile);
675 }
676
677 /* call real SCCS program */
678 execv(progpath, argv);
679 syserr("cannot execute %s", progpath);
680 exit(EX_UNAVAILABLE);
681 /*NOTREACHED*/
682}
683\f
684/*
685** MAKEFILE -- make filename of SCCS file
686**
687** If the name passed is already the name of an SCCS file,
688** just return it. Otherwise, munge the name into the name
689** of the actual SCCS file.
690**
691** There are cases when it is not clear what you want to
692** do. For example, if SccsPath is an absolute pathname
693** and the name given is also an absolute pathname, we go
694** for SccsPath (& only use the last component of the name
695** passed) -- this is important for security reasons (if
696** sccs is being used as a setuid front end), but not
697** particularly intuitive.
698**
699** Parameters:
700** name -- the file name to be munged.
701**
702** Returns:
703** The pathname of the sccs file.
704** NULL on error.
705**
706** Side Effects:
707** none.
708*/
709
710char *
711makefile(name)
712 char *name;
713{
714 register char *p;
715 char buf[512];
716 extern char *malloc();
717 extern char *rindex();
718 extern bool isdir();
719 register char *q;
720
721 p = rindex(name, '/');
722 if (p == NULL)
723 p = name;
724 else
725 p++;
726
727 /*
728 ** See if the name can be used as-is.
729 */
730
731 if (SccsPath[0] != '/' || name[0] == '/' || strncmp(name, "./", 2) == 0)
732 {
733 if (strncmp(p, "s.", 2) == 0)
734 return (name);
735 if (isdir(name))
736 return (name);
737 }
738
739 /*
740 ** Create the actual pathname.
741 */
742
743 /* first the directory part */
744 if (name[0] != '/')
745 {
746 strcpy(buf, SccsDir);
747 strcat(buf, "/");
748 }
749 else
750 strcpy(buf, "");
751
752 /* then the head of the pathname */
753 strncat(buf, name, p - name);
754 q = &buf[strlen(buf)];
755 strcpy(q, p);
756 if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
757 {
758 /* sorry, no; copy the SCCS pathname & the "s." */
759 strcpy(q, SccsPath);
760 strcat(buf, "/s.");
761
762 /* and now the end of the name */
763 strcat(buf, p);
764 }
765
766 /* if i haven't changed it, why did I do all this? */
767 if (strcmp(buf, name) == 0)
768 p = name;
769
770 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
771}
772\f/*
773** ISDIR -- return true if the argument is a directory.
774**
775** Parameters:
776** name -- the pathname of the file to check.
777**
778** Returns:
779** TRUE if 'name' is a directory, FALSE otherwise.
780**
781** Side Effects:
782** none.
783*/
784
785bool
786isdir(name)
787 char *name;
788{
789 struct stat stbuf;
790
791 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
792}
793\f
794/*
795** SAFEPATH -- determine whether a pathname is "safe"
796**
797** "Safe" pathnames only allow you to get deeper into the
798** directory structure, i.e., full pathnames and ".." are
799** not allowed.
800**
801** Parameters:
802** p -- the name to check.
803**
804** Returns:
805** TRUE -- if the path is safe.
806** FALSE -- if the path is not safe.
807**
808** Side Effects:
809** Prints a message if the path is not safe.
810*/
811
812bool
813safepath(p)
814 register char *p;
815{
816 extern char *index();
817
818 if (*p != '/')
819 {
820 while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
821 {
822 p = index(p, '/');
823 if (p == NULL)
824 return (TRUE);
825 p++;
826 }
827 }
828
829 printf("You may not use full pathnames or \"..\"\n");
830 return (FALSE);
831}
832\f
833/*
834** CLEAN -- clean out recreatable files
835**
836** Any file for which an "s." file exists but no "p." file
837** exists in the current directory is purged.
838**
839** Parameters:
840** mode -- tells whether this came from a "clean", "info", or
841** "check" command.
842** argv -- the rest of the argument vector.
843**
844** Returns:
845** none.
846**
847** Side Effects:
848** Removes files in the current directory.
849** Prints information regarding files being edited.
850** Exits if a "check" command.
851*/
852
853clean(mode, argv)
854 int mode;
855 char **argv;
856{
857 struct direct dir;
858 char buf[100];
859 char *bufend;
860 register FILE *dirfd;
861 register char *basefile;
862 bool gotedit;
863 bool gotpfent;
864 FILE *pfp;
865 bool nobranch = FALSE;
866 extern struct pfile *getpfent();
867 register struct pfile *pf;
868 register char **ap;
869 extern char *username();
870 char *usernm = NULL;
871 char *subdir = NULL;
872 char *cmdname;
873
874 /*
875 ** Process the argv
876 */
877
878 cmdname = *argv;
879 for (ap = argv; *++ap != NULL; )
880 {
881 if (**ap == '-')
882 {
883 /* we have a flag */
884 switch ((*ap)[1])
885 {
886 case 'b':
887 nobranch = TRUE;
888 break;
889
890 case 'u':
891 if ((*ap)[2] != '\0')
892 usernm = &(*ap)[2];
893 else if (ap[1] != NULL && ap[1][0] != '-')
894 usernm = *++ap;
895 else
896 usernm = username();
897 break;
898 }
899 }
900 else
901 {
902 if (subdir != NULL)
903 usrerr("too many args");
904 else
905 subdir = *ap;
906 }
907 }
908
909 /*
910 ** Find and open the SCCS directory.
911 */
912
913 strcpy(buf, SccsDir);
914 if (buf[0] != '\0')
915 strcat(buf, "/");
916 if (subdir != NULL)
917 {
918 strcat(buf, subdir);
919 strcat(buf, "/");
920 }
921 strcat(buf, SccsPath);
922 bufend = &buf[strlen(buf)];
923
924 dirfd = fopen(buf, "r");
925 if (dirfd == NULL)
926 {
927 usrerr("cannot open %s", buf);
928 return (EX_NOINPUT);
929 }
930
931 /*
932 ** Scan the SCCS directory looking for s. files.
933 ** gotedit tells whether we have tried to clean any
934 ** files that are being edited.
935 */
936
937 gotedit = FALSE;
938 while (fread((char *)&dir, sizeof dir, 1, dirfd) != NULL)
939 {
940 if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
941 continue;
942
943 /* got an s. file -- see if the p. file exists */
944 strcpy(bufend, "/p.");
945 basefile = bufend + 3;
946 strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
947 basefile[sizeof dir.d_name - 2] = '\0';
948
949 /*
950 ** open and scan the p-file.
951 ** 'gotpfent' tells if we have found a valid p-file
952 ** entry.
953 */
954
955 pfp = fopen(buf, "r");
956 gotpfent = FALSE;
957 if (pfp != NULL)
958 {
959 /* the file exists -- report it's contents */
960 while ((pf = getpfent(pfp)) != NULL)
961 {
962 if (nobranch && isbranch(pf->p_nsid))
963 continue;
964 if (usernm != NULL && strcmp(usernm, pf->p_user) != 0 && mode != CLEANC)
965 continue;
966 gotedit = TRUE;
967 gotpfent = TRUE;
968 if (mode == TELLC)
969 {
970 printf("%s\n", basefile);
971 break;
972 }
973 printf("%12s: being edited: %s %s %s %s %s\n",
974 basefile, pf->p_osid, pf->p_nsid,
975 pf->p_user, pf->p_date, pf->p_time);
976 }
977 fclose(pfp);
978 }
979
980 /* the s. file exists and no p. file exists -- unlink the g-file */
981 if (mode == CLEANC && !gotpfent)
982 {
983 strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
984 buf[sizeof dir.d_name - 2] = '\0';
985 unlink(buf);
986 }
987 }
988
989 /* cleanup & report results */
990 fclose(dirfd);
991 if (!gotedit && mode == INFOC)
992 {
993 printf("Nothing being edited");
994 if (nobranch)
995 printf(" (on trunk)");
996 if (usernm == NULL)
997 printf("\n");
998 else
999 printf(" by %s\n", usernm);
1000 }
1001 if (mode == CHECKC)
1002 exit(gotedit);
1003 return (EX_OK);
1004}
1005\f
1006/*
1007** ISBRANCH -- is the SID a branch?
1008**
1009** Parameters:
1010** sid -- the sid to check.
1011**
1012** Returns:
1013** TRUE if the sid represents a branch.
1014** FALSE otherwise.
1015**
1016** Side Effects:
1017** none.
1018*/
1019
1020isbranch(sid)
1021 char *sid;
1022{
1023 register char *p;
1024 int dots;
1025
1026 dots = 0;
1027 for (p = sid; *p != '\0'; p++)
1028 {
1029 if (*p == '.')
1030 dots++;
1031 if (dots > 1)
1032 return (TRUE);
1033 }
1034 return (FALSE);
1035}
1036\f
1037/*
1038** UNEDIT -- unedit a file
1039**
1040** Checks to see that the current user is actually editting
1041** the file and arranges that s/he is not editting it.
1042**
1043** Parameters:
1044** fn -- the name of the file to be unedited.
1045**
1046** Returns:
1047** TRUE -- if the file was successfully unedited.
1048** FALSE -- if the file was not unedited for some
1049** reason.
1050**
1051** Side Effects:
1052** fn is removed
1053** entries are removed from pfile.
1054*/
1055
1056bool
1057unedit(fn)
1058 char *fn;
1059{
1060 register FILE *pfp;
1061 char *pfn;
1062 static char tfn[] = "/tmp/sccsXXXXX";
1063 FILE *tfp;
1064 register char *q;
1065 bool delete = FALSE;
1066 bool others = FALSE;
1067 char *myname;
1068 extern char *username();
1069 struct pfile *pent;
1070 extern struct pfile *getpfent();
1071 char buf[120];
1072 extern char *makefile();
1073
1074 /* make "s." filename & find the trailing component */
1075 pfn = makefile(fn);
1076 if (pfn == NULL)
1077 return (FALSE);
1078 q = rindex(pfn, '/');
1079 if (q == NULL)
1080 q = &pfn[-1];
1081 if (q[1] != 's' || q[2] != '.')
1082 {
1083 usrerr("bad file name \"%s\"", fn);
1084 return (FALSE);
1085 }
1086
1087 /* turn "s." into "p." & try to open it */
1088 *++q = 'p';
1089
1090 pfp = fopen(pfn, "r");
1091 if (pfp == NULL)
1092 {
1093 printf("%12s: not being edited\n", fn);
1094 return (FALSE);
1095 }
1096
1097 /* create temp file for editing p-file */
1098 mktemp(tfn);
1099 tfp = fopen(tfn, "w");
1100 if (tfp == NULL)
1101 {
1102 usrerr("cannot create \"%s\"", tfn);
1103 exit(EX_OSERR);
1104 }
1105
1106 /* figure out who I am */
1107 myname = username();
1108
1109 /*
1110 ** Copy p-file to temp file, doing deletions as needed.
1111 */
1112
1113 while ((pent = getpfent(pfp)) != NULL)
1114 {
1115 if (strcmp(pent->p_user, myname) == 0)
1116 {
1117 /* a match */
1118 delete++;
1119 }
1120 else
1121 {
1122 /* output it again */
1123 fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
1124 pent->p_nsid, pent->p_user, pent->p_date,
1125 pent->p_time);
1126 others++;
1127 }
1128 }
1129
1130 /* do final cleanup */
1131 if (others)
1132 {
1133 /* copy it back (perhaps it should be linked?) */
1134 if (freopen(tfn, "r", tfp) == NULL)
1135 {
1136 syserr("cannot reopen \"%s\"", tfn);
1137 exit(EX_OSERR);
1138 }
1139 if (freopen(pfn, "w", pfp) == NULL)
1140 {
1141 usrerr("cannot create \"%s\"", pfn);
1142 return (FALSE);
1143 }
1144 while (fgets(buf, sizeof buf, tfp) != NULL)
1145 fputs(buf, pfp);
1146 }
1147 else
1148 {
1149 /* it's empty -- remove it */
1150 unlink(pfn);
1151 }
1152 fclose(tfp);
1153 fclose(pfp);
1154 unlink(tfn);
1155
1156 /* actually remove the g-file */
1157 if (delete)
1158 {
1159 unlink(tail(fn));
1160 printf("%12s: removed\n", tail(fn));
1161 return (TRUE);
1162 }
1163 else
1164 {
1165 printf("%12s: not being edited by you\n", fn);
1166 return (FALSE);
1167 }
1168}
1169\f
1170/*
1171** DODIFF -- diff an s-file against a g-file
1172**
1173** Parameters:
1174** getv -- argv for the 'get' command.
1175** gfile -- name of the g-file to diff against.
1176**
1177** Returns:
1178** Result of get.
1179**
1180** Side Effects:
1181** none.
1182*/
1183
1184dodiff(getv, gfile)
1185 char **getv;
1186 char *gfile;
1187{
1188 int pipev[2];
1189 int rval;
1190 register int i;
1191 register int pid;
1192 auto int st;
1193 extern int errno;
1194 int (*osig)();
1195 register char *p;
1196 register char **ap;
1197 bool makescript = FALSE;
1198
1199 for (ap = getv; *ap != NULL; ap++)
1200 {
1201 p = *ap;
1202 if (p[0] == '-')
1203 {
1204 switch (p[1])
1205 {
1206 case 'E':
1207 p[1] = 'e';
1208 makescript = TRUE;
1209 break;
1210 }
1211 }
1212 }
1213
1214 if (makescript)
1215 {
1216 printf("sccs edit %s\n", gfile);
1217 printf("ed - %s << 'xxEOFxx'\n", gfile);
1218 }
1219 else
1220 printf("\n------- %s -------\n", gfile);
1221 fflush(stdout);
1222
1223 /* create context for diff to run in */
1224 if (pipe(pipev) < 0)
1225 {
1226 syserr("dodiff: pipe failed");
1227 exit(EX_OSERR);
1228 }
1229 if ((pid = fork()) < 0)
1230 {
1231 syserr("dodiff: fork failed");
1232 exit(EX_OSERR);
1233 }
1234 else if (pid > 0)
1235 {
1236 /* in parent; run get */
1237 OutFile = pipev[1];
1238 close(pipev[0]);
1239 rval = command(&getv[1], TRUE, "get:rcixt -s -k -p");
1240 osig = signal(SIGINT, SIG_IGN);
1241 while (((i = wait(&st)) >= 0 && i != pid) || errno == EINTR)
1242 errno = 0;
1243 signal(SIGINT, osig);
1244 /* ignore result of diff */
1245 }
1246 else
1247 {
1248 /* in child, run diff */
1249 if (close(pipev[1]) < 0 || close(0) < 0 ||
1250 dup(pipev[0]) != 0 || close(pipev[0]) < 0)
1251 {
1252 syserr("dodiff: magic failed");
1253 exit(EX_OSERR);
1254 }
1255 command(&getv[1], FALSE, "-diff:elsfhbC");
1256 }
1257
1258 if (makescript)
1259 {
1260 printf("w\n");
1261 printf("q\n");
1262 printf("'xxEOFxx'\n");
1263 printf("sccs delta %s\n", gfile);
1264 }
1265
1266 return (rval);
1267}
1268\f
1269/*
1270** TAIL -- return tail of filename.
1271**
1272** Parameters:
1273** fn -- the filename.
1274**
1275** Returns:
1276** a pointer to the tail of the filename; e.g., given
1277** "cmd/ls.c", "ls.c" is returned.
1278**
1279** Side Effects:
1280** none.
1281*/
1282
1283char *
1284tail(fn)
1285 register char *fn;
1286{
1287 register char *p;
1288
1289 for (p = fn; *p != 0; p++)
1290 if (*p == '/' && p[1] != '\0' && p[1] != '/')
1291 fn = &p[1];
1292 return (fn);
1293}
1294\f
1295/*
1296** GETPFENT -- get an entry from the p-file
1297**
1298** Parameters:
1299** pfp -- p-file file pointer
1300**
1301** Returns:
1302** pointer to p-file struct for next entry
1303** NULL on EOF or error
1304**
1305** Side Effects:
1306** Each call wipes out results of previous call.
1307*/
1308
1309struct pfile *
1310getpfent(pfp)
1311 FILE *pfp;
1312{
1313 static struct pfile ent;
1314 static char buf[120];
1315 register char *p;
1316 extern char *nextfield();
1317
1318 if (fgets(buf, sizeof buf, pfp) == NULL)
1319 return (NULL);
1320
1321 ent.p_osid = p = buf;
1322 ent.p_nsid = p = nextfield(p);
1323 ent.p_user = p = nextfield(p);
1324 ent.p_date = p = nextfield(p);
1325 ent.p_time = p = nextfield(p);
1326 if (p == NULL || nextfield(p) != NULL)
1327 return (NULL);
1328
1329 return (&ent);
1330}
1331
1332
1333char *
1334nextfield(p)
1335 register char *p;
1336{
1337 if (p == NULL || *p == '\0')
1338 return (NULL);
1339 while (*p != ' ' && *p != '\n' && *p != '\0')
1340 p++;
1341 if (*p == '\n' || *p == '\0')
1342 {
1343 *p = '\0';
1344 return (NULL);
1345 }
1346 *p++ = '\0';
1347 return (p);
1348}
1349\f
1350/*
1351** USRERR -- issue user-level error
1352**
1353** Parameters:
1354** f -- format string.
1355** p1-p3 -- parameters to a printf.
1356**
1357** Returns:
1358** -1
1359**
1360** Side Effects:
1361** none.
1362*/
1363
1364/*VARARGS1*/
1365usrerr(f, p1, p2, p3)
1366 char *f;
1367{
1368 fprintf(stderr, "\n%s: ", MyName);
1369 fprintf(stderr, f, p1, p2, p3);
1370 fprintf(stderr, "\n");
1371
1372 return (-1);
1373}
1374\f
1375/*
1376** SYSERR -- print system-generated error.
1377**
1378** Parameters:
1379** f -- format string to a printf.
1380** p1, p2, p3 -- parameters to f.
1381**
1382** Returns:
1383** never.
1384**
1385** Side Effects:
1386** none.
1387*/
1388
1389/*VARARGS1*/
1390syserr(f, p1, p2, p3)
1391 char *f;
1392{
1393 extern int errno;
1394
1395 fprintf(stderr, "\n%s SYSERR: ", MyName);
1396 fprintf(stderr, f, p1, p2, p3);
1397 fprintf(stderr, "\n");
1398 if (errno == 0)
1399 exit(EX_SOFTWARE);
1400 else
1401 {
1402 perror(NULL);
1403 exit(EX_OSERR);
1404 }
1405}
1406\f/*
1407** USERNAME -- return name of the current user
1408**
1409** Parameters:
1410** none
1411**
1412** Returns:
1413** name of current user
1414**
1415** Side Effects:
1416** none
1417*/
1418
1419char *
1420username()
1421{
1422# ifdef UIDUSER
1423 extern struct passwd *getpwuid();
1424 register struct passwd *pw;
1425
1426 pw = getpwuid(getuid());
1427 if (pw == NULL)
1428 {
1429 syserr("who are you? (uid=%d)", getuid());
1430 exit(EX_OSERR);
1431 }
1432 return (pw->pw_name);
1433# else
1434 extern char *getlogin();
1435
1436 return (getlogin());
1437# endif UIDUSER
1438}