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