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