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