float integer actual to real formal
[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>
1cc2dcec 5# include <sysexits.h>
7a8ba074 6# include <whoami.h>
1cc2dcec 7
02ec3e87 8static char SccsId[] = "@(#)sccs.c 1.24 %G%";
2a0234bf
EA
9
10# define bitset(bit, word) ((bit) & (word))
11
12typedef char bool;
a97ecd0d
EA
13# define TRUE 1
14# define FALSE 0
81dc6403 15
1cc2dcec
EA
16struct sccsprog
17{
18 char *sccsname; /* name of SCCS routine */
a97ecd0d
EA
19 short sccsoper; /* opcode, see below */
20 short sccsflags; /* flags, see below */
1cc2dcec
EA
21 char *sccspath; /* pathname of binary implementing */
22};
23
a97ecd0d
EA
24/* values for sccsoper */
25# define PROG 0 /* call a program */
e672376b 26# define CMACRO 1 /* command substitution macro */
4535945e 27# define FIX 2 /* fix a delta */
399058e3 28# define CLEAN 3 /* clean out recreatable files */
61886f2a 29# define UNEDIT 4 /* unedit a file */
a97ecd0d 30
2a0234bf 31/* bits for sccsflags */
a97ecd0d
EA
32# define NO_SDOT 0001 /* no s. on front of args */
33# define REALUSER 0002 /* protected (e.g., admin) */
1cc2dcec 34
7a8ba074
EA
35# ifdef CSVAX
36# define PROGPATH(name) "/usr/local/name"
37# endif CSVAX
38
39# ifndef PROGPATH
40# define PROGPATH(name) "/usr/sccs/name"
41# endif PROGPATH
42
1cc2dcec
EA
43struct sccsprog SccsProg[] =
44{
7a8ba074
EA
45 "admin", PROG, REALUSER, PROGPATH(admin),
46 "chghist", PROG, 0, PROGPATH(rmdel),
47 "comb", PROG, 0, PROGPATH(comb),
48 "delta", PROG, 0, PROGPATH(delta),
49 "get", PROG, 0, PROGPATH(get),
50 "help", PROG, NO_SDOT, PROGPATH(help),
51 "prt", PROG, 0, PROGPATH(prt),
52 "rmdel", PROG, REALUSER, PROGPATH(rmdel),
53 "what", PROG, NO_SDOT, PROGPATH(what),
27e36d02
EA
54 "edit", CMACRO, 0, "get -e",
55 "delget", CMACRO, 0, "delta/get",
b7991d06 56 "deledit", CMACRO, 0, "delta/get -e",
e672376b 57 "del", CMACRO, 0, "delta/get",
4535945e
EA
58 "delt", CMACRO, 0, "delta/get",
59 "fix", FIX, 0, NULL,
db4904e0
EA
60 "clean", CLEAN, REALUSER, (char *) TRUE,
61 "info", CLEAN, REALUSER, (char *) FALSE,
61886f2a 62 "unedit", UNEDIT, 0, NULL,
a97ecd0d 63 NULL, -1, 0, NULL
1cc2dcec
EA
64};
65
61886f2a
EA
66struct pfile
67{
68 char *p_osid; /* old SID */
69 char *p_nsid; /* new SID */
70 char *p_user; /* user who did edit */
71 char *p_date; /* date of get */
72 char *p_time; /* time of get */
73};
74
2a0234bf 75char *SccsPath = "SCCS"; /* pathname of SCCS files */
02ec3e87 76char *SccsDir = ""; /* directory to begin search from */
2a0234bf 77bool RealUser; /* if set, running as real user */
27e36d02
EA
78# ifdef DEBUG
79bool Debug; /* turn on tracing */
80# endif
1cc2dcec
EA
81
82main(argc, argv)
83 int argc;
84 char **argv;
85{
86 register char *p;
1045e3ba 87 extern struct sccsprog *lookup();
1cc2dcec
EA
88
89 /*
90 ** Detect and decode flags intended for this program.
91 */
92
a97ecd0d
EA
93 if (argc < 2)
94 {
95 fprintf(stderr, "Usage: sccs [flags] command [flags]\n");
96 exit(EX_USAGE);
97 }
98 argv[argc] = NULL;
99
1045e3ba 100 if (lookup(argv[0]) == NULL)
1cc2dcec 101 {
1045e3ba 102 while ((p = *++argv) != NULL)
1cc2dcec 103 {
1045e3ba
EA
104 if (*p != '-')
105 break;
106 switch (*++p)
107 {
108 case 'r': /* run as real user */
109 setuid(getuid());
110 RealUser++;
111 break;
112
113 case 'p': /* path of sccs files */
114 SccsPath = ++p;
115 break;
116
02ec3e87
EA
117 case 'd': /* directory to search from */
118 SccsDir = ++p;
119 break;
120
27e36d02
EA
121# ifdef DEBUG
122 case 'T': /* trace */
123 Debug++;
124 break;
125# endif
126
1045e3ba
EA
127 default:
128 fprintf(stderr, "Sccs: unknown option -%s\n", p);
129 break;
130 }
1cc2dcec 131 }
1045e3ba
EA
132 if (SccsPath[0] == '\0')
133 SccsPath = ".";
1cc2dcec
EA
134 }
135
e672376b 136 command(argv, FALSE);
a97ecd0d
EA
137 exit(EX_OK);
138}
139
e672376b 140command(argv, forkflag)
a97ecd0d 141 char **argv;
e672376b 142 bool forkflag;
a97ecd0d
EA
143{
144 register struct sccsprog *cmd;
145 register char *p;
e672376b
EA
146 register char *q;
147 char buf[40];
1045e3ba 148 extern struct sccsprog *lookup();
9c3bf134 149 char *nav[200];
27e36d02 150 char **avp;
9c3bf134
EA
151 register int i;
152 extern bool unedit();
27e36d02
EA
153
154# ifdef DEBUG
155 if (Debug)
156 {
157 printf("command:\n");
158 for (avp = argv; *avp != NULL; avp++)
159 printf(" \"%s\"\n", *avp);
160 }
161# endif
2a0234bf 162
1cc2dcec
EA
163 /*
164 ** Look up command.
a97ecd0d 165 ** At this point, argv points to the command name.
1cc2dcec
EA
166 */
167
61886f2a 168 cmd = lookup(argv[0]);
1045e3ba 169 if (cmd == NULL)
1cc2dcec 170 {
61886f2a 171 fprintf(stderr, "Sccs: Unknown command \"%s\"\n", argv[0]);
1cc2dcec
EA
172 exit(EX_USAGE);
173 }
174
2a0234bf 175 /*
a97ecd0d 176 ** Interpret operation associated with this command.
2a0234bf
EA
177 */
178
a97ecd0d
EA
179 switch (cmd->sccsoper)
180 {
181 case PROG: /* call an sccs prog */
e672376b
EA
182 callprog(cmd->sccspath, cmd->sccsflags, argv, forkflag);
183 break;
184
185 case CMACRO: /* command macro */
186 for (p = cmd->sccspath; *p != '\0'; p++)
187 {
27e36d02
EA
188 avp = nav;
189 *avp++ = buf;
e672376b 190 for (q = buf; *p != '/' && *p != '\0'; p++, q++)
27e36d02
EA
191 {
192 if (*p == ' ')
193 {
194 *q = '\0';
195 *avp++ = &q[1];
196 }
197 else
198 *q = *p;
199 }
e672376b 200 *q = '\0';
27e36d02
EA
201 *avp = NULL;
202 xcommand(&argv[1], *p != '\0', nav[0], nav[1], nav[2],
203 nav[3], nav[4], nav[5], nav[6]);
e672376b
EA
204 }
205 fprintf(stderr, "Sccs internal error: CMACRO\n");
a97ecd0d
EA
206 exit(EX_SOFTWARE);
207
4535945e 208 case FIX: /* fix a delta */
b8d1a34d 209 if (strncmp(argv[1], "-r", 2) != 0)
4535945e
EA
210 {
211 fprintf(stderr, "Sccs: -r flag needed for fix command\n");
212 break;
213 }
214 xcommand(&argv[1], TRUE, "get", "-k", NULL);
215 xcommand(&argv[1], TRUE, "rmdel", NULL);
216 xcommand(&argv[2], FALSE, "get", "-e", "-g", NULL);
217 fprintf(stderr, "Sccs internal error: FIX\n");
218 exit(EX_SOFTWARE);
219
399058e3 220 case CLEAN:
db4904e0 221 clean((bool) cmd->sccspath);
399058e3
EA
222 break;
223
61886f2a 224 case UNEDIT:
9c3bf134 225 i = 0;
61886f2a 226 for (avp = &argv[1]; *avp != NULL; avp++)
9c3bf134
EA
227 {
228 if (unedit(*avp))
229 nav[i++] = *avp;
230 }
231 nav[i] = NULL;
232 if (i > 0)
233 xcommand(nav, FALSE, "get", NULL);
61886f2a
EA
234 break;
235
a97ecd0d
EA
236 default:
237 fprintf(stderr, "Sccs internal error: oper %d\n", cmd->sccsoper);
238 exit(EX_SOFTWARE);
239 }
240}
1045e3ba
EA
241\f/*
242** LOOKUP -- look up an SCCS command name.
243**
244** Parameters:
245** name -- the name of the command to look up.
246**
247** Returns:
248** ptr to command descriptor for this command.
249** NULL if no such entry.
250**
251** Side Effects:
252** none.
253*/
254
255struct sccsprog *
256lookup(name)
257 char *name;
258{
259 register struct sccsprog *cmd;
260
261 for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
262 {
263 if (strcmp(cmd->sccsname, name) == 0)
264 return (cmd);
265 }
266 return (NULL);
267}
a97ecd0d 268
4535945e
EA
269
270xcommand(argv, forkflag, arg0)
271 char **argv;
272 bool forkflag;
273 char *arg0;
274{
275 register char **av;
276 char *newargv[1000];
277 register char **np;
278
279 np = newargv;
280 for (av = &arg0; *av != NULL; av++)
281 *np++ = *av;
282 for (av = argv; *av != NULL; av++)
283 *np++ = *av;
284 *np = NULL;
285 command(newargv, forkflag);
286}
287
a97ecd0d
EA
288callprog(progpath, flags, argv, forkflag)
289 char *progpath;
290 short flags;
291 char **argv;
292 bool forkflag;
293{
294 register char *p;
295 register char **av;
a97ecd0d
EA
296 extern char *makefile();
297 register int i;
e672376b 298 auto int st;
e2758dc3 299 register char **nav;
a97ecd0d
EA
300
301 if (*argv == NULL)
302 return (-1);
2a0234bf 303
1cc2dcec 304 /*
4535945e 305 ** Fork if appropriate.
1cc2dcec
EA
306 */
307
a97ecd0d
EA
308 if (forkflag)
309 {
27e36d02
EA
310# ifdef DEBUG
311 if (Debug)
312 printf("Forking\n");
313# endif
a97ecd0d
EA
314 i = fork();
315 if (i < 0)
316 {
317 fprintf(stderr, "Sccs: cannot fork");
318 exit(EX_OSERR);
319 }
320 else if (i > 0)
e672376b
EA
321 {
322 wait(&st);
323 return (st);
324 }
a97ecd0d
EA
325 }
326
4535945e
EA
327 /*
328 ** Build new argument vector.
329 */
330
331 /* copy program filename arguments and flags */
e2758dc3 332 nav = &argv[1];
4535945e
EA
333 av = argv;
334 while ((p = *++av) != NULL)
335 {
336 if (!bitset(NO_SDOT, flags) && *p != '-')
e2758dc3
EA
337 *nav = makefile(p);
338 else
339 *nav = p;
340 if (*nav != NULL)
341 nav++;
4535945e 342 }
e2758dc3 343 *nav = NULL;
4535945e 344
a97ecd0d
EA
345 /*
346 ** Set protection as appropriate.
347 */
348
349 if (bitset(REALUSER, flags))
350 setuid(getuid());
4535945e 351
a97ecd0d 352 /*
4535945e 353 ** Call real SCCS program.
a97ecd0d
EA
354 */
355
4535945e 356 execv(progpath, argv);
1cc2dcec 357 fprintf(stderr, "Sccs: cannot execute ");
a97ecd0d 358 perror(progpath);
1cc2dcec
EA
359 exit(EX_UNAVAILABLE);
360}
e2758dc3
EA
361\f/*
362** MAKEFILE -- make filename of SCCS file
363**
364** If the name passed is already the name of an SCCS file,
365** just return it. Otherwise, munge the name into the name
366** of the actual SCCS file.
367**
368** There are cases when it is not clear what you want to
369** do. For example, if SccsPath is an absolute pathname
370** and the name given is also an absolute pathname, we go
371** for SccsPath (& only use the last component of the name
372** passed) -- this is important for security reasons (if
373** sccs is being used as a setuid front end), but not
374** particularly intuitive.
375**
376** Parameters:
377** name -- the file name to be munged.
378**
379** Returns:
380** The pathname of the sccs file.
381** NULL on error.
382**
383** Side Effects:
384** none.
385*/
1cc2dcec
EA
386
387char *
388makefile(name)
389 char *name;
390{
391 register char *p;
392 register char c;
393 char buf[512];
1cc2dcec 394 extern char *malloc();
e2758dc3 395 extern char *rindex();
8e7b9e7e
EA
396 extern bool isdir();
397 register char *q;
e2758dc3
EA
398
399 p = rindex(name, '/');
400 if (p == NULL)
401 p = name;
402 else
403 p++;
1cc2dcec
EA
404
405 /*
8e7b9e7e 406 ** See if the name can be used as-is.
1cc2dcec
EA
407 */
408
8e7b9e7e
EA
409 if (SccsPath[0] != '/' || name[0] == '/' || strncmp(name, "./", 2) == 0)
410 {
411 if (strncmp(p, "s.", 2) == 0)
412 return (name);
413 if (isdir(name))
414 return (name);
415 }
e2758dc3
EA
416
417 /*
8e7b9e7e 418 ** Create the actual pathname.
e2758dc3
EA
419 */
420
8e7b9e7e 421 if (name[0] != '/')
1cc2dcec 422 {
02ec3e87 423 strcpy(buf, SccsDir);
e2758dc3
EA
424 strcat(buf, "/");
425 }
426 else
427 strcpy(buf, "");
8e7b9e7e
EA
428 strncat(buf, name, p - name);
429 q = &buf[strlen(buf)];
430 strcpy(q, p);
431 if (strncmp(p, "s.", 2) != 0 && !isdir(buf))
e2758dc3 432 {
02ec3e87
EA
433 strcpy(q, SccsPath);
434 strcat(buf, "/s.");
e2758dc3
EA
435 strcat(buf, p);
436 }
1cc2dcec 437
02ec3e87
EA
438 if (strcmp(buf, name) == 0)
439 p = name;
440
441 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
1cc2dcec 442}
399058e3 443\f/*
8e7b9e7e
EA
444** ISDIR -- return true if the argument is a directory.
445**
446** Parameters:
447** name -- the pathname of the file to check.
448**
449** Returns:
450** TRUE if 'name' is a directory, FALSE otherwise.
451**
452** Side Effects:
453** none.
454*/
455
456bool
457isdir(name)
458 char *name;
459{
460 struct stat stbuf;
461
462 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
463}
464\f/*
e2758dc3
EA
465** SAFEPATH -- determine whether a pathname is "safe"
466**
467** "Safe" pathnames only allow you to get deeper into the
468** directory structure, i.e., full pathnames and ".." are
469** not allowed.
470**
471** Parameters:
472** p -- the name to check.
473**
474** Returns:
475** TRUE -- if the path is safe.
476** FALSE -- if the path is not safe.
477**
478** Side Effects:
479** Prints a message if the path is not safe.
480*/
481
482bool
483safepath(p)
484 register char *p;
485{
486 extern char *index();
487
488 if (*p != '/')
489 {
490 while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0)
491 {
492 p = index(p, '/');
493 if (p == NULL)
494 return (TRUE);
495 p++;
496 }
497 }
498
499 printf("You may not use full pathnames or \"..\"\n");
500 return (FALSE);
501}
502\f/*
399058e3
EA
503** CLEAN -- clean out recreatable files
504**
505** Any file for which an "s." file exists but no "p." file
506** exists in the current directory is purged.
507**
508** Parameters:
db4904e0
EA
509** really -- if TRUE, remove everything.
510** else, just report status.
399058e3
EA
511**
512** Returns:
513** none.
514**
515** Side Effects:
516** removes files in the current directory.
517*/
518
db4904e0
EA
519clean(really)
520 bool really;
399058e3
EA
521{
522 struct direct dir;
523 struct stat stbuf;
524 char buf[100];
bb9b2f29 525 char pline[120];
db4904e0
EA
526 register FILE *dirfd;
527 register char *basefile;
d7f27f60 528 bool gotedit;
bb9b2f29 529 FILE *pfp;
399058e3
EA
530
531 dirfd = fopen(SccsPath, "r");
532 if (dirfd == NULL)
533 {
534 fprintf(stderr, "Sccs: cannot open %s\n", SccsPath);
535 return;
536 }
537
538 /*
539 ** Scan the SCCS directory looking for s. files.
540 */
541
d7f27f60 542 gotedit = FALSE;
399058e3
EA
543 while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
544 {
b8d1a34d 545 if (dir.d_ino == 0 || strncmp(dir.d_name, "s.", 2) != 0)
399058e3
EA
546 continue;
547
548 /* got an s. file -- see if the p. file exists */
549 strcpy(buf, SccsPath);
550 strcat(buf, "/p.");
db4904e0 551 basefile = &buf[strlen(buf)];
b8d1a34d 552 strncpy(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
bb9b2f29
EA
553 basefile[sizeof dir.d_name - 2] = '\0';
554 pfp = fopen(buf, "r");
555 if (pfp != NULL)
db4904e0 556 {
bb9b2f29 557 while (fgets(pline, sizeof pline, pfp) != NULL)
fdca7108 558 printf("%12s: being edited: %s", basefile, pline);
bb9b2f29 559 fclose(pfp);
d7f27f60 560 gotedit = TRUE;
399058e3 561 continue;
db4904e0 562 }
399058e3
EA
563
564 /* the s. file exists and no p. file exists -- unlink the g-file */
db4904e0
EA
565 if (really)
566 {
b8d1a34d 567 strncpy(buf, &dir.d_name[2], sizeof dir.d_name - 2);
db4904e0
EA
568 buf[sizeof dir.d_name - 2] = '\0';
569 unlink(buf);
570 }
399058e3
EA
571 }
572
573 fclose(dirfd);
d7f27f60 574 if (!gotedit && !really)
fdca7108 575 printf("Nothing being edited\n");
399058e3 576}
61886f2a
EA
577\f/*
578** UNEDIT -- unedit a file
579**
580** Checks to see that the current user is actually editting
581** the file and arranges that s/he is not editting it.
582**
583** Parameters:
fdca7108 584** fn -- the name of the file to be unedited.
61886f2a
EA
585**
586** Returns:
9c3bf134
EA
587** TRUE -- if the file was successfully unedited.
588** FALSE -- if the file was not unedited for some
589** reason.
61886f2a
EA
590**
591** Side Effects:
592** fn is removed
593** entries are removed from pfile.
594*/
595
9c3bf134 596bool
61886f2a
EA
597unedit(fn)
598 char *fn;
599{
600 register FILE *pfp;
601 char *pfn;
602 static char tfn[] = "/tmp/sccsXXXXX";
603 FILE *tfp;
604 register char *p;
605 register char *q;
606 bool delete = FALSE;
607 bool others = FALSE;
608 char *myname;
609 extern char *getlogin();
610 struct pfile *pent;
611 extern struct pfile *getpfile();
612 char buf[120];
613
614 /* make "s." filename & find the trailing component */
615 pfn = makefile(fn);
e2758dc3
EA
616 if (pfn == NULL)
617 return (FALSE);
618 q = rindex(pfn, '/');
619 if (q == NULL)
620 q = &pfn[-1];
621 if (q[1] != 's' || q[2] != '.')
61886f2a
EA
622 {
623 fprintf(stderr, "Sccs: bad file name \"%s\"\n", fn);
9c3bf134 624 return (FALSE);
61886f2a
EA
625 }
626
627 /* turn "s." into "p." */
628 *++q = 'p';
629
630 pfp = fopen(pfn, "r");
631 if (pfp == NULL)
632 {
fdca7108 633 printf("%12s: not being edited\n", fn);
9c3bf134 634 return (FALSE);
61886f2a
EA
635 }
636
637 /*
638 ** Copy p-file to temp file, doing deletions as needed.
639 */
640
641 mktemp(tfn);
642 tfp = fopen(tfn, "w");
643 if (tfp == NULL)
644 {
645 fprintf(stderr, "Sccs: cannot create \"%s\"\n", tfn);
646 exit(EX_OSERR);
647 }
648
649 myname = getlogin();
650 while ((pent = getpfile(pfp)) != NULL)
651 {
652 if (strcmp(pent->p_user, myname) == 0)
653 {
654 /* a match */
655 delete++;
656 }
657 else
658 {
659 fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
660 pent->p_nsid, pent->p_user, pent->p_date,
661 pent->p_time);
662 others++;
663 }
664 }
665
666 /* do final cleanup */
667 if (others)
668 {
669 if (freopen(tfn, "r", tfp) == NULL)
670 {
671 fprintf(stderr, "Sccs: cannot reopen \"%s\"\n", tfn);
672 exit(EX_OSERR);
673 }
674 if (freopen(pfn, "w", pfp) == NULL)
675 {
676 fprintf(stderr, "Sccs: cannot create \"%s\"\n", pfn);
9c3bf134 677 return (FALSE);
61886f2a
EA
678 }
679 while (fgets(buf, sizeof buf, tfp) != NULL)
680 fputs(buf, pfp);
681 }
682 else
683 {
684 unlink(pfn);
685 }
686 fclose(tfp);
687 fclose(pfp);
688 unlink(tfn);
689
690 if (delete)
691 {
692 unlink(fn);
693 printf("%12s: removed\n", fn);
9c3bf134 694 return (TRUE);
61886f2a
EA
695 }
696 else
697 {
fdca7108 698 printf("%12s: not being edited by you\n", fn);
9c3bf134 699 return (FALSE);
61886f2a
EA
700 }
701}
702\f/*
703** GETPFILE -- get an entry from the p-file
704**
705** Parameters:
706** pfp -- p-file file pointer
707**
708** Returns:
709** pointer to p-file struct for next entry
710** NULL on EOF or error
711**
712** Side Effects:
713** Each call wipes out results of previous call.
714*/
715
716struct pfile *
717getpfile(pfp)
718 FILE *pfp;
719{
720 static struct pfile ent;
721 static char buf[120];
722 register char *p;
723 extern char *nextfield();
724
725 if (fgets(buf, sizeof buf, pfp) == NULL)
726 return (NULL);
727
728 ent.p_osid = p = buf;
729 ent.p_nsid = p = nextfield(p);
730 ent.p_user = p = nextfield(p);
731 ent.p_date = p = nextfield(p);
732 ent.p_time = p = nextfield(p);
733 if (p == NULL || nextfield(p) != NULL)
734 return (NULL);
735
736 return (&ent);
737}
738
739
740char *
741nextfield(p)
742 register char *p;
743{
744 if (p == NULL || *p == '\0')
745 return (NULL);
746 while (*p != ' ' && *p != '\n' && *p != '\0')
747 p++;
748 if (*p == '\n' || *p == '\0')
749 {
750 *p = '\0';
751 return (NULL);
752 }
753 *p++ = '\0';
754 return (p);
755}