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