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