print g-file name on diffs command
[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>
1cc2dcec 9
27e713f7
EA
10/*
11** SCCS.C -- human-oriented front end to the SCCS system.
12**
13** Without trying to add any functionality to speak of, this
14** program tries to make SCCS a little more accessible to human
15** types. The main thing it does is automatically put the
16** string "SCCS/s." on the front of names. Also, it has a
17** couple of things that are designed to shorten frequent
18** combinations, e.g., "delget" which expands to a "delta"
19** and a "get".
20**
21** This program can also function as a setuid front end.
22** To do this, you should copy the source, renaming it to
23** whatever you want, e.g., "syssccs". Change any defaults
24** in the program (e.g., syssccs might default -d to
25** "/usr/src/sys"). Then recompile and put the result
26** as setuid to whomever you want. In this mode, sccs
27** knows to not run setuid for certain programs in order
28** to preserve security, and so forth.
29**
30** Usage:
31** sccs [flags] command [args]
32**
33** Flags:
34** -d<dir> <dir> represents a directory to search
35** out of. It should be a full pathname
36** for general usage. E.g., if <dir> is
37** "/usr/src/sys", then a reference to the
38** file "dev/bio.c" becomes a reference to
39** "/usr/src/sys/dev/bio.c".
40** -p<path> prepends <path> to the final component
41** of the pathname. By default, this is
42** "SCCS". For example, in the -d example
43** above, the path then gets modified to
44** "/usr/src/sys/dev/SCCS/s.bio.c". In
45** more common usage (without the -d flag),
46** "prog.c" would get modified to
47** "SCCS/s.prog.c". In both cases, the
48** "s." gets automatically prepended.
49** -r run as the real user.
50**
51** Commands:
52** admin,
53** get,
54** delta,
55** rmdel,
56** chghist,
57** etc. Straight out of SCCS; only difference
58** is that pathnames get modified as
59** described above.
60** edit Macro for "get -e".
61** unedit Removes a file being edited, knowing
62** about p-files, etc.
63** delget Macro for "delta" followed by "get".
64** deledit Macro for "delta" followed by "get -e".
65** info Tell what files being edited.
66** clean Remove all files that can be
67** regenerated from SCCS files.
d266e8cb 68** check Like info, but return exit status, for
27e713f7
EA
69** use in makefiles.
70** fix Remove a top delta & reedit, but save
71** the previous changes in that delta.
72**
73** Compilation Flags:
74** UIDUSER -- determine who the user is by looking at the
75** uid rather than the login name -- for machines
76** where SCCS gets the user in this way.
56151b9e 77** SCCSDIR -- if defined, forces the -d flag to take on
d266e8cb
EA
78** this value. This is so that the setuid
79** aspects of this program cannot be abused.
56151b9e
EA
80** This flag also disables the -p flag.
81** SCCSPATH -- the default for the -p flag.
27082f2b
EA
82** MYNAME -- the title this program should print when it
83** gives error messages.
27e713f7
EA
84**
85** Compilation Instructions:
86** cc -O -n -s sccs.c
27082f2b
EA
87** The flags listed above can be -D defined to simplify
88** recompilation for variant versions.
27e713f7
EA
89**
90** Author:
91** Eric Allman, UCB/INGRES
56151b9e 92** Copyright 1980 Regents of the University of California
27e713f7
EA
93*/
94
d597ad9e 95static char SccsId[] = "@(#)sccs.c 1.40 %G%";
2d351698 96\f
56151b9e
EA
97/******************* Configuration Information ********************/
98
27082f2b
EA
99/* special defines for local berkeley systems */
100# include <whoami.h>
101
27e713f7
EA
102# ifdef CSVAX
103# define UIDUSER
56151b9e
EA
104# define PROGPATH(name) "/usr/local/name"
105# endif CSVAX
106
27082f2b
EA
107/* end of berkeley systems defines */
108
109# ifndef SCCSPATH
2d351698 110# define SCCSPATH "SCCS" /* pathname in which to find s-files */
27082f2b 111# endif NOT SCCSPATH
56151b9e 112
27082f2b
EA
113# ifndef MYNAME
114# define MYNAME "sccs" /* name used for printing errors */
115# endif NOT MYNAME
27e713f7 116
2d351698
EA
117# ifndef PROGPATH
118# define PROGPATH(name) "/usr/sccs/name" /* place to find binaries */
119# endif PROGPATH
2a0234bf 120
2d351698
EA
121/**************** End of Configuration Information ****************/
122\f
2a0234bf 123typedef char bool;
a97ecd0d
EA
124# define TRUE 1
125# define FALSE 0
81dc6403 126
e1e64d36
EA
127# define bitset(bit, word) ((bool) ((bit) & (word)))
128
27e713f7
EA
129# ifdef UIDUSER
130# include <pwd.h>
131# endif UIDUSER
132
1cc2dcec
EA
133struct sccsprog
134{
135 char *sccsname; /* name of SCCS routine */
a97ecd0d
EA
136 short sccsoper; /* opcode, see below */
137 short sccsflags; /* flags, see below */
1dbb3fe7 138 char *sccsklets; /* valid key-letters on macros */
1cc2dcec
EA
139 char *sccspath; /* pathname of binary implementing */
140};
141
a97ecd0d
EA
142/* values for sccsoper */
143# define PROG 0 /* call a program */
e672376b 144# define CMACRO 1 /* command substitution macro */
4535945e 145# define FIX 2 /* fix a delta */
399058e3 146# define CLEAN 3 /* clean out recreatable files */
61886f2a 147# define UNEDIT 4 /* unedit a file */
c9ac6819 148# define SHELL 5 /* call a shell file (like PROG) */
629c0863 149# define DIFFS 6 /* diff between sccs & file out */
a97ecd0d 150
2a0234bf 151/* bits for sccsflags */
a97ecd0d
EA
152# define NO_SDOT 0001 /* no s. on front of args */
153# define REALUSER 0002 /* protected (e.g., admin) */
1cc2dcec 154
89819fbe
EA
155/* modes for the "clean", "info", "check" ops */
156# define CLEANC 0 /* clean command */
157# define INFOC 1 /* info command */
158# define CHECKC 2 /* check command */
159
2d351698
EA
160/*
161** Description of commands known to this program.
162** First argument puts the command into a class. Second arg is
163** info regarding treatment of this command. Third arg is a
164** list of flags this command accepts from macros, etc. Fourth
165** arg is the pathname of the implementing program, or the
166** macro definition, or the arg to a sub-algorithm.
167*/
7a8ba074 168
1cc2dcec
EA
169struct sccsprog SccsProg[] =
170{
1dbb3fe7
EA
171 "admin", PROG, REALUSER, "", PROGPATH(admin),
172 "chghist", PROG, 0, "", PROGPATH(rmdel),
173 "comb", PROG, 0, "", PROGPATH(comb),
174 "delta", PROG, 0, "mysrp", PROGPATH(delta),
778fafae 175 "get", PROG, 0, "ixbeskcl", PROGPATH(get),
1dbb3fe7
EA
176 "help", PROG, NO_SDOT, "", PROGPATH(help),
177 "prt", PROG, 0, "", PROGPATH(prt),
178 "rmdel", PROG, REALUSER, "", PROGPATH(rmdel),
179 "what", PROG, NO_SDOT, "", PROGPATH(what),
c9ac6819 180 "sccsdiff", SHELL, REALUSER, "", PROGPATH(sccsdiff),
778fafae 181 "edit", CMACRO, NO_SDOT, "ixbscl", "get -e",
1dbb3fe7
EA
182 "delget", CMACRO, NO_SDOT, "", "delta/get -t",
183 "deledit", CMACRO, NO_SDOT, "", "delta/get -e -t",
184 "fix", FIX, NO_SDOT, "", NULL,
185 "clean", CLEAN, REALUSER, "", (char *) CLEANC,
186 "info", CLEAN, REALUSER, "", (char *) INFOC,
187 "check", CLEAN, REALUSER, "", (char *) CHECKC,
188 "unedit", UNEDIT, NO_SDOT, "", NULL,
629c0863 189 "diffs", DIFFS, NO_SDOT|REALUSER, "", NULL,
1dbb3fe7 190 NULL, -1, 0, "", NULL
1cc2dcec
EA
191};
192
2d351698 193/* one line from a p-file */
61886f2a
EA
194struct pfile
195{
196 char *p_osid; /* old SID */
197 char *p_nsid; /* new SID */
198 char *p_user; /* user who did edit */
199 char *p_date; /* date of get */
200 char *p_time; /* time of get */
201};
202
56151b9e
EA
203char *SccsPath = SCCSPATH; /* pathname of SCCS files */
204# ifdef SCCSDIR
205char *SccsDir = SCCSDIR; /* directory to begin search from */
d266e8cb 206# else
56151b9e 207char *SccsDir = "";
d266e8cb 208# endif
27082f2b 209char MyName[] = MYNAME; /* name used in messages */
629c0863 210int OutFile = -1; /* override output file for commands */
2a0234bf 211bool RealUser; /* if set, running as real user */
27e36d02
EA
212# ifdef DEBUG
213bool Debug; /* turn on tracing */
214# endif
2d351698 215\f
1cc2dcec
EA
216main(argc, argv)
217 int argc;
218 char **argv;
219{
220 register char *p;
1045e3ba 221 extern struct sccsprog *lookup();
e8a6a730 222 register int i;
1cc2dcec
EA
223
224 /*
225 ** Detect and decode flags intended for this program.
226 */
227
a97ecd0d
EA
228 if (argc < 2)
229 {
d266e8cb 230 fprintf(stderr, "Usage: %s [flags] command [flags]\n", MyName);
a97ecd0d
EA
231 exit(EX_USAGE);
232 }
233 argv[argc] = NULL;
234
1045e3ba 235 if (lookup(argv[0]) == NULL)
1cc2dcec 236 {
1045e3ba 237 while ((p = *++argv) != NULL)
1cc2dcec 238 {
1045e3ba
EA
239 if (*p != '-')
240 break;
241 switch (*++p)
242 {
243 case 'r': /* run as real user */
244 setuid(getuid());
245 RealUser++;
246 break;
247
56151b9e 248# ifndef SCCSDIR
1045e3ba
EA
249 case 'p': /* path of sccs files */
250 SccsPath = ++p;
251 break;
252
02ec3e87
EA
253 case 'd': /* directory to search from */
254 SccsDir = ++p;
255 break;
d266e8cb 256# endif
02ec3e87 257
27e36d02
EA
258# ifdef DEBUG
259 case 'T': /* trace */
260 Debug++;
261 break;
262# endif
263
1045e3ba 264 default:
d266e8cb 265 usrerr("unknown option -%s", p);
1045e3ba
EA
266 break;
267 }
1cc2dcec 268 }
1045e3ba
EA
269 if (SccsPath[0] == '\0')
270 SccsPath = ".";
1cc2dcec
EA
271 }
272
1dbb3fe7 273 i = command(argv, FALSE, FALSE, "");
e8a6a730 274 exit(i);
a97ecd0d 275}
2d351698
EA
276\f
277/*
e8a6a730
EA
278** COMMAND -- look up and perform a command
279**
280** This routine is the guts of this program. Given an
281** argument vector, it looks up the "command" (argv[0])
282** in the configuration table and does the necessary stuff.
283**
284** Parameters:
285** argv -- an argument vector to process.
286** forkflag -- if set, fork before executing the command.
1dbb3fe7
EA
287** editflag -- if set, only include flags listed in the
288** sccsklets field of the command descriptor.
289** arg0 -- a space-seperated list of arguments to insert
290** before argv.
e8a6a730
EA
291**
292** Returns:
293** zero -- command executed ok.
294** else -- error status.
295**
296** Side Effects:
297** none.
298*/
a97ecd0d 299
1dbb3fe7 300command(argv, forkflag, editflag, arg0)
a97ecd0d 301 char **argv;
e672376b 302 bool forkflag;
1dbb3fe7
EA
303 bool editflag;
304 char *arg0;
a97ecd0d
EA
305{
306 register struct sccsprog *cmd;
307 register char *p;
e672376b 308 char buf[40];
1045e3ba 309 extern struct sccsprog *lookup();
1dbb3fe7
EA
310 char *nav[1000];
311 char **np;
c9ac6819 312 register char **ap;
9c3bf134 313 register int i;
c9ac6819 314 register char *q;
9c3bf134 315 extern bool unedit();
e8a6a730 316 int rval = 0;
1dbb3fe7
EA
317 extern char *index();
318 extern char *makefile();
4cd62a3c 319 extern char *tail();
27e36d02
EA
320
321# ifdef DEBUG
322 if (Debug)
323 {
1dbb3fe7
EA
324 printf("command:\n\t\"%s\"\n", arg0);
325 for (np = argv; *np != NULL; np++)
326 printf("\t\"%s\"\n", *np);
27e36d02
EA
327 }
328# endif
2a0234bf 329
1dbb3fe7
EA
330 /*
331 ** Copy arguments.
e1e64d36
EA
332 ** Copy from arg0 & if necessary at most one arg
333 ** from argv[0].
1dbb3fe7
EA
334 */
335
c9ac6819 336 np = ap = &nav[1];
1dbb3fe7
EA
337 for (p = arg0, q = buf; *p != '\0' && *p != '/'; )
338 {
339 *np++ = q;
340 while (*p == ' ')
341 p++;
342 while (*p != ' ' && *p != '\0' && *p != '/')
343 *q++ = *p++;
344 *q++ = '\0';
345 }
346 *np = NULL;
c9ac6819 347 if (*ap == NULL)
1dbb3fe7
EA
348 *np++ = *argv++;
349
1cc2dcec
EA
350 /*
351 ** Look up command.
c9ac6819 352 ** At this point, *ap is the command name.
1cc2dcec
EA
353 */
354
c9ac6819 355 cmd = lookup(*ap);
1045e3ba 356 if (cmd == NULL)
1cc2dcec 357 {
c9ac6819 358 usrerr("Unknown command \"%s\"", *ap);
e8a6a730 359 return (EX_USAGE);
1cc2dcec
EA
360 }
361
1dbb3fe7
EA
362 /*
363 ** Copy remaining arguments doing editing as appropriate.
364 */
365
366 for (; *argv != NULL; argv++)
367 {
368 p = *argv;
369 if (*p == '-')
370 {
371 if (p[1] == '\0' || !editflag || cmd->sccsklets == NULL ||
372 index(cmd->sccsklets, p[1]) != NULL)
373 *np++ = p;
374 }
375 else
376 {
377 if (!bitset(NO_SDOT, cmd->sccsflags))
378 p = makefile(p);
379 if (p != NULL)
380 *np++ = p;
381 }
382 }
383 *np = NULL;
384
2a0234bf 385 /*
a97ecd0d 386 ** Interpret operation associated with this command.
2a0234bf
EA
387 */
388
a97ecd0d
EA
389 switch (cmd->sccsoper)
390 {
c9ac6819
EA
391 case SHELL: /* call a shell file */
392 *ap = cmd->sccspath;
393 *--ap = "sh";
394 rval = callprog("/bin/sh", cmd->sccsflags, ap, forkflag);
395 break;
396
a97ecd0d 397 case PROG: /* call an sccs prog */
c9ac6819 398 rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag);
e672376b
EA
399 break;
400
401 case CMACRO: /* command macro */
e1e64d36 402 /* step through & execute each part of the macro */
e672376b
EA
403 for (p = cmd->sccspath; *p != '\0'; p++)
404 {
1dbb3fe7
EA
405 q = p;
406 while (*p != '\0' && *p != '/')
407 p++;
c9ac6819 408 rval = command(&ap[1], *p != '\0', TRUE, q);
e8a6a730
EA
409 if (rval != 0)
410 break;
e672376b 411 }
e8a6a730 412 break;
a97ecd0d 413
4535945e 414 case FIX: /* fix a delta */
c9ac6819 415 if (strncmp(ap[1], "-r", 2) != 0)
4535945e 416 {
d266e8cb 417 usrerr("-r flag needed for fix command");
e8a6a730 418 rval = EX_USAGE;
4535945e
EA
419 break;
420 }
e1e64d36
EA
421
422 /* get the version with all changes */
c9ac6819 423 rval = command(&ap[1], TRUE, TRUE, "get -k");
e1e64d36
EA
424
425 /* now remove that version from the s-file */
e8a6a730 426 if (rval == 0)
c9ac6819 427 rval = command(&ap[1], TRUE, TRUE, "rmdel");
e1e64d36
EA
428
429 /* and edit the old version (but don't clobber new vers) */
e8a6a730 430 if (rval == 0)
c9ac6819 431 rval = command(&ap[2], FALSE, TRUE, "get -e -g");
e8a6a730 432 break;
4535945e 433
399058e3 434 case CLEAN:
e8a6a730 435 rval = clean((int) cmd->sccspath);
399058e3
EA
436 break;
437
61886f2a 438 case UNEDIT:
c9ac6819 439 for (argv = np = &ap[1]; *argv != NULL; argv++)
9c3bf134 440 {
1dbb3fe7
EA
441 if (unedit(*argv))
442 *np++ = *argv;
9c3bf134 443 }
1dbb3fe7 444 *np = NULL;
e1e64d36
EA
445
446 /* get all the files that we unedited successfully */
9c3bf134 447 if (i > 0)
c9ac6819 448 rval = command(&ap[1], FALSE, FALSE, "get");
61886f2a
EA
449 break;
450
629c0863
EA
451 case DIFFS: /* diff between s-file & edit file */
452 /* find the end of the flag arguments */
453 for (np = &ap[1]; *np != NULL && **np == '-'; np++)
454 continue;
455 argv = np;
456
457 /* for each file, do the diff */
458 while (*np != NULL)
459 {
e1e64d36 460 /* messy, but we need a null terminated argv */
629c0863
EA
461 *argv = *np++;
462 p = *np;
463 *np = NULL;
4cd62a3c 464 i = dodiff(ap, tail(*argv));
629c0863
EA
465 if (rval == 0)
466 rval = i;
467 *np = p;
468 }
469 break;
470
a97ecd0d 471 default:
d266e8cb 472 syserr("oper %d", cmd->sccsoper);
a97ecd0d
EA
473 exit(EX_SOFTWARE);
474 }
e8a6a730
EA
475# ifdef DEBUG
476 if (Debug)
477 printf("command: rval=%d\n", rval);
478# endif
479 return (rval);
a97ecd0d 480}
2d351698
EA
481\f
482/*
1045e3ba
EA
483** LOOKUP -- look up an SCCS command name.
484**
485** Parameters:
486** name -- the name of the command to look up.
487**
488** Returns:
489** ptr to command descriptor for this command.
490** NULL if no such entry.
491**
492** Side Effects:
493** none.
494*/
495
496struct sccsprog *
497lookup(name)
498 char *name;
499{
500 register struct sccsprog *cmd;
501
502 for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
503 {
504 if (strcmp(cmd->sccsname, name) == 0)
505 return (cmd);
506 }
507 return (NULL);
508}
2d351698
EA
509\f
510/*
e8a6a730
EA
511** CALLPROG -- call a program
512**
1dbb3fe7 513** Used to call the SCCS programs.
e8a6a730
EA
514**
515** Parameters:
516** progpath -- pathname of the program to call.
517** flags -- status flags from the command descriptors.
518** argv -- an argument vector to pass to the program.
519** forkflag -- if true, fork before calling, else just
520** exec.
521**
522** Returns:
523** The exit status of the program.
524** Nothing if forkflag == FALSE.
525**
526** Side Effects:
527** Can exit if forkflag == FALSE.
528*/
4535945e 529
a97ecd0d
EA
530callprog(progpath, flags, argv, forkflag)
531 char *progpath;
532 short flags;
533 char **argv;
534 bool forkflag;
535{
a97ecd0d 536 register int i;
e672376b 537 auto int st;
1dbb3fe7
EA
538
539# ifdef DEBUG
540 if (Debug)
541 {
542 printf("callprog:\n");
543 for (i = 0; argv[i] != NULL; i++)
544 printf("\t\"%s\"\n", argv[i]);
545 }
546# endif
a97ecd0d
EA
547
548 if (*argv == NULL)
549 return (-1);
2a0234bf 550
1cc2dcec 551 /*
4535945e 552 ** Fork if appropriate.
1cc2dcec
EA
553 */
554
a97ecd0d
EA
555 if (forkflag)
556 {
27e36d02
EA
557# ifdef DEBUG
558 if (Debug)
559 printf("Forking\n");
560# endif
a97ecd0d
EA
561 i = fork();
562 if (i < 0)
563 {
d266e8cb 564 syserr("cannot fork");
a97ecd0d
EA
565 exit(EX_OSERR);
566 }
567 else if (i > 0)
e672376b
EA
568 {
569 wait(&st);
e8a6a730
EA
570 if ((st & 0377) == 0)
571 st = (st >> 8) & 0377;
629c0863
EA
572 if (OutFile >= 0)
573 {
574 close(OutFile);
575 OutFile = -1;
576 }
e672376b
EA
577 return (st);
578 }
a97ecd0d 579 }
629c0863
EA
580 else if (OutFile >= 0)
581 {
582 syserr("callprog: setting stdout w/o forking");
583 exit(EX_SOFTWARE);
584 }
a97ecd0d 585
629c0863 586 /* set protection as appropriate */
a97ecd0d
EA
587 if (bitset(REALUSER, flags))
588 setuid(getuid());
a97ecd0d 589
629c0863
EA
590 /* change standard input & output if needed */
591 if (OutFile >= 0)
592 {
593 close(1);
594 dup(OutFile);
595 close(OutFile);
596 }
597
598 /* call real SCCS program */
4535945e 599 execv(progpath, argv);
d266e8cb 600 syserr("cannot execute %s", progpath);
1cc2dcec
EA
601 exit(EX_UNAVAILABLE);
602}
2d351698
EA
603\f
604/*
e2758dc3
EA
605** MAKEFILE -- make filename of SCCS file
606**
607** If the name passed is already the name of an SCCS file,
608** just return it. Otherwise, munge the name into the name
609** of the actual SCCS file.
610**
611** There are cases when it is not clear what you want to
612** do. For example, if SccsPath is an absolute pathname
613** and the name given is also an absolute pathname, we go
614** for SccsPath (& only use the last component of the name
615** passed) -- this is important for security reasons (if
616** sccs is being used as a setuid front end), but not
617** particularly intuitive.
618**
619** Parameters:
620** name -- the file name to be munged.
621**
622** Returns:
623** The pathname of the sccs file.
624** NULL on error.
625**
626** Side Effects:
627** none.
628*/
1cc2dcec
EA
629
630char *
631makefile(name)
632 char *name;
633{
634 register char *p;
635 register char c;
636 char buf[512];
1cc2dcec 637 extern char *malloc();
e2758dc3 638 extern char *rindex();
8e7b9e7e
EA
639 extern bool isdir();
640 register char *q;
e2758dc3
EA
641
642 p = rindex(name, '/');
643 if (p == NULL)
644 p = name;
645 else
646 p++;
1cc2dcec
EA
647
648 /*
8e7b9e7e 649 ** See if the name can be used as-is.
1cc2dcec
EA
650 */
651
8e7b9e7e
EA
652 if (SccsPath[0] != '/' || name[0] == '/' || strncmp(name, "./", 2) == 0)
653 {
654 if (strncmp(p, "s.", 2) == 0)
655 return (name);
656 if (isdir(name))
657 return (name);
658 }
e2758dc3
EA
659
660 /*
8e7b9e7e 661 ** Create the actual pathname.
e2758dc3
EA
662 */
663
e1e64d36 664 /* first the directory part */
8e7b9e7e 665 if (name[0] != '/')
1cc2dcec 666 {
02ec3e87 667 strcpy(buf, SccsDir);
e2758dc3
EA
668 strcat(buf, "/");
669 }
670 else
671 strcpy(buf, "");
e1e64d36
EA
672
673 /* then the head of the pathname */
8e7b9e7e
EA
674 strncat(buf, name, p - name);
675 q = &buf[strlen(buf)];
676 strcpy(q, p);
677 if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
e2758dc3 678 {
e1e64d36 679 /* sorry, no; copy the SCCS pathname & the "s." */
02ec3e87
EA
680 strcpy(q, SccsPath);
681 strcat(buf, "/s.");
e1e64d36
EA
682
683 /* and now the end of the name */
e2758dc3
EA
684 strcat(buf, p);
685 }
1cc2dcec 686
e1e64d36 687 /* if i haven't changed it, why did I do all this? */
02ec3e87
EA
688 if (strcmp(buf, name) == 0)
689 p = name;
690
691 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
1cc2dcec 692}
399058e3 693\f/*
8e7b9e7e
EA
694** ISDIR -- return true if the argument is a directory.
695**
696** Parameters:
697** name -- the pathname of the file to check.
698**
699** Returns:
700** TRUE if 'name' is a directory, FALSE otherwise.
701**
702** Side Effects:
703** none.
704*/
705
706bool
707isdir(name)
708 char *name;
709{
710 struct stat stbuf;
711
712 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
713}
2d351698
EA
714\f
715/*
e2758dc3
EA
716** SAFEPATH -- determine whether a pathname is "safe"
717**
718** "Safe" pathnames only allow you to get deeper into the
719** directory structure, i.e., full pathnames and ".." are
720** not allowed.
721**
722** Parameters:
723** p -- the name to check.
724**
725** Returns:
726** TRUE -- if the path is safe.
727** FALSE -- if the path is not safe.
728**
729** Side Effects:
730** Prints a message if the path is not safe.
731*/
732
733bool
734safepath(p)
735 register char *p;
736{
737 extern char *index();
738
739 if (*p != '/')
740 {
741 while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
742 {
743 p = index(p, '/');
744 if (p == NULL)
745 return (TRUE);
746 p++;
747 }
748 }
749
750 printf("You may not use full pathnames or \"..\"\n");
751 return (FALSE);
752}
2d351698
EA
753\f
754/*
399058e3
EA
755** CLEAN -- clean out recreatable files
756**
757** Any file for which an "s." file exists but no "p." file
758** exists in the current directory is purged.
759**
760** Parameters:
89819fbe
EA
761** tells whether this came from a "clean", "info", or
762** "check" command.
399058e3
EA
763**
764** Returns:
765** none.
766**
767** Side Effects:
89819fbe
EA
768** Removes files in the current directory.
769** Prints information regarding files being edited.
770** Exits if a "check" command.
399058e3
EA
771*/
772
89819fbe
EA
773clean(mode)
774 int mode;
399058e3
EA
775{
776 struct direct dir;
777 struct stat stbuf;
778 char buf[100];
bb9b2f29 779 char pline[120];
db4904e0
EA
780 register FILE *dirfd;
781 register char *basefile;
d7f27f60 782 bool gotedit;
bb9b2f29 783 FILE *pfp;
399058e3 784
e1e64d36
EA
785 /*
786 ** Find and open the SCCS directory.
787 */
788
0c925940
EA
789 strcpy(buf, SccsDir);
790 if (buf[0] != '\0')
791 strcat(buf, "/");
792 strcat(buf, SccsPath);
e1e64d36 793
0c925940 794 dirfd = fopen(buf, "r");
399058e3
EA
795 if (dirfd == NULL)
796 {
0c925940 797 usrerr("cannot open %s", buf);
e8a6a730 798 return (EX_NOINPUT);
399058e3
EA
799 }
800
801 /*
802 ** Scan the SCCS directory looking for s. files.
e1e64d36
EA
803 ** gotedit tells whether we have tried to clean any
804 ** files that are being edited.
399058e3
EA
805 */
806
d7f27f60 807 gotedit = FALSE;
399058e3
EA
808 while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
809 {
b8d1a34d 810 if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
399058e3
EA
811 continue;
812
813 /* got an s. file -- see if the p. file exists */
0c925940
EA
814 strcpy(buf, SccsDir);
815 if (buf[0] != '\0')
816 strcat(buf, "/");
817 strcat(buf, SccsPath);
399058e3 818 strcat(buf, "/p.");
db4904e0 819 basefile = &buf[strlen(buf)];
b8d1a34d 820 strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
bb9b2f29
EA
821 basefile[sizeof dir.d_name - 2] = '\0';
822 pfp = fopen(buf, "r");
823 if (pfp != NULL)
db4904e0 824 {
e1e64d36 825 /* the file exists -- report it's contents */
bb9b2f29 826 while (fgets(pline, sizeof pline, pfp) != NULL)
fdca7108 827 printf("%12s: being edited: %s", basefile, pline);
bb9b2f29 828 fclose(pfp);
d7f27f60 829 gotedit = TRUE;
399058e3 830 continue;
db4904e0 831 }
399058e3
EA
832
833 /* the s. file exists and no p. file exists -- unlink the g-file */
89819fbe 834 if (mode == CLEANC)
db4904e0 835 {
b8d1a34d 836 strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
db4904e0
EA
837 buf[sizeof dir.d_name - 2] = '\0';
838 unlink(buf);
839 }
399058e3
EA
840 }
841
e1e64d36 842 /* cleanup & report results */
399058e3 843 fclose(dirfd);
89819fbe 844 if (!gotedit && mode == INFOC)
fdca7108 845 printf("Nothing being edited\n");
89819fbe
EA
846 if (mode == CHECKC)
847 exit(gotedit);
e8a6a730 848 return (EX_OK);
399058e3 849}
2d351698
EA
850\f
851/*
61886f2a
EA
852** UNEDIT -- unedit a file
853**
854** Checks to see that the current user is actually editting
855** the file and arranges that s/he is not editting it.
856**
857** Parameters:
fdca7108 858** fn -- the name of the file to be unedited.
61886f2a
EA
859**
860** Returns:
9c3bf134
EA
861** TRUE -- if the file was successfully unedited.
862** FALSE -- if the file was not unedited for some
863** reason.
61886f2a
EA
864**
865** Side Effects:
866** fn is removed
867** entries are removed from pfile.
868*/
869
9c3bf134 870bool
61886f2a
EA
871unedit(fn)
872 char *fn;
873{
874 register FILE *pfp;
875 char *pfn;
876 static char tfn[] = "/tmp/sccsXXXXX";
877 FILE *tfp;
878 register char *p;
879 register char *q;
880 bool delete = FALSE;
881 bool others = FALSE;
882 char *myname;
883 extern char *getlogin();
884 struct pfile *pent;
885 extern struct pfile *getpfile();
886 char buf[120];
1dbb3fe7 887 extern char *makefile();
27e713f7
EA
888# ifdef UIDUSER
889 struct passwd *pw;
890 extern struct passwd *getpwuid();
891# endif UIDUSER
61886f2a
EA
892
893 /* make "s." filename & find the trailing component */
894 pfn = makefile(fn);
e2758dc3
EA
895 if (pfn == NULL)
896 return (FALSE);
897 q = rindex(pfn, '/');
898 if (q == NULL)
899 q = &pfn[-1];
900 if (q[1] != 's' || q[2] != '.')
61886f2a 901 {
d266e8cb 902 usrerr("bad file name \"%s\"", fn);
9c3bf134 903 return (FALSE);
61886f2a
EA
904 }
905
e1e64d36 906 /* turn "s." into "p." & try to open it */
61886f2a
EA
907 *++q = 'p';
908
909 pfp = fopen(pfn, "r");
910 if (pfp == NULL)
911 {
fdca7108 912 printf("%12s: not being edited\n", fn);
9c3bf134 913 return (FALSE);
61886f2a
EA
914 }
915
e1e64d36 916 /* create temp file for editing p-file */
61886f2a
EA
917 mktemp(tfn);
918 tfp = fopen(tfn, "w");
919 if (tfp == NULL)
920 {
d266e8cb 921 usrerr("cannot create \"%s\"", tfn);
61886f2a
EA
922 exit(EX_OSERR);
923 }
924
e1e64d36 925 /* figure out who I am */
27e713f7
EA
926# ifdef UIDUSER
927 pw = getpwuid(getuid());
928 if (pw == NULL)
929 {
d266e8cb 930 syserr("who are you? (uid=%d)", getuid());
27e713f7
EA
931 exit(EX_OSERR);
932 }
933 myname = pw->pw_name;
934# else
61886f2a 935 myname = getlogin();
27e713f7 936# endif UIDUSER
e1e64d36
EA
937
938 /*
939 ** Copy p-file to temp file, doing deletions as needed.
940 */
941
61886f2a
EA
942 while ((pent = getpfile(pfp)) != NULL)
943 {
944 if (strcmp(pent->p_user, myname) == 0)
945 {
946 /* a match */
947 delete++;
948 }
949 else
950 {
e1e64d36 951 /* output it again */
61886f2a
EA
952 fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
953 pent->p_nsid, pent->p_user, pent->p_date,
954 pent->p_time);
955 others++;
956 }
957 }
958
959 /* do final cleanup */
960 if (others)
961 {
e1e64d36 962 /* copy it back (perhaps it should be linked?) */
61886f2a
EA
963 if (freopen(tfn, "r", tfp) == NULL)
964 {
d266e8cb 965 syserr("cannot reopen \"%s\"", tfn);
61886f2a
EA
966 exit(EX_OSERR);
967 }
968 if (freopen(pfn, "w", pfp) == NULL)
969 {
d266e8cb 970 usrerr("cannot create \"%s\"", pfn);
9c3bf134 971 return (FALSE);
61886f2a
EA
972 }
973 while (fgets(buf, sizeof buf, tfp) != NULL)
974 fputs(buf, pfp);
975 }
976 else
977 {
e1e64d36 978 /* it's empty -- remove it */
61886f2a
EA
979 unlink(pfn);
980 }
981 fclose(tfp);
982 fclose(pfp);
983 unlink(tfn);
984
e1e64d36 985 /* actually remove the g-file */
61886f2a
EA
986 if (delete)
987 {
4cd62a3c
EA
988 unlink(tail(fn));
989 printf("%12s: removed\n", tail(fn));
9c3bf134 990 return (TRUE);
61886f2a
EA
991 }
992 else
993 {
fdca7108 994 printf("%12s: not being edited by you\n", fn);
9c3bf134 995 return (FALSE);
61886f2a
EA
996 }
997}
2d351698 998\f
629c0863
EA
999/*
1000** DODIFF -- diff an s-file against a g-file
1001**
1002** Parameters:
1003** getv -- argv for the 'get' command.
1004** gfile -- name of the g-file to diff against.
1005**
1006** Returns:
1007** Result of get.
1008**
1009** Side Effects:
1010** none.
1011*/
1012
1013dodiff(getv, gfile)
1014 char **getv;
1015 char *gfile;
1016{
1017 int pipev[2];
1018 int rval;
1019 register int i;
1020 register int pid;
1021 auto int st;
1022 extern int errno;
1023 int (*osig)();
1024
d597ad9e
EA
1025 printf("\n------- %s -------\n", gfile);
1026
e1e64d36 1027 /* create context for diff to run in */
629c0863
EA
1028 if (pipe(pipev) < 0)
1029 {
1030 syserr("dodiff: pipe failed");
1031 exit(EX_OSERR);
1032 }
1033 if ((pid = fork()) < 0)
1034 {
1035 syserr("dodiff: fork failed");
1036 exit(EX_OSERR);
1037 }
1038 else if (pid > 0)
1039 {
1040 /* in parent; run get */
1041 OutFile = pipev[1];
1042 close(pipev[0]);
1043 rval = command(&getv[1], TRUE, FALSE, "get -s -k -p");
1044 osig = signal(SIGINT, SIG_IGN);
1045 while (((i = wait(&st)) >= 0 && i != pid) || errno == EINTR)
1046 errno = 0;
1047 signal(SIGINT, osig);
1048 /* ignore result of diff */
1049 }
1050 else
1051 {
1052 /* in child, run diff */
1053 if (close(pipev[1]) < 0 || close(0) < 0 ||
1054 dup(pipev[0]) != 0 || close(pipev[0]) < 0)
1055 {
1056 syserr("dodiff: magic failed");
1057 exit(EX_OSERR);
1058 }
1059 execl(PROGPATH(bdiff), "bdiff", "-", gfile, NULL);
1060# ifndef V6
1061 execlp("bdiff", "bdiff", "-", gfile, NULL);
1062 execlp("diff", "diff", "-", gfile, NULL);
1063# endif NOT V6
1064 syserr("bdiff: cannot execute");
1065 exit(EX_OSERR);
1066 }
1067 return (rval);
1068}
1069\f
4cd62a3c
EA
1070/*
1071** TAIL -- return tail of filename.
1072**
1073** Parameters:
1074** fn -- the filename.
1075**
1076** Returns:
1077** a pointer to the tail of the filename; e.g., given
1078** "cmd/ls.c", "ls.c" is returned.
1079**
1080** Side Effects:
1081** none.
1082*/
1083
1084char *
1085tail(fn)
1086 register char *fn;
1087{
1088 register char *p;
1089
1090 for (p = fn; *p != 0; p++)
1091 if (*p == '/' && p[1] != '\0' && p[1] != '/')
1092 fn = &p[1];
1093 return (fn);
1094}
1095\f
2d351698 1096/*
61886f2a
EA
1097** GETPFILE -- get an entry from the p-file
1098**
1099** Parameters:
1100** pfp -- p-file file pointer
1101**
1102** Returns:
1103** pointer to p-file struct for next entry
1104** NULL on EOF or error
1105**
1106** Side Effects:
1107** Each call wipes out results of previous call.
1108*/
1109
1110struct pfile *
1111getpfile(pfp)
1112 FILE *pfp;
1113{
1114 static struct pfile ent;
1115 static char buf[120];
1116 register char *p;
1117 extern char *nextfield();
1118
1119 if (fgets(buf, sizeof buf, pfp) == NULL)
1120 return (NULL);
1121
1122 ent.p_osid = p = buf;
1123 ent.p_nsid = p = nextfield(p);
1124 ent.p_user = p = nextfield(p);
1125 ent.p_date = p = nextfield(p);
1126 ent.p_time = p = nextfield(p);
1127 if (p == NULL || nextfield(p) != NULL)
1128 return (NULL);
1129
1130 return (&ent);
1131}
1132
1133
1134char *
1135nextfield(p)
1136 register char *p;
1137{
1138 if (p == NULL || *p == '\0')
1139 return (NULL);
1140 while (*p != ' ' && *p != '\n' && *p != '\0')
1141 p++;
1142 if (*p == '\n' || *p == '\0')
1143 {
1144 *p = '\0';
1145 return (NULL);
1146 }
1147 *p++ = '\0';
1148 return (p);
1149}
2d351698
EA
1150\f
1151/*
d266e8cb
EA
1152** USRERR -- issue user-level error
1153**
1154** Parameters:
1155** f -- format string.
1156** p1-p3 -- parameters to a printf.
1157**
1158** Returns:
1159** -1
1160**
1161** Side Effects:
1162** none.
1163*/
1164
1165usrerr(f, p1, p2, p3)
1166 char *f;
1167{
1168 fprintf(stderr, "\n%s: ", MyName);
1169 fprintf(stderr, f, p1, p2, p3);
1170 fprintf(stderr, "\n");
1171
1172 return (-1);
1173}
2d351698
EA
1174\f
1175/*
d266e8cb
EA
1176** SYSERR -- print system-generated error.
1177**
1178** Parameters:
1179** f -- format string to a printf.
1180** p1, p2, p3 -- parameters to f.
1181**
1182** Returns:
1183** never.
1184**
1185** Side Effects:
1186** none.
1187*/
1188
1189syserr(f, p1, p2, p3)
1190 char *f;
1191{
1192 extern int errno;
1193
1194 fprintf(stderr, "\n%s SYSERR: ", MyName);
1195 fprintf(stderr, f, p1, p2, p3);
1196 fprintf(stderr, "\n");
1197 if (errno == 0)
1198 exit(EX_SOFTWARE);
1199 else
1200 {
1201 perror(0);
1202 exit(EX_OSERR);
1203 }
1204}