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