added "unedit" command
[unix-history] / usr / src / usr.bin / sccs / sccs.c
CommitLineData
1cc2dcec
EA
1# include <stdio.h>
2# include <sys/types.h>
3# include <sys/stat.h>
399058e3 4# include <sys/dir.h>
1cc2dcec 5# include <sysexits.h>
7a8ba074 6# include <whoami.h>
1cc2dcec 7
61886f2a 8static char SccsId[] = "@(#)sccs.c 1.17 %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",
56 "deled", 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 */
2a0234bf 76bool RealUser; /* if set, running as real user */
27e36d02
EA
77# ifdef DEBUG
78bool Debug; /* turn on tracing */
79# endif
1cc2dcec
EA
80
81main(argc, argv)
82 int argc;
83 char **argv;
84{
85 register char *p;
1045e3ba 86 extern struct sccsprog *lookup();
1cc2dcec
EA
87
88 /*
89 ** Detect and decode flags intended for this program.
90 */
91
a97ecd0d
EA
92 if (argc < 2)
93 {
94 fprintf(stderr, "Usage: sccs [flags] command [flags]\n");
95 exit(EX_USAGE);
96 }
97 argv[argc] = NULL;
98
1045e3ba 99 if (lookup(argv[0]) == NULL)
1cc2dcec 100 {
1045e3ba 101 while ((p = *++argv) != NULL)
1cc2dcec 102 {
1045e3ba
EA
103 if (*p != '-')
104 break;
105 switch (*++p)
106 {
107 case 'r': /* run as real user */
108 setuid(getuid());
109 RealUser++;
110 break;
111
112 case 'p': /* path of sccs files */
113 SccsPath = ++p;
114 break;
115
27e36d02
EA
116# ifdef DEBUG
117 case 'T': /* trace */
118 Debug++;
119 break;
120# endif
121
1045e3ba
EA
122 default:
123 fprintf(stderr, "Sccs: unknown option -%s\n", p);
124 break;
125 }
1cc2dcec 126 }
1045e3ba
EA
127 if (SccsPath[0] == '\0')
128 SccsPath = ".";
1cc2dcec
EA
129 }
130
e672376b 131 command(argv, FALSE);
a97ecd0d
EA
132 exit(EX_OK);
133}
134
e672376b 135command(argv, forkflag)
a97ecd0d 136 char **argv;
e672376b 137 bool forkflag;
a97ecd0d
EA
138{
139 register struct sccsprog *cmd;
140 register char *p;
e672376b
EA
141 register char *q;
142 char buf[40];
1045e3ba 143 extern struct sccsprog *lookup();
27e36d02
EA
144 char *nav[7];
145 char **avp;
146
147# ifdef DEBUG
148 if (Debug)
149 {
150 printf("command:\n");
151 for (avp = argv; *avp != NULL; avp++)
152 printf(" \"%s\"\n", *avp);
153 }
154# endif
2a0234bf 155
1cc2dcec
EA
156 /*
157 ** Look up command.
a97ecd0d 158 ** At this point, argv points to the command name.
1cc2dcec
EA
159 */
160
61886f2a 161 cmd = lookup(argv[0]);
1045e3ba 162 if (cmd == NULL)
1cc2dcec 163 {
61886f2a 164 fprintf(stderr, "Sccs: Unknown command \"%s\"\n", argv[0]);
1cc2dcec
EA
165 exit(EX_USAGE);
166 }
167
2a0234bf 168 /*
a97ecd0d 169 ** Interpret operation associated with this command.
2a0234bf
EA
170 */
171
a97ecd0d
EA
172 switch (cmd->sccsoper)
173 {
174 case PROG: /* call an sccs prog */
e672376b
EA
175 callprog(cmd->sccspath, cmd->sccsflags, argv, forkflag);
176 break;
177
178 case CMACRO: /* command macro */
179 for (p = cmd->sccspath; *p != '\0'; p++)
180 {
27e36d02
EA
181 avp = nav;
182 *avp++ = buf;
e672376b 183 for (q = buf; *p != '/' && *p != '\0'; p++, q++)
27e36d02
EA
184 {
185 if (*p == ' ')
186 {
187 *q = '\0';
188 *avp++ = &q[1];
189 }
190 else
191 *q = *p;
192 }
e672376b 193 *q = '\0';
27e36d02
EA
194 *avp = NULL;
195 xcommand(&argv[1], *p != '\0', nav[0], nav[1], nav[2],
196 nav[3], nav[4], nav[5], nav[6]);
e672376b
EA
197 }
198 fprintf(stderr, "Sccs internal error: CMACRO\n");
a97ecd0d
EA
199 exit(EX_SOFTWARE);
200
4535945e 201 case FIX: /* fix a delta */
399058e3 202 if (strcmpn(argv[1], "-r", 2) != 0)
4535945e
EA
203 {
204 fprintf(stderr, "Sccs: -r flag needed for fix command\n");
205 break;
206 }
207 xcommand(&argv[1], TRUE, "get", "-k", NULL);
208 xcommand(&argv[1], TRUE, "rmdel", NULL);
209 xcommand(&argv[2], FALSE, "get", "-e", "-g", NULL);
210 fprintf(stderr, "Sccs internal error: FIX\n");
211 exit(EX_SOFTWARE);
212
399058e3 213 case CLEAN:
db4904e0 214 clean((bool) cmd->sccspath);
399058e3
EA
215 break;
216
61886f2a
EA
217 case UNEDIT:
218 for (avp = &argv[1]; *avp != NULL; avp++)
219 unedit(*avp);
220 break;
221
a97ecd0d
EA
222 default:
223 fprintf(stderr, "Sccs internal error: oper %d\n", cmd->sccsoper);
224 exit(EX_SOFTWARE);
225 }
226}
1045e3ba
EA
227\f/*
228** LOOKUP -- look up an SCCS command name.
229**
230** Parameters:
231** name -- the name of the command to look up.
232**
233** Returns:
234** ptr to command descriptor for this command.
235** NULL if no such entry.
236**
237** Side Effects:
238** none.
239*/
240
241struct sccsprog *
242lookup(name)
243 char *name;
244{
245 register struct sccsprog *cmd;
246
247 for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
248 {
249 if (strcmp(cmd->sccsname, name) == 0)
250 return (cmd);
251 }
252 return (NULL);
253}
a97ecd0d 254
4535945e
EA
255
256xcommand(argv, forkflag, arg0)
257 char **argv;
258 bool forkflag;
259 char *arg0;
260{
261 register char **av;
262 char *newargv[1000];
263 register char **np;
264
265 np = newargv;
266 for (av = &arg0; *av != NULL; av++)
267 *np++ = *av;
268 for (av = argv; *av != NULL; av++)
269 *np++ = *av;
270 *np = NULL;
271 command(newargv, forkflag);
272}
273
a97ecd0d
EA
274callprog(progpath, flags, argv, forkflag)
275 char *progpath;
276 short flags;
277 char **argv;
278 bool forkflag;
279{
280 register char *p;
281 register char **av;
a97ecd0d
EA
282 extern char *makefile();
283 register int i;
e672376b 284 auto int st;
a97ecd0d
EA
285
286 if (*argv == NULL)
287 return (-1);
2a0234bf 288
1cc2dcec 289 /*
4535945e 290 ** Fork if appropriate.
1cc2dcec
EA
291 */
292
a97ecd0d
EA
293 if (forkflag)
294 {
27e36d02
EA
295# ifdef DEBUG
296 if (Debug)
297 printf("Forking\n");
298# endif
a97ecd0d
EA
299 i = fork();
300 if (i < 0)
301 {
302 fprintf(stderr, "Sccs: cannot fork");
303 exit(EX_OSERR);
304 }
305 else if (i > 0)
e672376b
EA
306 {
307 wait(&st);
308 return (st);
309 }
a97ecd0d
EA
310 }
311
4535945e
EA
312 /*
313 ** Build new argument vector.
314 */
315
316 /* copy program filename arguments and flags */
317 av = argv;
318 while ((p = *++av) != NULL)
319 {
320 if (!bitset(NO_SDOT, flags) && *p != '-')
321 *av = makefile(p);
322 }
323
a97ecd0d
EA
324 /*
325 ** Set protection as appropriate.
326 */
327
328 if (bitset(REALUSER, flags))
329 setuid(getuid());
4535945e 330
a97ecd0d 331 /*
4535945e 332 ** Call real SCCS program.
a97ecd0d
EA
333 */
334
4535945e 335 execv(progpath, argv);
1cc2dcec 336 fprintf(stderr, "Sccs: cannot execute ");
a97ecd0d 337 perror(progpath);
1cc2dcec
EA
338 exit(EX_UNAVAILABLE);
339}
340
341
342char *
343makefile(name)
344 char *name;
345{
346 register char *p;
347 register char c;
348 char buf[512];
349 struct stat stbuf;
350 extern char *malloc();
351
352 /*
353 ** See if this filename should be used as-is.
354 ** There are three conditions where this can occur.
355 ** 1. The name already begins with "s.".
356 ** 2. The name has a "/" in it somewhere.
357 ** 3. The name references a directory.
358 */
359
399058e3 360 if (strcmpn(name, "s.", 2) == 0)
1cc2dcec
EA
361 return (name);
362 for (p = name; (c = *p) != '\0'; p++)
363 {
364 if (c == '/')
365 return (name);
366 }
367 if (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR)
368 return (name);
369
370 /*
371 ** Prepend the path of the sccs file.
372 */
373
374 strcpy(buf, SccsPath);
2a0234bf 375 strcat(buf, "/s.");
1cc2dcec
EA
376 strcat(buf, name);
377 p = malloc(strlen(buf) + 1);
378 if (p == NULL)
379 {
380 perror("Sccs: no mem");
381 exit(EX_OSERR);
382 }
383 strcpy(p, buf);
384 return (p);
385}
399058e3
EA
386\f/*
387** CLEAN -- clean out recreatable files
388**
389** Any file for which an "s." file exists but no "p." file
390** exists in the current directory is purged.
391**
392** Parameters:
db4904e0
EA
393** really -- if TRUE, remove everything.
394** else, just report status.
399058e3
EA
395**
396** Returns:
397** none.
398**
399** Side Effects:
400** removes files in the current directory.
401*/
402
db4904e0
EA
403clean(really)
404 bool really;
399058e3
EA
405{
406 struct direct dir;
407 struct stat stbuf;
408 char buf[100];
bb9b2f29 409 char pline[120];
db4904e0
EA
410 register FILE *dirfd;
411 register char *basefile;
d7f27f60 412 bool gotedit;
bb9b2f29 413 FILE *pfp;
399058e3
EA
414
415 dirfd = fopen(SccsPath, "r");
416 if (dirfd == NULL)
417 {
418 fprintf(stderr, "Sccs: cannot open %s\n", SccsPath);
419 return;
420 }
421
422 /*
423 ** Scan the SCCS directory looking for s. files.
424 */
425
d7f27f60 426 gotedit = FALSE;
399058e3
EA
427 while (fread(&dir, sizeof dir, 1, dirfd) != NULL)
428 {
429 if (dir.d_ino == 0 || strcmpn(dir.d_name, "s.", 2) != 0)
430 continue;
431
432 /* got an s. file -- see if the p. file exists */
433 strcpy(buf, SccsPath);
434 strcat(buf, "/p.");
db4904e0 435 basefile = &buf[strlen(buf)];
db4904e0 436 strcpyn(basefile, &dir.d_name[2], sizeof dir.d_name - 2);
bb9b2f29
EA
437 basefile[sizeof dir.d_name - 2] = '\0';
438 pfp = fopen(buf, "r");
439 if (pfp != NULL)
db4904e0 440 {
bb9b2f29
EA
441 while (fgets(pline, sizeof pline, pfp) != NULL)
442 printf("%12s: being editted: %s", basefile, pline);
443 fclose(pfp);
d7f27f60 444 gotedit = TRUE;
399058e3 445 continue;
db4904e0 446 }
399058e3
EA
447
448 /* the s. file exists and no p. file exists -- unlink the g-file */
db4904e0
EA
449 if (really)
450 {
451 strcpyn(buf, &dir.d_name[2], sizeof dir.d_name - 2);
452 buf[sizeof dir.d_name - 2] = '\0';
453 unlink(buf);
454 }
399058e3
EA
455 }
456
457 fclose(dirfd);
d7f27f60
EA
458 if (!gotedit && !really)
459 printf("Nothing being editted\n");
399058e3 460}
61886f2a
EA
461\f/*
462** UNEDIT -- unedit a file
463**
464** Checks to see that the current user is actually editting
465** the file and arranges that s/he is not editting it.
466**
467** Parameters:
468** fn -- the name of the file to be uneditted.
469**
470** Returns:
471** none.
472**
473** Side Effects:
474** fn is removed
475** entries are removed from pfile.
476*/
477
478unedit(fn)
479 char *fn;
480{
481 register FILE *pfp;
482 char *pfn;
483 static char tfn[] = "/tmp/sccsXXXXX";
484 FILE *tfp;
485 register char *p;
486 register char *q;
487 bool delete = FALSE;
488 bool others = FALSE;
489 char *myname;
490 extern char *getlogin();
491 struct pfile *pent;
492 extern struct pfile *getpfile();
493 char buf[120];
494
495 /* make "s." filename & find the trailing component */
496 pfn = makefile(fn);
497 q = &pfn[strlen(pfn) - 1];
498 while (q > pfn && *q != '/')
499 q--;
500 if (q <= pfn && (q[0] != 's' || q[1] != '.'))
501 {
502 fprintf(stderr, "Sccs: bad file name \"%s\"\n", fn);
503 return;
504 }
505
506 /* turn "s." into "p." */
507 *++q = 'p';
508
509 pfp = fopen(pfn, "r");
510 if (pfp == NULL)
511 {
512 printf("%12s: not being editted\n", fn);
513 return;
514 }
515
516 /*
517 ** Copy p-file to temp file, doing deletions as needed.
518 */
519
520 mktemp(tfn);
521 tfp = fopen(tfn, "w");
522 if (tfp == NULL)
523 {
524 fprintf(stderr, "Sccs: cannot create \"%s\"\n", tfn);
525 exit(EX_OSERR);
526 }
527
528 myname = getlogin();
529 while ((pent = getpfile(pfp)) != NULL)
530 {
531 if (strcmp(pent->p_user, myname) == 0)
532 {
533 /* a match */
534 delete++;
535 }
536 else
537 {
538 fprintf(tfp, "%s %s %s %s %s\n", pent->p_osid,
539 pent->p_nsid, pent->p_user, pent->p_date,
540 pent->p_time);
541 others++;
542 }
543 }
544
545 /* do final cleanup */
546 if (others)
547 {
548 if (freopen(tfn, "r", tfp) == NULL)
549 {
550 fprintf(stderr, "Sccs: cannot reopen \"%s\"\n", tfn);
551 exit(EX_OSERR);
552 }
553 if (freopen(pfn, "w", pfp) == NULL)
554 {
555 fprintf(stderr, "Sccs: cannot create \"%s\"\n", pfn);
556 return;
557 }
558 while (fgets(buf, sizeof buf, tfp) != NULL)
559 fputs(buf, pfp);
560 }
561 else
562 {
563 unlink(pfn);
564 }
565 fclose(tfp);
566 fclose(pfp);
567 unlink(tfn);
568
569 if (delete)
570 {
571 unlink(fn);
572 printf("%12s: removed\n", fn);
573 }
574 else
575 {
576 printf("%12s: not being editted by you\n", fn);
577 }
578}
579\f/*
580** GETPFILE -- get an entry from the p-file
581**
582** Parameters:
583** pfp -- p-file file pointer
584**
585** Returns:
586** pointer to p-file struct for next entry
587** NULL on EOF or error
588**
589** Side Effects:
590** Each call wipes out results of previous call.
591*/
592
593struct pfile *
594getpfile(pfp)
595 FILE *pfp;
596{
597 static struct pfile ent;
598 static char buf[120];
599 register char *p;
600 extern char *nextfield();
601
602 if (fgets(buf, sizeof buf, pfp) == NULL)
603 return (NULL);
604
605 ent.p_osid = p = buf;
606 ent.p_nsid = p = nextfield(p);
607 ent.p_user = p = nextfield(p);
608 ent.p_date = p = nextfield(p);
609 ent.p_time = p = nextfield(p);
610 if (p == NULL || nextfield(p) != NULL)
611 return (NULL);
612
613 return (&ent);
614}
615
616
617char *
618nextfield(p)
619 register char *p;
620{
621 if (p == NULL || *p == '\0')
622 return (NULL);
623 while (*p != ' ' && *p != '\n' && *p != '\0')
624 p++;
625 if (*p == '\n' || *p == '\0')
626 {
627 *p = '\0';
628 return (NULL);
629 }
630 *p++ = '\0';
631 return (p);
632}