1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License ("CDDL"), version 1.0.
6 * You may use this file only in accordance with the terms of version
7 * 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this
10 * source. A copy of the CDDL is also available via the Internet at
11 * http://www.opensource.org/licenses/cddl1.txt
12 * See the License for the specific language governing permissions
13 * and limitations under the License.
14 *
15 * When distributing Covered Code, include this CDDL HEADER in each
16 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17 * If applicable, add the following below this CDDL HEADER, with the
18 * fields enclosed by brackets "[]" replaced with your own identifying
19 * information: Portions Copyright [yyyy] [name of copyright owner]
20 *
21 * CDDL HEADER END
22 */
23 /* Copyright (c) 1988 AT&T */
24 /* All Rights Reserved */
25 /*
26 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
27 * Use is subject to license terms.
28 */
29 /*
30 * Copyright 2006-2020 J. Schilling
31 *
32 * @(#)admin.c 1.148 20/09/16 J. Schilling
33 */
34 #if defined(sun)
35 #pragma ident "@(#)admin.c 1.148 20/09/16 J. Schilling"
36 #endif
37 /*
38 * @(#)admin.c 1.39 06/12/12
39 */
40
41 #if defined(sun)
42 #pragma ident "@(#)admin.c"
43 #pragma ident "@(#)sccs:cmd/admin.c"
44 #endif
45
46 # define NEED_PRINTF_J /* Need defines for js_snprintf()? */
47 # define SCCS_MAIN /* define global vars */
48 # include <defines.h>
49 # include <version.h>
50 # include <had.h>
51 # include <i18n.h>
52 # include <schily/dirent.h>
53 # include <schily/setjmp.h>
54 # include <schily/utsname.h>
55 # include <schily/wait.h>
56 # include <schily/sysexits.h>
57 # include <schily/maxpath.h>
58 # define VMS_VFORK_OK
59 # include <schily/vfork.h>
60
61 /*
62 Program to create new SCCS files and change parameters
63 of existing ones. Arguments to the program may appear in
64 any order and consist of keyletters, which begin with '-',
65 and named files. Named files which do not exist are created
66 and their parameters are initialized according to the given
67 keyletter arguments, or are given default values if the
68 corresponding keyletters were not supplied. Named files which
69 do exist have those parameters corresponding to given key-letter
70 arguments changed and other parameters are left as is.
71
72 If a directory is given as an argument, each SCCS file within
73 the directory is processed as if it had been specifically named.
74 If a name of '-' is given, the standard input is read for a list
75 of names of SCCS files to be processed.
76 Non-SCCS files are ignored.
77
78 Files created are given mode 444.
79 */
80
81 /*
82 TRANSLATION_NOTE
83 For all message strings the double quotation marks at the beginning
84 and end of the string MUST be included in the translation. Formatting
85 characters, e.g. "%s" "%c" "%d" "\n" "\t" must appear in the
86 translated string exactly as they do in the msgid string. Spaces
87 and/or tabs around these formats must be maintained.
88
89 The following are examples of text that should not be translated, but
90 should appear exactly as they do in the msgid string:
91
92 - Any SCCS error code, which will be one or two letters followed by one
93 or two numbers all in parenthesis, e.g. "(ad3)",
94
95 - All descriptions of SCCS option flags, e.g. "-r" or " 'e'" , or "f",
96 or "-fz"
97
98 - ".FRED", "sid", "SID", "MRs", "CMR", "p-file", "CASSI", "cassi",
99 function names, e.g. "getcwd()",
100 */
101
102 # define MAXNAMES 9
103
104 static FILE *Cs; /* The changeset file */
105 static char stdin_file_buf [20]; /* For "/tmp/admin.XXXXXX" */
106 static char *ifile; /* -i argument */
107 static char *tfile; /* -t argument */
108 static char *dir_name; /* directory for -N */
109 static struct timespec ifile_mtime; /* Timestamp for -i -o */
110 static Nparms N; /* Keep -N parameters */
111 static Xparms X; /* Keep -X parameters */
112 static char *CMFAPPL; /* CMF MODS */
113 static char *z; /* for validation program name */
114 static char had_flag[NFLAGS]; /* -f seen list */
115 static char rm_flag[NFLAGS]; /* -d seen list */
116 #if defined(PROTOTYPES) && defined(INS_BASE)
117 static char Valpgmp[] = NOGETTEXT(INS_BASE "/" SCCS_BIN_PRE "bin/" "val");
118 #endif
119 static char Valpgm[] = NOGETTEXT("val");
120 static int fexists; /* Current file exists */
121 static int num_files; /* Number of file args */
122 static int VFLAG = 0; /* -v option seen */
123 static int versflag = -1; /* history vers for new files */
124 static struct sid new_sid; /* -r argument */
125 static char *anames[MAXNAMES]; /* -a arguments */
126 static char *enames[MAXNAMES]; /* -e arguments */
127 static char *unlockarg; /* -dl argument */
128 static char *locks; /* 'l' flag value in file */
129 static char *flag_p[NFLAGS]; /* -f arguments */
130 static int asub; /* Index for anames[] */
131 static int esub; /* Index for enames[] */
132 static int check_id; /* To check for Keywds with -i */
133 static struct utsname un; /* uname for lockit() */
134 static char *uuname; /* un.nodename */
135 static int Encoded = EF_TEXT; /* Default encoding is '0' */
136 static off_t Encodeflag_offset; /* offset in file where encoded flag is stored */
137 static off_t Flagdummy_offset; /* offset in file where dummy flag is stored */
138 static off_t Checksum_offset; /* offset in file where g-file hash is stored */
139
140 int main __PR((int argc, char **argv));
141 static void admin __PR((char *afile));
142 static int fgetchk __PR((FILE *inptr, char *file, struct packet *pkt, int fflag));
143 static void warnctl __PR((char *file, off_t nline));
144 static void warnnull __PR((char *file, off_t nline));
145 static void clean_up __PR((void));
146 static void cmt_ba __PR((register struct deltab *dt, char *str, int flags));
147 static void putmrs __PR((struct packet *pkt));
148 static char * adjust __PR((char *line));
149 static char * getval __PR((register char *sourcep, register char *destp));
150 static int val_list __PR((register char *list));
151 static int pos_ser __PR((char *s1, char *s2));
152 static int range __PR((register char *line));
153 static FILE * code __PR((FILE *iptr, char *afile, off_t offset, int thash, struct packet *pktp));
154 static void direrror __PR((char *dir, int keylet));
155
156 int
main(argc,argv)157 main(argc,argv)
158 int argc;
159 char *argv[];
160 {
161 register int j;
162 register char *p;
163 char f;
164 int i, testklt,c;
165 extern int Fcnt;
166 struct sid sid;
167 int no_arg=0;
168 int current_optind;
169
170 /*
171 * Set locale for all categories.
172 */
173 setlocale(LC_ALL, NOGETTEXT(""));
174
175 sccs_setinsbase(INS_BASE);
176
177 /*
178 * Set directory to search for general l10n SCCS messages.
179 */
180 #ifdef PROTOTYPES
181 (void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
182 NOGETTEXT(INS_BASE "/" SCCS_BIN_PRE "lib/locale/"));
183 #else
184 (void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
185 NOGETTEXT("/usr/ccs/lib/locale/"));
186 #endif
187
188 (void) textdomain(NOGETTEXT("SUNW_SPRO_SCCS"));
189
190 tzset(); /* Set up timezome related vars */
191
192 #ifdef SCHILY_BUILD
193 save_args(argc, argv);
194 #endif
195 /*
196 Set flags for 'fatal' to issue message, call clean-up
197 routine and terminate processing.
198 */
199 set_clean_up(clean_up);
200 Fflags = FTLMSG | FTLCLN | FTLEXIT;
201 #ifdef SCCS_FATALHELP
202 Fflags |= FTLFUNC;
203 Ffunc = sccsfatalhelp;
204 #endif
205
206 testklt = 1;
207
208 /*
209 The following loop processes keyletters and arguments.
210 Note that these are processed only once for each
211 invocation of 'main'.
212 */
213
214 current_optind = 1;
215 optind = 1;
216 opterr = 0;
217 j = 1;
218 /*CONSTCOND*/
219 while (1) {
220 if(current_optind < optind) {
221 current_optind = optind;
222 argv[j] = 0;
223 if (optind > j+1 ) {
224 if((argv[j+1][0] != '-') && !no_arg){
225 argv[j+1] = NULL;
226 }
227 else {
228 optind = j+1;
229 current_optind = optind;
230 }
231 }
232 }
233 no_arg = 0;
234 j = current_optind;
235 c = getopt(argc, argv, "()-i:t:m:y:d:f:r:nN:hzboqkw:a:e:X:V:(version)");
236 /*
237 * this takes care of options given after
238 * file names.
239 */
240 if (c == EOF) {
241 if (optind < argc) {
242 /* if it's due to -- then break; */
243 if(argv[j][0] == '-' &&
244 argv[j][1] == '-') {
245 argv[j] = 0;
246 break;
247 }
248 optind++;
249 current_optind = optind;
250 continue;
251 } else {
252 break;
253 }
254 }
255 p = optarg;
256 switch (c) {
257
258 case 'i': /* name of file of body */
259 if (optarg == argv[j+1]) {
260 no_arg = 1;
261 ifile = "";
262 break;
263 }
264 /*
265 * Use -i. to tell admin to retrieve the ifile
266 * name from the g-file name with -N
267 * We cannot check for "." here as -N may have
268 * been specified later on the cmd line.
269 */
270 ifile = p;
271 if (*ifile &&
272 !(ifile[0] == '.' && ifile[1] == '\0') &&
273 exists(ifile)) {
274 if ((Statbuf.st_mode & S_IFMT) == S_IFDIR) {
275 direrror(ifile, c);
276 } else {
277 ifile_mtime.tv_sec = Statbuf.st_mtime;
278 ifile_mtime.tv_nsec = stat_mnsecs(&Statbuf);
279 }
280 }
281 break;
282 case 't': /* name of file of descriptive text */
283 if (optarg == argv[j+1]) {
284 no_arg = 1;
285 tfile = NULL;
286 break;
287 }
288 tfile = p;
289 if (*tfile && exists(tfile))
290 if ((Statbuf.st_mode & S_IFMT) == S_IFDIR)
291 direrror(tfile, c);
292 break;
293 case 'm': /* mr flag */
294 /*
295 * Former sccs versions did allow to call
296 * admin -fv -m -i... to specify "no mr".
297 * With getopt, this only works with '-m '
298 * or -m ''.
299 */
300 if (*p == '-')
301 fatal(gettext("bad m argument (ad34)"));
302 Mrs = p;
303 repl(Mrs,'\n',' ');
304 break;
305 case 'y': /* comments flag for entry */
306 if (optarg == argv[j+1]) {
307 no_arg = 1;
308 Comments = "";
309 } else {
310 Comments = p;
311 }
312 break;
313 case 'd': /* flags to be deleted */
314 testklt = 0;
315 if ((f = *p) == '\0')
316 fatal(gettext("d has no argument (ad1)"));
317 p = p+1;
318
319 switch (f) {
320
321 case IDFLAG: /* see 'f' keyletter */
322 case BRCHFLAG: /* for meanings of flags */
323 case VALFLAG:
324 case TYPEFLAG:
325 case MODFLAG:
326 case QSECTFLAG:
327 case NULLFLAG:
328 case FLORFLAG:
329 case CEILFLAG:
330 case DEFTFLAG:
331 case JOINTFLAG:
332 case SCANFLAG:
333 case EXTENSFLAG:
334 case EXPANDFLAG:
335 case CMFFLAG: /* option installed by CMF */
336 if (*p) {
337 sprintf(SccsError,
338 gettext("value after %c flag (ad12)"),
339 f);
340 fatal(SccsError);
341 }
342 break;
343 case LOCKFLAG:
344 if (*p == '\0') {
345 fatal(gettext("bad list format (ad27)"));
346 }
347 if (*p) {
348 /*
349 set pointer to releases
350 to be unlocked
351 */
352 repl(p,',',' ');
353 if (!val_list(p))
354 fatal(gettext("bad list format (ad27)"));
355 if (!range(p))
356 fatal(gettext("element in list out of range (ad28)"));
357 if (*p != 'a')
358 unlockarg = p;
359 }
360 break;
361
362 default:
363 fatal(gettext("unknown flag (ad3)"));
364 }
365
366 if (rm_flag[f - 'a']++)
367 fatal(gettext("flag twice (ad4)"));
368 break;
369
370 case 'f': /* flags to be added */
371 testklt = 0;
372 if ((f = *p) == '\0')
373 fatal(gettext("f has no argument (ad5)"));
374 p = p+1;
375
376 switch (f) {
377
378 case BRCHFLAG: /* branch */
379 case NULLFLAG: /* null deltas */
380 case JOINTFLAG: /* joint edit flag */
381 if (*p) {
382 sprintf(SccsError,
383 gettext("value after %c flag (ad13)"),
384 f);
385 fatal(SccsError);
386 }
387 break;
388
389 case IDFLAG: /* id-kwd message (err/warn) */
390 break;
391
392 case VALFLAG: /* mr validation */
393 VFLAG++;
394 if (*p)
395 z = p;
396 break;
397
398 case CMFFLAG: /* CMFNET MODS */
399 if (*p) /* CMFNET MODS */
400 CMFAPPL = p; /* CMFNET MODS */
401 else /* CMFNET MODS */
402 fatal (gettext("No application with application flag."));
403 if (gf (CMFAPPL) == (char*) NULL)
404 fatal (gettext("No .FRED file exists for this application."));
405 break; /* END CMFNET MODS */
406
407 case FLORFLAG: /* floor */
408 if ((i = patoi(p)) == -1)
409 fatal(gettext("floor not numeric (ad22)"));
410 if (((int) size(p) > 5) || (i < MINR) ||
411 (i > MAXR))
412 fatal(gettext("floor out of range (ad23)"));
413 break;
414
415 case CEILFLAG: /* ceiling */
416 if ((i = patoi(p)) == -1)
417 fatal(gettext("ceiling not numeric (ad24)"));
418 if (((int) size(p) > 5) || (i < MINR) ||
419 (i > MAXR))
420 fatal(gettext("ceiling out of range (ad25)"));
421 break;
422
423 case DEFTFLAG: /* default sid */
424 if (!(*p))
425 fatal(gettext("no default sid (ad14)"));
426 chksid(sid_ab(p,&sid),&sid);
427 break;
428
429 case TYPEFLAG: /* type */
430 case MODFLAG: /* module name */
431 case QSECTFLAG: /* csect name */
432 if (!(*p)) {
433 sprintf(SccsError,
434 gettext("flag %c has no value (ad2)"),
435 f);
436 fatal(SccsError);
437 }
438 break;
439 case LOCKFLAG: /* release lock */
440 if (!(*p))
441 /*
442 lock all releases
443 */
444 p = NOGETTEXT("a");
445 /*
446 replace all commas with
447 blanks in SCCS file
448 */
449 repl(p,',',' ');
450 if (!val_list(p))
451 fatal(gettext("bad list format (ad27)"));
452 if (!range(p))
453 fatal(gettext("element in list out of range (ad28)"));
454 break;
455 case SCANFLAG: /* the number of lines that are scanned to expand of SCCS KeyWords. */
456 if ((i = patoi(p)) == -1)
457 fatal(gettext("line not numeric (ad33)"));
458 break;
459 case EXTENSFLAG: /* exable SCCS extensions */
460 p = "SCHILY"; /* we use SCHILY type ext */
461 break;
462 case EXPANDFLAG: /* expand the SCCS KeyWords */
463 /* replace all commas with blanks in SCCS file */
464 repl(p,',',' ');
465 break;
466
467 default:
468 fatal(gettext("unknown flag (ad3)"));
469 }
470
471 if (had_flag[f - 'a']++)
472 fatal(gettext("flag twice (ad4)"));
473 flag_p[f - 'a'] = p;
474 break;
475
476 case 'r': /* initial release number supplied */
477 chksid(sid_ab(p,&new_sid),&new_sid);
478 if ((new_sid.s_rel < MINR) ||
479 (new_sid.s_rel > MAXR))
480 fatal(gettext("r out of range (ad7)"));
481 break;
482
483 case 'N': /* creating new SCCS files */
484 initN(&N);
485 if (optarg == argv[j+1]) {
486 no_arg = 1;
487 break;
488 }
489 N.n_parm = p;
490 break;
491 case 'n': /* creating new SCCS file */
492 case 'h': /* only check hash of file */
493 case 'k': /* get(1) without keyword expand */
494 case 'z': /* zero the input hash */
495 case 'b': /* force file to be encoded (binary) */
496 case 'o': /* use original file date */
497 break;
498
499 case 'q': /* activate NSE features */
500 if(p) {
501 if (*p) {
502 nsedelim = p;
503 }
504 } else {
505 nsedelim = (char *) 0;
506 }
507 break;
508
509 case 'w':
510 if (p)
511 whatsetup(p);
512 break;
513
514 case 'a': /* user-name allowed to make deltas */
515 testklt = 0;
516 if (!(*p) || *p == '-')
517 fatal(gettext("bad a argument (ad8)"));
518 if (asub > MAXNAMES) {
519 fatal(gettext("too many 'a' keyletters (ad9)"));
520 /* NOTREACHED */
521 }
522 anames[asub++] = sccs_user(p);
523 break;
524
525 case 'e': /* user-name to be removed */
526 testklt = 0;
527 if (!(*p) || *p == '-')
528 fatal(gettext("bad e argument (ad10)"));
529 if (esub > MAXNAMES) {
530 fatal(gettext("too many 'e' keyletters (ad11)"));
531 /* NOTREACHED */
532 }
533 enames[esub++] = sccs_user(p);
534 break;
535
536 case 'X':
537 X.x_parm = optarg;
538 X.x_flags = XO_INIT_PATH|XO_URAND|\
539 XO_UNLINK|XO_MAIL|XO_USER|XO_DATE|\
540 XO_NULLPATH|XO_NOBULK|XO_G_PATH;
541 if (!parseX(&X))
542 goto err;
543 had[NLOWER+c-'A'] = 0; /* Allow mult -X */
544 break;
545
546 case 'V': /* version */
547 if (optarg == argv[j+1]) {
548 doversion:
549 printf(gettext(
550 "admin %s-SCCS version %s %s (%s-%s-%s)\n"),
551 PROVIDER,
552 VERSION,
553 VDATE,
554 HOST_CPU, HOST_VENDOR, HOST_OS);
555 exit(EX_OK);
556 }
557 if (p[1] == '\0') {
558 if (p[0] == '4') {
559 versflag = 4;
560 break;
561 } else if (p[0] == '6') {
562 versflag = 6;
563 break;
564 }
565 }
566
567 default:
568 err:
569 /*
570 * Check whether "-V" was last arg...
571 */
572 if (optind == argc &&
573 ((argv[argc-1][0] == '-' &&
574 argv[argc-1][1] == 'V' &&
575 argv[argc-1][2] == '\0') ||
576 strcmp(argv[argc-1], "-version") == 0 ||
577 strcmp(argv[argc-1], "--version") == 0))
578 goto doversion;
579 fatal(gettext("Usage: admin [ -bhknoz ][ -ausername|groupid ]\n\t[ -dflag ][ -eusername|groupid ]\n\t[ -fflag [value]][ -i [filename]]\n\t[ -m mr-list][ -r release ][ -t [description-file]]\n\t[ -N[bulk-spec]][ -Xxopts ] [ -y[comment]] s.filename ..."));
580 }
581 /*
582 * Make sure that we only collect option letters from
583 * the range 'a'..'z' and 'A'..'Z'.
584 */
585 if (ALPHA(c) &&
586 (had[LOWER(c)? c-'a' : NLOWER+c-'A']++ && testklt++)) {
587 if (c != 'X')
588 fatal(gettext("key letter twice (cm2)"));
589 }
590 }
591
592 for (i = 1; i < argc; i++){
593 if (argv[i]) {
594 num_files++;
595 }
596 }
597
598 if (num_files == 0 && !HADUCN)
599 fatal(gettext("missing file arg (cm3)"));
600 if (num_files > 1 && (X.x_opts & (XO_INIT_PATH|XO_URAND)))
601 fatal(gettext("too many file args (cm18)"));
602
603 setsig();
604 xsethome(NULL);
605 if (HADUCN) { /* Parse -N args */
606 /*
607 * initN() was already called while parsing options.
608 */
609 if (HADI)
610 N.n_flags |= N_IFILE;
611 if (HADI && ifile[0] == '.' && ifile[1] == '\0')
612 N.n_flags |= N_IDOT;
613 if (HADN)
614 N.n_flags |= N_NFILE;
615 parseN(&N);
616 if (N.n_get)
617 N.n_flags |= N_GETI;
618
619 if (N.n_sdot && (sethomestat & SETHOME_OFFTREE))
620 fatal(gettext("-Ns. not supported in off-tree project mode"));
621
622 /*
623 * If someone previously successfully called "sccs init" and
624 * "admin" is called with the option -N, we default to -V6.
625 */
626 if (versflag < 0 && SETHOME_INIT()) {
627 versflag = 6;
628 }
629
630 } else if (HADI && ifile[0] == '.' && ifile[1] == '\0') {
631 direrror(ifile, 'i');
632 }
633
634 #ifdef SCCS_V6_ENV
635 if (versflag < 0) { /* Not explicitly selected, check ENV */
636 if (getenv("SCCS_V6"))
637 versflag = 6;
638 }
639 #endif
640 if (versflag < 0)
641 versflag = 4; /* This is the historic default */
642
643 if ((HADY || HADM) && ! (HADI || HADN))
644 fatal(gettext("illegal use of 'y' or 'm' keyletter (ad30)"));
645 if (HADI && !HADUCN && num_files > 1) /* only one file allowed with `i' */
646 fatal(gettext("more than one file (ad15)"));
647 if ((HADI || HADN) && ! logname())
648 fatal(gettext("USER ID not in password file (cm9)"));
649
650 /*
651 * Get the name of our machine to be used for the lockfile.
652 */
653 uname(&un);
654 uuname = un.nodename;
655
656 /*
657 * Set up a project global lock on the changeset file.
658 * Since we set FTLJMP, we do not need to unlockchset() from clean_up().
659 */
660 if (!HADH && SETHOME_CHSET()) {
661 lockchset(getppid(), getpid(), uuname);
662 if (HADUCN && exists(changesetgfile)) {
663 /*
664 * Should we open/create the file even if it does not
665 * yet exist? This may change with the final concept.
666 */
667 Cs = xfopen(changesetgfile, O_WRONLY|O_APPEND|O_BINARY);
668 }
669 }
670 timerchsetlock();
671
672 /*
673 Change flags for 'fatal' so that it will return to this
674 routine (main) instead of terminating processing.
675 */
676 Fflags &= ~FTLEXIT;
677 Fflags |= FTLJMP;
678
679 /*
680 Call 'admin' routine for each file argument.
681 */
682 for (j=1; j<argc; j++)
683 if ((p = argv[j]) != NULL)
684 do_file(p, admin, HADN|HADI ? 0 : 1, N.n_sdot, &X);
685
686 if (num_files == 0 && HADUCN)
687 do_file("-", admin, 0, N.n_sdot, &X);
688
689 /*
690 * Only remove the global lock it it was created by us and not by
691 * our parent.
692 */
693 if (!HADH && SETHOME_CHSET()) {
694 if (HADUCN) {
695 bulkchdir(&N);
696 if (Cs)
697 fclose(Cs);
698 }
699 unlockchset(getpid(), uuname);
700 }
701
702 return (Fcnt ? 1 : 0);
703 }
704
705 /*
706 Routine that actually does admin's work on SCCS files.
707 Existing s-files are copied, with changes being made, to a
708 temporary file (x-file). The name of the x-file is the same as the
709 name of the s-file, with the 's.' replaced by 'x.'.
710 s-files which are to be created are processed in a similar
711 manner, except that a dummy s-file is first created with
712 mode 444.
713 At end of processing, the x-file is renamed with the name of s-file
714 and the old s-file is removed.
715 */
716
717 static struct packet gpkt; /* see file defines.h */
718 static char Zhold[MAXPATHLEN]; /* temporary z-file name */
719
720 static void
admin(afile)721 admin(afile)
722 char *afile;
723 {
724 struct deltab dt; /* see file defines.h */
725 struct stats stats; /* see file defines.h */
726 struct stat sbuf;
727 FILE *iptr;
728 register int k;
729 register char *cp;
730 register signed char *q;
731 char *in_f; /* ptr holder for lockflag vals in SCCS file */
732 char nline[MAXLINE];
733 char *p_lval, *tval;
734 char *lval;
735 char f; /* character holder for flag character */
736 char line[MAXLINE];
737 int ck_it; /* used for lockflag checking */
738 off_t offset;
739 int thash;
740 int status; /* used for status return from fork */
741 int from_stdin; /* used for ifile */
742 extern char had_dir;
743
744 if (setjmp(Fjmp)) /* set up to return here from 'fatal' */
745 return; /* and return to caller of admin */
746
747 /*
748 * Initialize here to avoid setjmp() clobbering warnings
749 */
750 iptr = NULL;
751 lval = NULL;
752 ck_it = 0;
753 from_stdin = 0;
754 dir_name = "";
755 Encoded = EF_TEXT; /* Default encoding is '0' */
756
757 zero((char *) &stats,sizeof(stats));
758
759 /*
760 * In order to make the global lock with a potentially long duration
761 * not look as if it was expired, we refresh it for every file in our
762 * task list. This is needed since another SCCS instance on a different
763 * NFS machine cannot use kill() to check for a still active process.
764 */
765 if (!HADH && SETHOME_CHSET()) {
766 if (HADUCN)
767 bulkchdir(&N); /* Done by bulkprepare() anyway */
768 refreshchsetlock();
769 }
770
771 if (HADUCN && !(X.x_opts & XO_NOBULK)) {
772 char *oafile = afile;
773
774 afile = bulkprepare(&N, afile);
775 if (N.n_error != 0 && N.n_error != BULK_EISDIR) {
776 fatal(gettext(bulkerror(&N)));
777 } else if (afile == NULL) {
778 if (N.n_ifile)
779 direrror(N.n_ifile, 'i');
780 else
781 direrror(oafile, 'i');
782 }
783 if (N.n_mtime.tv_sec != 0) {
784 ifile_mtime.tv_sec = N.n_mtime.tv_sec;
785 ifile_mtime.tv_nsec = N.n_mtime.tv_nsec;
786 }
787 had_dir = 0;
788 dir_name = N.n_dir_name;
789 if (dir_name == NULL)
790 dir_name = "";
791 if (N.n_flags & N_IDOT)
792 ifile = N.n_ifile;
793 }
794 if (X.x_opts & XO_G_PATH)
795 ifile = X.x_gpath;
796
797 if (HADI && had_dir) /* directory not allowed with `i' keyletter */
798 fatal(gettext("directory named with `i' keyletter (ad26)"));
799
800 fexists = exists(afile);
801
802 if (HADI)
803 HADN = 1;
804 if (HADI || HADN) {
805 if (VFLAG && had_flag[CMFFLAG - 'a'])
806 fatal(gettext("Can't have two verification routines."));
807
808 if (HADM && !VFLAG && !had_flag[CMFFLAG - 'a'])
809 fatal(gettext("MRs not allowed (de8)"));
810
811 if (VFLAG && !HADM)
812 fatal(gettext("MRs required (de10)"));
813
814 }
815
816 if (!(HADI||HADN) && HADR)
817 fatal(gettext("r only allowed with i or n (ad16)"));
818
819 if (HADN && HADT && !tfile)
820 fatal(gettext("t has no argument (ad17)"));
821
822 if (HADN && HADD)
823 fatal(gettext("d not allowed with n (ad18)"));
824
825 if (HADN && fexists) {
826 sprintf(SccsError, gettext("file %s exists (ad19)"),
827 afile);
828 fatal(SccsError);
829 }
830
831 if (!HADN && !fexists) {
832 sprintf(SccsError, gettext("file %s does not exist (ad20)"),
833 afile);
834 fatal(SccsError);
835 }
836 if (HADH) {
837 pid_t pid;
838
839 /*
840 fork here so 'admin' can execute 'val' to
841 check for a corrupted file.
842 */
843 if ((pid = vfork()) < 0)
844 efatal(gettext("cannot fork, try again"));
845 if (pid == 0) { /* child */
846 /*
847 perform 'val' with appropriate keyletters
848 */
849 #if defined(PROTOTYPES) && defined(INS_BASE)
850 execlp(Valpgmp, Valpgm, "-s", afile, (char *)0);
851 #endif
852 execlp(Valpgm, Valpgm, "-s", afile, (char *)0);
853 sprintf(SccsError, gettext("cannot execute '%s'"),
854 Valpgm);
855 #ifdef HAVE_VFORK
856 Fflags |= FTLVFORK;
857 #endif
858 efatal(SccsError);
859 }
860 else {
861 wait(&status); /* wait on status from 'execlp' */
862 if (status)
863 fatal(gettext("corrupted file (co6)"));
864 return; /* return to caller of 'admin' */
865 }
866 }
867
868 /*
869 * Lock out any other user who may be trying to process
870 * the same file.
871 */
872 if (!HADH && !islockchset(copy(auxf(afile, 'z'), Zhold))) {
873 if (lockit(Zhold, SCCS_LOCK_ATTEMPTS, getpid(), uuname)) {
874 lockfatal(Zhold, getpid(), uuname);
875 }
876 timersetlockfile(Zhold);
877 }
878
879 if (fexists) { /* modifying */
880 int cklen = 8;
881
882 sinit(&gpkt, afile, SI_OPEN); /* init pkt & open s-file */
883
884 if (gpkt.p_flags & PF_V6)
885 cklen = 15;
886
887 /* Modify checksum if corrupted */
888
889 if ((int) strlen(gpkt.p_line) > cklen &&
890 gpkt.p_line[0] == '\001' &&
891 gpkt.p_line[1] == '\150') {
892 gpkt.p_line[cklen-1] = '\012';
893 gpkt.p_line[cklen] = '\000';
894 }
895 }
896 else {
897 if ((int) strlen(sname(afile)) > MAXNAMLEN) {
898 sprintf(SccsError, gettext("file name is greater than %d characters"),
899 MAXNAMLEN);
900 fatal(SccsError);
901 }
902 if (sccsfile(afile)) {
903 FILE *xf;
904
905 /*
906 * create dummy s-file
907 *
908 * Closing is needed on Cygwin to avoid an EPERM in
909 * rename()
910 */
911 Statbuf.st_mode = 0;
912 if (HADI && *ifile)
913 (void) exists(ifile);
914 else
915 (void )exists(auxf(afile,'g'));
916 if (S_IEXEC & Statbuf.st_mode) {
917 xf = xfcreat(afile, 0555);
918 } else {
919 xf = xfcreat(afile, 0444);
920 }
921 if (xf)
922 fclose(xf);
923 }
924
925 sinit(&gpkt, afile, SI_INIT); /* and init pkt */
926
927 /*
928 * Initialize global meta data
929 */
930 if (versflag == 6) {
931 if (X.x_opts & XO_INIT_PATH) {
932 gpkt.p_init_path = X.x_init_path;
933 } else if (HADUCN && !(X.x_opts & XO_NOBULK)) {
934 /*
935 * Only if we have been called with -N..., we
936 * know the real g-file name. We cannot derive
937 * the g-file name from the s.file name
938 * otherwise, since we cannot know about sub
939 * dirs like "SCCS" in the other cases.
940 */
941 set_init_path(&gpkt, N.n_ifile, dir_name);
942 }
943
944 if (X.x_opts & XO_URAND)
945 gpkt.p_rand = X.x_rand;
946 else
947 urandom(&gpkt.p_rand);
948 }
949 }
950
951 if (!HADH)
952 /*
953 set the flag for 'putline' routine to open
954 the 'x-file' and allow writing on it.
955 */
956 gpkt.p_upd = 1;
957
958 if (HADZ) {
959 gpkt.do_chksum = 0; /* ignore checksum processing */
960 gpkt.p_ihash = 0;
961 }
962
963 /*
964 Get statistics of latest delta in old file.
965 */
966 if (!HADN) {
967 stats_ab(&gpkt,&stats);
968 gpkt.p_wrttn++;
969 newstats(&gpkt,line,"0");
970 }
971
972 if (HADN) { /* N E W F I L E */
973
974 if (versflag == 6)
975 gpkt.p_flags |= PF_V6;
976
977 /*
978 Beginning of SCCS file.
979 */
980 putmagic(&gpkt, "00000");
981
982 /*
983 Statistics.
984 */
985 newstats(&gpkt,line,"0");
986
987 dt.d_type = 'D'; /* type of delta */
988 if (X.x_opts & XO_UNLINK)
989 dt.d_type = 'U';
990
991 /*
992 Set initial release, level, branch and
993 sequence values.
994 */
995 if (HADR)
996 {
997 dt.d_sid.s_rel = new_sid.s_rel;
998 dt.d_sid.s_lev = new_sid.s_lev;
999 dt.d_sid.s_br = new_sid.s_br ;
1000 dt.d_sid.s_seq = new_sid.s_seq;
1001 if (dt.d_sid.s_lev == 0) dt.d_sid.s_lev = 1;
1002 if ((dt.d_sid.s_br) && ( ! dt.d_sid.s_seq))
1003 dt.d_sid.s_seq = 1;
1004 }
1005 else
1006 {
1007 dt.d_sid.s_rel = 1;
1008 dt.d_sid.s_lev = 1;
1009 if (X.x_opts & XO_UNLINK)
1010 dt.d_sid.s_lev = 0;
1011 dt.d_sid.s_br = dt.d_sid.s_seq = 0;
1012 }
1013 dtime(&dt.d_dtime); /* get time and date */
1014 if (HADN && HADI && (HADO || HADQ) &&
1015 (ifile_mtime.tv_sec != 0)) {
1016 /*
1017 * When specifying -o (original date) and
1018 * for NSE when putting existing file under sccs the
1019 * delta time is the mtime of the clear file.
1020 */
1021 time2dt(&dt.d_dtime,
1022 ifile_mtime.tv_sec, ifile_mtime.tv_nsec);
1023 }
1024 if (HADN && (X.x_opts & XO_DATE)) {
1025 dt.d_dtime = X.x_dtime;
1026 }
1027
1028 strlcpy(dt.d_pgmr, logname(), LOGSIZE); /* get user's name */
1029 if (X.x_user) /* from -Xuser= */
1030 strlcpy(dt.d_pgmr, X.x_user, LOGSIZE);
1031
1032 dt.d_serial = 1;
1033 dt.d_pred = 0;
1034
1035 gpkt.p_reqsid = dt.d_sid; /* set sid for changelog */
1036
1037 del_ba(&dt,line, gpkt.p_flags); /* form and write */
1038 putline(&gpkt,line); /* delta-table entry */
1039
1040 /*
1041 If -m flag, enter MR numbers
1042 */
1043
1044 if (Mrs) {
1045 if (had_flag[CMFFLAG - 'a']) { /* CMF check routine */
1046 if (cmrcheck (Mrs, CMFAPPL) != 0) { /* check them */
1047 fatal (gettext("Bad CMR number(s)."));
1048 }
1049 }
1050 mrfixup();
1051 if (z && valmrs(&gpkt,z))
1052 fatal(gettext("invalid MRs (de9)"));
1053 putmrs(&gpkt);
1054 }
1055 if (gpkt.p_flags & PF_V6) {
1056 Checksum_offset = ftell(gpkt.p_xiop);
1057 gpkt.p_mail = X.x_mail;
1058 sidext_ba(&gpkt, &dt); /* Will not write "dt" entry. */
1059 gpkt.p_mail = NULL;
1060 }
1061
1062 /*
1063 Enter comment line for `chghist'
1064 */
1065
1066 if (HADY) {
1067 char *comment = savecmt(Comments);
1068 sprintf(line,"%c%c ",CTLCHAR,COMMENTS);
1069 putline(&gpkt,line);
1070 putline(&gpkt,comment);
1071 putline(&gpkt,"\n");
1072 }
1073 else {
1074 /*
1075 insert date/time and pgmr into comment line
1076 */
1077 cmt_ba(&dt, line, gpkt.p_flags);
1078 putline(&gpkt,line);
1079 }
1080 /*
1081 End of delta-table.
1082 */
1083 sprintf(line,CTLSTR,CTLCHAR,EDELTAB);
1084 putline(&gpkt,line);
1085
1086 /*
1087 Beginning of user-name section.
1088 */
1089 sprintf(line,CTLSTR,CTLCHAR,BUSERNAM);
1090 putline(&gpkt,line);
1091 }
1092 else
1093 /*
1094 For old file, copy to x-file until user-name section
1095 is found.
1096 */
1097 flushto(&gpkt, BUSERNAM, FLUSH_COPY);
1098
1099 /*
1100 Write user-names to be added to list of those
1101 allowed to make deltas.
1102 */
1103 if (HADA)
1104 for (k = 0; k < asub; k++) {
1105 sprintf(line,"%s\n",anames[k]);
1106 putline(&gpkt,line);
1107 }
1108
1109 /*
1110 Do not copy those user-names which are to be erased.
1111 */
1112 if (HADE && !HADN)
1113 while (((cp = getline(&gpkt)) != NULL) &&
1114 !(*cp++ == CTLCHAR && *cp == EUSERNAM)) {
1115 for (k = 0; k < esub; k++) {
1116 cp = gpkt.p_line;
1117 while (*cp) /* find and */
1118 cp++; /* zero newline */
1119 *--cp = '\0'; /* character */
1120
1121 if (equal(enames[k],gpkt.p_line)) {
1122 /*
1123 Tell getline not to output
1124 previously read line.
1125 */
1126 gpkt.p_wrttn = 1;
1127 break;
1128 }
1129 else
1130 *cp = '\n'; /* restore newline */
1131 }
1132 }
1133
1134 if (HADN) { /* N E W F I L E */
1135
1136 /*
1137 End of user-name section.
1138 */
1139 sprintf(line,CTLSTR,CTLCHAR,EUSERNAM);
1140 putline(&gpkt,line);
1141 } else {
1142 /*
1143 For old file, copy to x-file until end of
1144 user-names section is found.
1145 */
1146 if (!HADE)
1147 flushto(&gpkt, EUSERNAM, FLUSH_COPY);
1148 }
1149
1150 /*
1151 For old file, read flags and their values (if any), and
1152 store them. Check to see if the flag read is one that
1153 should be deleted.
1154 */
1155 if (!HADN) {
1156 while (((cp = getline(&gpkt)) != NULL) &&
1157 (*cp++ == CTLCHAR && *cp++ == FLAG)) {
1158
1159 gpkt.p_wrttn = 1; /* don't write previous line */
1160
1161 NONBLANK(cp); /* point to flag character */
1162 k = *cp - 'a';
1163 if (k < 0 || k >= NFLAGS) {
1164 fprintf(stderr,
1165 gettext(
1166 "WARNING [%s]: unsupported flag at line %d\n"),
1167 gpkt.p_file,
1168 gpkt.p_slnno);
1169 /*
1170 * Better to abort then to silently remove flags
1171 * as previous versions did.
1172 */
1173 fatal("unsupported flag (ad35)");
1174 continue;
1175 }
1176 f = *cp++;
1177 NONBLANK(cp);
1178 if (f == LOCKFLAG) {
1179 p_lval = cp;
1180 tval = fmalloc(size(gpkt.p_line)- (unsigned)5);
1181 copy(++p_lval,tval);
1182 lval = tval;
1183 while(*tval)
1184 ++tval;
1185 *--tval = '\0';
1186 }
1187
1188 if (!had_flag[k] && !rm_flag[k]) {
1189 had_flag[k] = 2; /* indicate flag is */
1190 /* from file, not */
1191 /* from arg list */
1192
1193 if (*cp != '\n') { /* get flag value */
1194 q = (signed char *) fmalloc(size(gpkt.p_line) - (unsigned)5);
1195 copy(cp, (char *)q);
1196 flag_p[k] = (char*) q;
1197 while (*q) /* find and */
1198 q++; /* zero newline */
1199 *--q = '\0'; /* character */
1200 if (k == ENCODEFLAG - 'a') {
1201 int i;
1202
1203 NONBLANK(cp);
1204 cp = satoi(cp, &i);
1205 if (*cp == '\n')
1206 gpkt.p_encoding = i;
1207 }
1208 }
1209 }
1210 if (rm_flag[k]) {
1211 if (f == LOCKFLAG) {
1212 if (unlockarg) {
1213 in_f = lval;
1214 if (((lval = adjust(in_f)) != NULL) &&
1215 !had_flag[k])
1216 ck_it = had_flag[k] = 1;
1217 }
1218 else had_flag[k] = 0;
1219 }
1220 else had_flag[k] = 0;
1221 }
1222 }
1223 }
1224
1225 /*
1226 Write out flags.
1227 */
1228 /* test to see if the CMFFLAG is safe */
1229 if (had_flag[CMFFLAG - 'a']) {
1230 if (had_flag[VALFLAG - 'a'] && !rm_flag[VALFLAG - 'a'])
1231 fatal (gettext("Can't use -fz with -fv."));
1232 }
1233 for (k = 0; k < NFLAGS; k++) {
1234 if (had_flag[k]) {
1235 int i; /* for flag string cleanup */
1236
1237 if (flag_p[k] || lval ) {
1238 if (('a' + k) == LOCKFLAG && had_flag[k] == 1) {
1239 if ((flag_p[k] && *flag_p[k] == 'a') || (lval && *lval == 'a'))
1240 locks = NOGETTEXT("a");
1241 else if (lval && flag_p[k]) {
1242 nline[0] = '\0';
1243 i = strlcatl(nline, sizeof (nline),
1244 lval, " ", flag_p[k], (char *)0);
1245 if (i >= sizeof (nline))
1246 fatal(gettext("line too long (co31)"));
1247 locks = nline;
1248 } else if (lval) {
1249 locks = lval;
1250 } else {
1251 locks = flag_p[k];
1252 }
1253 sprintf(line,"%c%c %c %s\n",
1254 CTLCHAR,FLAG,'a' + k,locks);
1255 locks = 0;
1256 if (lval) {
1257 ffree(lval);
1258 tval = lval = 0;
1259 }
1260 if (ck_it)
1261 had_flag[k] = ck_it = 0;
1262 }
1263 else if (flag_p[k])
1264 sprintf(line,"%c%c %c %s\n",
1265 CTLCHAR,FLAG,'a'+k,flag_p[k]);
1266 else
1267 sprintf(line,"%c%c %c\n",
1268 CTLCHAR,FLAG,'a'+k);
1269 }
1270 else
1271 sprintf(line,"%c%c %c\n",
1272 CTLCHAR,FLAG,'a'+k);
1273
1274 /* flush imbeded newlines from flag value */
1275 i = 4;
1276 if (line[i] == ' ')
1277 for (i++; line[i+1]; i++)
1278 if (line[i] == '\n')
1279 line[i] = ' ';
1280 putline(&gpkt,line);
1281
1282 if (had_flag[k] == 2) { /* flag was taken from file */
1283 had_flag[k] = 0;
1284 if (flag_p[k]) {
1285 ffree(flag_p[k]);
1286 flag_p[k] = 0;
1287 }
1288 }
1289 }
1290 }
1291
1292 if (HADN) {
1293 if (HADI || HADB) {
1294 /*
1295 If the "encoded" flag was not present, put it in
1296 with a value of 0; this acts as a place holder,
1297 so that if we later discover that the file contains
1298 non-ASCII characters we can flag it as encoded
1299 by setting the value to 1.
1300 */
1301 Encodeflag_offset = ftell(gpkt.p_xiop);
1302 sprintf(line,"%c%c %c %d\n",
1303 CTLCHAR, FLAG, ENCODEFLAG, Encoded);
1304 putline(&gpkt,line);
1305
1306 if (HADI && !HADB && (gpkt.p_flags & PF_V6) &&
1307 (had_flag[EXPANDFLAG - 'a'] == 0)) {
1308 Flagdummy_offset = ftell(gpkt.p_xiop);
1309 sprintf(line,"%c%c \n",
1310 CTLCHAR, NAMEDFLAG);
1311 putline(&gpkt,line);
1312 }
1313 }
1314 /*
1315 * Writing out SCCS v6 flags belongs here.
1316 */
1317
1318 /*
1319 * Since we are creating a new history file, everything is from
1320 * us and every meta data needs to be written.
1321 */
1322 putmeta(&gpkt, M_ALL);
1323
1324 /*
1325 Beginning of descriptive (user) text.
1326 */
1327 sprintf(line,CTLSTR,CTLCHAR,BUSERTXT);
1328 putline(&gpkt,line);
1329 } else {
1330 /*
1331 * Check where the above loop that processes flags stopped:
1332 */
1333 gpkt.p_wrttn = 0;
1334 /*
1335 * Copy over SCCS v6 flags.
1336 */
1337 while (gpkt.p_line_length > 1 &&
1338 gpkt.p_line[0] == CTLCHAR &&
1339 gpkt.p_line[1] == NAMEDFLAG) {
1340 if (gpkt.p_line[1] == NAMEDFLAG) {
1341 q = (signed char *)&gpkt.p_line[2];
1342 NONBLANK(q);
1343 if (*q == '\n') {
1344 /*
1345 * Skip dummy flag, it is only needed
1346 * as a placeholder by "admin -i...".
1347 */
1348 gpkt.p_wrttn = 1;
1349 getline(&gpkt);
1350 continue;
1351 }
1352 }
1353 getline(&gpkt);
1354
1355 }
1356
1357 /*
1358 * Copy over SCCS v6 global metadata.
1359 */
1360 while (gpkt.p_line_length > 1 &&
1361 gpkt.p_line[0] == CTLCHAR &&
1362 gpkt.p_line[1] == GLOBALEXTENS) {
1363 getline(&gpkt);
1364 }
1365
1366 /*
1367 * Write out everything until the BUSERTXT record.
1368 * This includes possible future extensions.
1369 */
1370 if (gpkt.p_line[0] == CTLCHAR && gpkt.p_line[1] == BUSERTXT)
1371 putline(&gpkt,(char *) 0);
1372 else
1373 flushto(&gpkt, BUSERTXT, FLUSH_COPY);
1374 }
1375
1376 /*
1377 Get user description, copy to x-file.
1378 */
1379 if (HADT) {
1380 if (tfile) {
1381 if (*tfile) {
1382 iptr = xfopen(tfile, O_RDONLY|O_BINARY);
1383 #ifdef USE_SETVBUF
1384 setvbuf(iptr, NULL, _IOFBF, VBUF_SIZE);
1385 #endif
1386 (void)fgetchk(iptr, tfile, &gpkt, 0);
1387 fclose(iptr);
1388 iptr = NULL;
1389 /*
1390 * fgetchk() did set p_ghash and in case that the
1391 * file has zero size or -n is used, p_ghash may never
1392 * be set up again. So we need to clear it here.
1393 */
1394 gpkt.p_ghash = 0;
1395 }
1396 }
1397
1398 /*
1399 If old file, ignore any previously supplied
1400 commentary. (i.e., don't copy it to x-file.)
1401 */
1402 if (!HADN)
1403 flushto(&gpkt, EUSERTXT, FLUSH_NOCOPY);
1404 }
1405
1406 if (HADN) { /* N E W F I L E */
1407
1408 /*
1409 End of user description.
1410 */
1411 sprintf(line,CTLSTR,CTLCHAR,EUSERTXT);
1412 putline(&gpkt,line);
1413
1414 /*
1415 Beginning of body (text) of first delta.
1416 */
1417 sprintf(line,"%c%c %d\n",CTLCHAR,INS,1);
1418 putline(&gpkt,line);
1419
1420 if (HADB)
1421 Encoded |= EF_UUENCODE;
1422 if (HADI) { /* get body */
1423
1424 /*
1425 Set indicator to check lines of body of file for
1426 keyword definitions.
1427 If no keywords are found, a warning
1428 will be produced.
1429 */
1430 check_id = 1;
1431 /*
1432 Set indicator that tells whether there
1433 were any keywords to 'no'.
1434 */
1435 gpkt.p_did_id = 0;
1436 if (ifile) {
1437 if (*ifile) {
1438 /* from a file */
1439 from_stdin = 0;
1440 } else {
1441 /* from standard input */
1442 int err = 0, cnt;
1443 char buf[MAXLINE];
1444 FILE * out;
1445 mode_t cur_umask;
1446
1447 from_stdin = 1;
1448 ifile = stdin_file_buf;
1449 strlcpy(stdin_file_buf, "/tmp/admin.XXXXXX", sizeof (stdin_file_buf));
1450 cur_umask = umask((mode_t)((S_IRWXU|S_IRWXG|S_IRWXO)&~(S_IRUSR|S_IWUSR)));
1451 #ifdef HAVE_MKSTEMP
1452 if ((out = fdopen(mkstemp(ifile), "wb")) == NULL) {
1453 xmsg(ifile, NOGETTEXT("admin"));
1454 }
1455 #else
1456 mktemp(stdin_file_buf);
1457 if ((out = fopen(ifile, "wb")) == NULL) {
1458 xmsg(ifile, NOGETTEXT("admin"));
1459 }
1460 #endif
1461 setmode(fileno(out), O_BINARY);
1462 (void)umask(cur_umask);
1463 /*CONSTCOND*/
1464 while (1) {
1465 if ((cnt = fread(buf, 1, sizeof (buf), stdin))) {
1466 if (fwrite(buf, 1, cnt, out) == cnt) {
1467 continue;
1468 }
1469 err = 1;
1470 break;
1471 } else {
1472 if (!feof(stdin)) {
1473 err = 1;
1474 }
1475 break;
1476 }
1477 }
1478 if (err) {
1479 unlink(ifile);
1480 xmsg(ifile, NOGETTEXT("admin"));
1481 }
1482 fclose(out);
1483 }
1484 iptr = xfopen(ifile, O_RDONLY|O_BINARY);
1485 #ifdef USE_SETVBUF
1486 setvbuf(iptr, NULL, _IOFBF, VBUF_SIZE);
1487 #endif
1488 }
1489
1490 /* save an offset to x-file in case need to encode
1491 file. Then won't have to start all over. Also
1492 save the hash value up to this point.
1493 */
1494 offset = ftell(gpkt.p_xiop);
1495 thash = gpkt.p_nhash;
1496
1497 /*
1498 If we haven't already been told that the file
1499 should be encoded, read and copy to x-file,
1500 while checking for control characters (octal 1),
1501 and also check if file ends in newline. If
1502 control char or no newline, the file needs to
1503 be encoded.
1504 Also, count lines read, and set statistics'
1505 structure appropriately.
1506 The 'fgetchk' routine will check for keywords.
1507 */
1508 if (!HADB) {
1509 stats.s_ins = fgetchk(iptr, ifile, &gpkt, 1);
1510 if (stats.s_ins == -1 ) {
1511 Encoded |= EF_UUENCODE;
1512 } else {
1513 Encoded &= ~EF_UUENCODE; /* Keep EF_GZIP */
1514 }
1515 } else {
1516 Encoded |= EF_UUENCODE;
1517 }
1518 if (Encoded & EF_UUENCODE) {
1519 /* non-ascii characters in file, encode them */
1520 iptr = code(iptr, afile, offset, thash, &gpkt);
1521 stats.s_ins = fgetchk(iptr, ifile, &gpkt, 0);
1522 }
1523 if (iptr) {
1524 fclose(iptr);
1525 iptr = NULL;
1526 }
1527 stats.s_del = stats.s_unc = 0;
1528
1529 /*
1530 * If no keywords were found, issue warning unless in
1531 * NSE mode...or warnings have been disabled via an
1532 * empty 'y' flag value.
1533 */
1534 if (!gpkt.p_did_id && !HADQ &&
1535 (!Flagdummy_offset || !(gpkt.p_props & CK_NULL)) &&
1536 (!flag_p[EXPANDFLAG - 'a'] ||
1537 *(flag_p[EXPANDFLAG - 'a']))) {
1538 if (had_flag[IDFLAG - 'a']) {
1539 if(!(flag_p[IDFLAG -'a']))
1540 fatal(gettext("no id keywords (cm6)"));
1541 else
1542 fatal(gettext("invalid id keywords (cm10)"));
1543 } else {
1544 fprintf(stderr, gettext("No id keywords (cm7)\n"));
1545 (void) sccsfatalhelp("(cm7)");
1546 }
1547 }
1548
1549 check_id = 0;
1550 gpkt.p_did_id = 0;
1551 }
1552
1553 /*
1554 End of body of first delta.
1555 */
1556 sprintf(line,"%c%c %d\n",CTLCHAR,END,1);
1557 putline(&gpkt,line);
1558 if (gpkt.p_flags & PF_V6 && gpkt.p_ghash != 0) {
1559 fseek(gpkt.p_xiop, Checksum_offset, SEEK_SET);
1560 fprintf(gpkt.p_xiop, "%c%c s %5.5d\n",
1561 CTLCHAR, SIDEXTENS, gpkt.p_ghash);
1562 gpkt.p_nhash -= 5 * '0';
1563 sprintf(line, "%5.5d", gpkt.p_ghash);
1564 q = (signed char *) line;
1565 while (*q)
1566 gpkt.p_nhash += *q++;
1567 }
1568 } else {
1569 /*
1570 Indicate that EOF at this point is ok, and
1571 flush rest of (old) s-file to x-file.
1572 */
1573 gpkt.p_chkeof = 1;
1574 while (getline(&gpkt)) ;
1575 }
1576
1577 /* If encoded file, put change "fe" flag and recalculate
1578 the hash value
1579 */
1580
1581 if (Encoded & EF_UUENCODE)
1582 {
1583 strcpy(line,"0");
1584 q = (signed char *) line;
1585 while (*q)
1586 gpkt.p_nhash -= *q++;
1587 strcpy(line,"1");
1588 q = (signed char *) line;
1589 while (*q)
1590 gpkt.p_nhash += *q++;
1591 fseek(gpkt.p_xiop, Encodeflag_offset, SEEK_SET);
1592 fprintf(gpkt.p_xiop,"%c%c %c %d\n",
1593 CTLCHAR, FLAG, ENCODEFLAG, Encoded);
1594 } else if (Flagdummy_offset && (gpkt.p_props & CK_NULL)) {
1595 strcpy(line,"F ");
1596 q = (signed char *) line;
1597 while (*q)
1598 gpkt.p_nhash -= *q++;
1599 strcpy(line,"f y ");
1600 q = (signed char *) line;
1601 while (*q)
1602 gpkt.p_nhash += *q++;
1603 fseek(gpkt.p_xiop, Flagdummy_offset, SEEK_SET);
1604 fprintf(gpkt.p_xiop,"%c%c %c \n",
1605 CTLCHAR, FLAG, EXPANDFLAG);
1606 }
1607
1608 /*
1609 Flush the buffer, take care of rewinding to insert
1610 checksum and statistics in file, and close.
1611 */
1612 flushline(&gpkt,&stats);
1613
1614 /*
1615 Change x-file name to s-file, and delete old file.
1616 Unlock file before returning.
1617 */
1618 if (!HADH) {
1619 if (!HADN) stat(gpkt.p_file,&sbuf);
1620 rename(auxf(gpkt.p_file,'x'), gpkt.p_file);
1621 if (!HADN) {
1622 chmod(gpkt.p_file, sbuf.st_mode);
1623 chown(gpkt.p_file,sbuf.st_uid, sbuf.st_gid);
1624 }
1625 if (HADO) {
1626 struct timespec ts[2];
1627 extern dtime_t Timenow;
1628
1629 ts[0].tv_sec = Timenow.dt_sec;
1630 ts[0].tv_nsec = Timenow.dt_nsec;
1631 ts[1].tv_sec = ifile_mtime.tv_sec;
1632 ts[1].tv_nsec = ifile_mtime.tv_nsec;
1633
1634 /*
1635 * As SunPro make and gmake call sccs get when the time
1636 * if s.file equals the time stamp of the g-file, make
1637 * sure the s.file is a bit older.
1638 */
1639 if (!(gpkt.p_flags & PF_V6)) {
1640 struct timespec tn;
1641
1642 getnstimeofday(&tn);
1643 ts[1].tv_nsec = tn.tv_nsec;
1644 }
1645 if (ts[1].tv_nsec > 500000000)
1646 ts[1].tv_nsec -= 500000000;
1647
1648 utimensat(AT_FDCWD, gpkt.p_file, ts, 0);
1649 }
1650 if (HADI && *ifile) {
1651 if (N.n_comma) {
1652 char cfile[FILESIZE];
1653 register char *snp;
1654
1655 snp = sname(ifile);
1656
1657 strlcpy(cfile, ifile, sizeof (cfile));
1658 cfile[snp-ifile] = '\0';
1659 strlcat(cfile, ",", sizeof (cfile));
1660 if (strlcat(cfile, snp, sizeof (cfile)) <
1661 sizeof (cfile)) {
1662 rename(ifile, cfile);
1663 }
1664 } else if(N.n_unlink) {
1665 unlink(ifile);
1666 } else if (N.n_get) {
1667 unlink(ifile);
1668 doget(gpkt.p_file, ifile, 1);
1669 if (HADO)
1670 dogtime(&gpkt, ifile, &ifile_mtime);
1671 }
1672 }
1673 xrm(&gpkt);
1674 timersetlockfile(NULL);
1675 if (!islockchset(Zhold))
1676 unlockit(Zhold, getpid(), uuname);
1677
1678 if ((gpkt.p_flags & PF_V6) && Cs && gpkt.p_init_path) {
1679 char cbuf[2*MAXPATHLEN];
1680
1681 change_ba(&gpkt, cbuf, sizeof (cbuf));
1682 fprintf(Cs, "%s\n", cbuf);
1683 }
1684 }
1685
1686 if (HADI)
1687 unlink(auxf(gpkt.p_file,'e'));
1688 if (from_stdin) {
1689 unlink(stdin_file_buf);
1690 stdin_file_buf[0] = '\0';
1691 }
1692 if (gpkt.p_init_path) {
1693 if (gpkt.p_init_path != X.x_init_path)
1694 ffree(gpkt.p_init_path);
1695 gpkt.p_init_path = NULL;
1696 }
1697 sclose(&gpkt);
1698 sfree(&gpkt);
1699 ffreeall();
1700 }
1701
1702 static int
fgetchk(inptr,file,pkt,fflag)1703 fgetchk(inptr, file, pkt, fflag)
1704 FILE *inptr; /* File pointer to read from */
1705 char *file; /* File name to read from */
1706 struct packet *pkt; /* struct paket for output */
1707 int fflag; /* 0 = abort, 1 == flag */
1708 {
1709 off_t nline;
1710 int idx = 0;
1711 int warned = 0;
1712 int nwarned = 0;
1713 char chkflags = 0;
1714 char lastchar;
1715 #ifndef RECORD_IO
1716 char *p = NULL; /* Intialize to make gcc quiet */
1717 char *pn = NULL; /* Pointer to nul character */
1718 char line[VBUF_SIZE+1];
1719 char *lastline = line; /* Init to make GCC quiet */
1720 #else
1721 int search_on = 0;
1722 int llen;
1723 char line[256+1]; /* Avoid a too long buffer for speed */
1724 #endif
1725 off_t ibase = 0; /* Ifile off from last read operation */
1726 off_t ioff = 0; /* Ifile offset past last newline */
1727 off_t soff = ftell(pkt->p_xiop); /* Ofile (s. file) base offset */
1728 off_t coff = 0; /* Offset from additional ^A escapes */
1729 unsigned int sum = 0;
1730 int pktv6 = pkt->p_flags & PF_V6;
1731
1732 /*
1733 * This gives the illusion that a zero-length file ends
1734 * in a newline so that it won't be mistaken for a
1735 * binary file.
1736 */
1737 lastchar = '\n';
1738
1739 nline = 0;
1740 (void)memset(line, '\377', sizeof (line));
1741 #ifndef RECORD_IO
1742 /*
1743 * In most cases (non record oriented I/O), we can optimize the way we
1744 * scan files for '\0' bytes, line-ends '\n' and ^A '\1'. The optimized
1745 * algorithm allows to avoid to do a reverse scan for '\0' from the end
1746 * of the buffer.
1747 */
1748 while ((idx = fread(line, 1, sizeof (line) - 1, inptr)) > 0) {
1749 sum += usum(line, idx);
1750 lastline = line;
1751 if (lastchar == '\n' && line[0] == CTLCHAR) {
1752 chkflags |= CK_CTLCHAR;
1753 if (fflag && pktv6) {
1754 if (!warned) {
1755 warnctl(file, nline+1);
1756 warned = 1;
1757 }
1758 putctl(pkt);
1759 coff++;
1760 } else {
1761 goto err;
1762 }
1763 }
1764 lastchar = line[idx-1];
1765 p = findbytes(line, idx, '\0');
1766 if (p != NULL) {
1767 chkflags |= CK_NULL;
1768 pn = p;
1769 }
1770 for (p = line;
1771 (p = findbytes(p, idx - (p-line), '\n')) != NULL; p++) {
1772 ioff = ibase + (p - line) + 1;
1773 if (pn && p > pn) { /* '\0' before '\n' */
1774 if (pktv6) {
1775 if (!nwarned) {
1776 warnnull(file, nline+1);
1777 nwarned = 1;
1778 }
1779 } else {
1780 goto err;
1781 }
1782 }
1783 nline++;
1784 if ((p - line) >= (idx-1)) /* '\n' last in buf */
1785 break;
1786
1787 if (p[1] == CTLCHAR) {
1788 chkflags |= CK_CTLCHAR;
1789 if (fflag && pktv6 &&
1790 (p[1] == CTLCHAR)) {
1791 if (!warned) {
1792 warnctl(file, nline+1);
1793 warned = 1;
1794 }
1795 p[1] = '\0';
1796 putlline(pkt, lastline, &p[1] - lastline);
1797 p[1] = CTLCHAR;
1798 lastline = &p[1];
1799 putctl(pkt);
1800 coff++;
1801 continue;
1802 }
1803 err:
1804 if (fflag) {
1805 return(-1);
1806 } else {
1807 sprintf(SccsError,
1808 gettext(
1809 "file '%s' contains illegal data on line %jd (ad21)"),
1810 file, (Intmax_t)++nline);
1811 fatal(SccsError);
1812 }
1813 }
1814 }
1815 line[idx] = '\0';
1816 putlline(pkt, lastline, &line[idx] - lastline);
1817
1818 if (check_id && pkt->p_did_id == 0) {
1819 pkt->p_did_id =
1820 chkid(line, flag_p[IDFLAG - 'a'], flag_p);
1821 }
1822 ibase += idx;
1823 }
1824 #else /* !RECORD_IO */
1825 /*
1826 * We support nul bytes with fgets() by pre-filling the buffer.
1827 */
1828 while (fgets(line, sizeof (line), inptr) != NULL) {
1829 if (lastchar == '\n' && line[0] == CTLCHAR) {
1830 chkflags |= CK_CTLCHAR;
1831 if (fflag && pktv6) {
1832 if (!warned) {
1833 warnctl(file, nline+1);
1834 warned = 1;
1835 }
1836 putctl(pkt);
1837 coff++;
1838 } else {
1839 nline++;
1840 goto err;
1841 }
1842 }
1843 search_on = 0;
1844 for (idx = sizeof (line)-1; idx >= 0; idx--) {
1845 if (search_on > 0) {
1846 if (line[idx] == '\0') {
1847 chkflags |= CK_NULL;
1848 err:
1849 if (fflag) {
1850 if (pktv6) {
1851 if ((chkflags & CK_NULL) && !nwarned) {
1852 warnnull(file, nline);
1853 nwarned = 1;
1854 }
1855 continue;
1856 }
1857 return(-1);
1858 } else {
1859 sprintf(SccsError,
1860 gettext("file '%s' contains illegal data on line %jd (ad21)"),
1861 file, (Intmax_t)nline);
1862 fatal(SccsError);
1863 }
1864 }
1865 } else {
1866 if (line[idx] == '\0') {
1867 sum += usum(line, idx);
1868 search_on = 1;
1869 lastchar = line[idx-1];
1870 if (lastchar == '\n') {
1871 nline++;
1872 ioff = ibase + idx;
1873
1874 }
1875 ibase += idx;
1876 llen = idx;
1877 }
1878 }
1879 }
1880 if (check_id && pkt->p_did_id == 0) {
1881 pkt->p_did_id =
1882 chkid(line, flag_p[IDFLAG - 'a'], flag_p);
1883 }
1884 putlline(pkt, line, llen);
1885 (void)memset(line, '\377', sizeof (line));
1886 }
1887 #endif /* !RECORD_IO */
1888 pkt->p_ghash = sum & 0xFFFF;
1889 if (lastchar != '\n')
1890 chkflags |= CK_NONL;
1891 pkt->p_props |= chkflags;
1892
1893 if (chkflags & CK_NONL) {
1894 #ifndef RECORD_IO
1895 if (!pktv6)
1896 if (pn && nline == 0) /* Found null byte but no newline past null */
1897 goto err;
1898 #endif
1899 if (fflag && pktv6) {
1900 int first = 1;
1901
1902 fseek(inptr, ioff, SEEK_SET);
1903 fseek(pkt->p_xiop, ioff + soff + coff, SEEK_SET);
1904
1905 /*
1906 * Write again already written text without recomputing
1907 * the checksum for this part of the text.
1908 */
1909 while ((idx =
1910 fread(line, 1, sizeof (line) - 1, inptr)) > 0) {
1911 if (first) {
1912 first = 0;
1913 if (line[0] == CTLCHAR) {
1914 /*
1915 * ^A escape is already present
1916 */
1917 putchr(pkt, NONL);
1918 } else {
1919 putctlnnl(pkt);
1920 }
1921 }
1922 if (fwrite(line, 1, idx, pkt->p_xiop) <= 0)
1923 FAILPUT;
1924 }
1925 putchr(pkt, '\n');
1926 fprintf(stderr, gettext(
1927 "WARNING [%s%s%s]: No newline at end of file (ad31)\n"),
1928 dir_name,
1929 *dir_name?"/":"",
1930 file);
1931 return (nline);
1932 }
1933
1934 if (fflag) {
1935 return(-1);
1936 } else {
1937 sprintf(SccsError,
1938 gettext("No newline at end of file '%s' (ad31)"),
1939 file);
1940 fatal(SccsError);
1941 }
1942 }
1943 return(nline);
1944 }
1945
1946 static void
warnctl(file,nline)1947 warnctl(file, nline)
1948 char *file;
1949 off_t nline;
1950 {
1951 fprintf(stderr,
1952 gettext(
1953 "WARNING [%s%s%s]: line %jd begins with ^A\n"),
1954 dir_name,
1955 *dir_name?"/":"",
1956 file, (Intmax_t)nline);
1957 }
1958
1959 static void
warnnull(file,nline)1960 warnnull(file, nline)
1961 char *file;
1962 off_t nline;
1963 {
1964 fprintf(stderr,
1965 gettext(
1966 "WARNING [%s]: line %jd contains a '\\000'\n"),
1967 file, (Intmax_t)nline);
1968 }
1969
1970
1971 static void
clean_up()1972 clean_up()
1973 {
1974 xrm(&gpkt);
1975 if(gpkt.p_file[0]) {
1976 unlink(auxf(gpkt.p_file,'x'));
1977 if (HADI)
1978 unlink(auxf(gpkt.p_file,'e'));
1979 if (HADN)
1980 unlink(gpkt.p_file);
1981 }
1982 if (gpkt.p_init_path) {
1983 if (gpkt.p_init_path != X.x_init_path)
1984 ffree(gpkt.p_init_path);
1985 gpkt.p_init_path = NULL;
1986 }
1987 if (!HADH) {
1988 uname(&un);
1989 uuname = un.nodename;
1990 timersetlockfile(NULL);
1991 if (!islockchset(Zhold))
1992 unlockit(Zhold, getpid(), uuname);
1993 }
1994 sclose(&gpkt);
1995 sfree(&gpkt);
1996 ffreeall();
1997 }
1998
1999 static void
cmt_ba(dt,str,flags)2000 cmt_ba(dt,str, flags)
2001 register struct deltab *dt;
2002 char *str;
2003 int flags;
2004 {
2005 register char *p;
2006
2007 p = str;
2008 *p++ = CTLCHAR;
2009 *p++ = COMMENTS;
2010 *p++ = ' ';
2011 copy(NOGETTEXT("date and time created"),p);
2012 while (*p++)
2013 ;
2014 --p;
2015 *p++ = ' ';
2016 /*
2017 * The s-file is not part of the POSIX standard. For this reason, we
2018 * are free to switch to a 4-digit year for the initial comment.
2019 */
2020 if ((flags & PF_V6) ||
2021 (dt->d_dtime.dt_sec < Y1969) ||
2022 (dt->d_dtime.dt_sec >= Y2038)) /* comment only */
2023 date_bazl(&dt->d_dtime,p, flags); /* 4 digit year */
2024 else
2025 date_ba(&dt->d_dtime.dt_sec,p, flags); /* 2 digit year */
2026 while (*p++)
2027 ;
2028 --p;
2029 *p++ = ' ';
2030 copy(NOGETTEXT("by"),p);
2031 while (*p++)
2032 ;
2033 --p;
2034 *p++ = ' ';
2035 copy(dt->d_pgmr,p);
2036 while (*p++)
2037 ;
2038 --p;
2039 *p++ = '\n';
2040 *p = 0;
2041 }
2042
2043 static void
putmrs(pkt)2044 putmrs(pkt)
2045 struct packet *pkt;
2046 {
2047 register char **argv;
2048 char str[64];
2049 extern char **Varg;
2050
2051 for (argv = &Varg[VSTART]; *argv; argv++) {
2052 sprintf(str,"%c%c %s\n",CTLCHAR,MRNUM,*argv);
2053 putline(pkt,str);
2054 }
2055 }
2056
2057
2058 static char*
adjust(line)2059 adjust(line)
2060 char *line;
2061 {
2062 register int k;
2063 register int i;
2064 char *t_unlock;
2065 char t_line[MAXLINE];
2066 char rel[5];
2067
2068 t_unlock = unlockarg;
2069 while(*t_unlock) {
2070 NONBLANK(t_unlock);
2071 t_unlock = getval(t_unlock,rel);
2072 while ((k = pos_ser(line,rel)) != -1) {
2073 for(i = k; i < ((int) size(rel) + k); i++) {
2074 line[i] = '+';
2075 if (line[i++] == ' ')
2076 line[i] = '+';
2077 else if (line[i] == '\0')
2078 break;
2079 else --i;
2080 }
2081 k = 0;
2082 for(i = 0; i < (int) length(line); i++) {
2083 if (line[i] == '+')
2084 continue;
2085 else if (k == 0 && line[i] == ' ')
2086 continue;
2087 else t_line[k++] = line[i];
2088 }
2089 t_line[k] = '\0';
2090 if (k > 0 &&
2091 t_line[(int) strlen(t_line) - 1] == ' ')
2092 t_line[(int) strlen(t_line) - 1] = '\0';
2093 line = t_line;
2094 }
2095 }
2096 if (length(line) == 0)
2097 return (0);
2098 if (line == t_line) {
2099 t_unlock = fmalloc(size(line));
2100 copy(line, t_unlock);
2101 return (t_unlock);
2102 }
2103 return (line);
2104 }
2105
2106 static char*
getval(sourcep,destp)2107 getval(sourcep,destp)
2108 register char *sourcep;
2109 register char *destp;
2110 {
2111 while (*sourcep != ' ' && *sourcep != '\t' && *sourcep != '\0')
2112 *destp++ = *sourcep++;
2113 *destp = 0;
2114 return(sourcep);
2115 }
2116
2117 static int
val_list(list)2118 val_list(list)
2119 register char *list;
2120 {
2121 register int i;
2122
2123 if (list[0] == 'a')
2124 return(1);
2125 else for(i = 0; list[i] != '\0'; i++)
2126 if (list[i] == ' ' || numeric(list[i]))
2127 continue;
2128 else if (list[i] == 'a') {
2129 list[0] = 'a';
2130 list[1] = '\0';
2131 return(1);
2132 }
2133 else return(0);
2134 return(1);
2135 }
2136
2137 static int
pos_ser(s1,s2)2138 pos_ser(s1,s2)
2139 char *s1;
2140 char *s2;
2141 {
2142 register int offset;
2143 register char *p;
2144 char num[5];
2145
2146 p = s1;
2147 offset = 0;
2148
2149 while(*p) {
2150 NONBLANK(p);
2151 p = getval(p,num);
2152 if (equal(num,s2)) {
2153 return(offset);
2154 }
2155 offset = offset + (int) size(num);
2156 }
2157 return(-1);
2158 }
2159
2160 static int
range(line)2161 range(line)
2162 register char *line;
2163 {
2164 register char *p;
2165 char rel[MAXLINE];
2166
2167 p = line;
2168 while(*p) {
2169 NONBLANK(p);
2170 p = getval(p,rel);
2171 if ((int) size(rel) > 5)
2172 return(0);
2173 }
2174 return(1);
2175 }
2176
2177 static FILE *
code(iptr,afile,offset,thash,pktp)2178 code(iptr,afile,offset,thash,pktp)
2179 FILE *iptr;
2180 char *afile;
2181 off_t offset;
2182 int thash;
2183 struct packet *pktp;
2184 {
2185 FILE *eptr;
2186
2187
2188 /* issue a warning that file is non-text */
2189 if (!HADB) {
2190 fprintf(stderr, gettext("WARNING [%s%s%s]: %s"),
2191 dir_name,
2192 *dir_name?"/":"",
2193 ifile,
2194 gettext("Not a text file (ad32)\n"));
2195 (void) sccsfatalhelp("(ad32)");
2196 }
2197 rewind(iptr);
2198 eptr = fopen(auxf(afile,'e'), "wb");
2199
2200 encode(iptr,eptr);
2201 fclose(eptr);
2202 fclose(iptr);
2203 iptr = fopen(auxf(afile,'e'), "rb");
2204 /* close the stream to xfile and reopen it at offset. Offset is
2205 * the end of sccs header info and before gfile contents
2206 */
2207 putline(pktp,0);
2208 fseek(pktp->p_xiop, offset, SEEK_SET);
2209 pktp->p_nhash = thash;
2210
2211 return (iptr);
2212 }
2213
2214 static void
direrror(dir,keylet)2215 direrror(dir, keylet)
2216 char *dir;
2217 int keylet;
2218 {
2219 sprintf(SccsError,
2220 gettext("directory `%s' specified as `%c' keyletter value (ad29)"),
2221 dir, (char)keylet);
2222 fatal(SccsError);
2223 }
2224