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