1 /*
2 * sccsfile.cc: Part of GNU CSSC.
3 *
4 *
5 * Copyright (C) 1997, 1998, 1999, 2001, 2003, 2004, 2007, 2008, 2009,
6 * 2010, 2011, 2014, 2019 Free Software Foundation, Inc.
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 * CSSC was originally Based on MySC, by Ross Ridge, which was
22 * placed in the Public Domain.
23 *
24 *
25 * Common members of the class sccs_file and its subclasses. Most of
26 * the members in this file are used to read from the SCCS file.
27 *
28 */
29 #include <config.h>
30 #include <errno.h>
31 #include <string>
32
33 #include "cssc.h"
34 #include "sccsfile.h"
35 #include "delta-table.h"
36 #include "delta-iterator.h"
37 #include "linebuf.h"
38 #include "quit.h"
39 #include "ioerr.h"
40 #include "file.h"
41
42 #include <ctype.h>
43 #include <unistd.h> // SEEK_SET on SunOS.
44 #include <sys/stat.h> /* fstat(), struct stat */
45
46
47 #if defined HAVE_FILENO && defined HAVE_FSTAT
48 /* If an SCCS file has a link count greater than one, then the normal
49 * process of updating the file will break the link. We try to detect this
50 * even if the file is being opened to reading only, to give an early
51 * warning (and because SCCS does so).
52 */
just_one_link(FILE * f)53 static int just_one_link(FILE *f)
54 {
55 int fd = fileno(f);
56 if (fd >= 0)
57 {
58 struct stat st;
59 if (0 != fstat(fd, &st))
60 {
61 /* We cannot stat the file descriptor. Perhaps there is a
62 * file system functionality issue. If that's the case then we
63 * will give it the benefit of the doubt on the link coutn front.
64 */
65 return 1; /* We're happy with the file */
66 }
67 if (st.st_nlink > 1)
68 return 0; /* We don't like it. */
69 }
70 return 1; /* OK. */
71 }
72 #else
just_one_link(FILE * f)73 static int just_one_link(FILE *f)
74 {
75 /* Without fileno(), we have no way of checking. */
76 return 1;
77 }
78 #endif
79
80
81 /* Static member for opening a SCCS file and then calculating its checksum. */
82 FILE *
open_sccs_file(const char * name,enum _mode mode,int * sump,bool * isBKFile)83 sccs_file::open_sccs_file(const char *name,
84 enum _mode mode,
85 int *sump,
86 bool* isBKFile)
87 {
88 FILE *f_local;
89
90 #ifdef CONFIG_OPEN_SCCS_FILES_IN_BINARY_MODE
91 f_local = fopen(name, "rb");
92 #else
93 if (mode == UPDATE)
94 f_local = fopen(name, "r+");
95 else
96 f_local = fopen(name, "r");
97 #endif
98
99 if (f_local == NULL)
100 {
101 const char *purpose = (mode == UPDATE) ? "update" : "reading";
102 s_missing_quit("Cannot open SCCS file %s for %s", name, purpose);
103 /*NOTEACHED*/
104 return NULL;
105 }
106
107 if (!just_one_link(f_local))
108 {
109 // xx: issue error message here
110 errormsg("%s had a hard link count which is greater than one.\n"
111 "This means that the normal process of updating the file\n"
112 "would break the hard link. This error is therefore fatal,\n"
113 "please fix the problem.\n",
114 name);
115 (void)fclose(f_local);
116 return NULL;
117 }
118
119 bool badMagic = false;
120 if (getc(f_local) != '\001')
121 {
122 badMagic = true;
123 }
124 else
125 {
126 char magicMarker = getc(f_local);
127 if (magicMarker == 'H')
128 {
129 if (READ == mode)
130 {
131 /* We support read-only access to BK files. */
132 warning("%s is a BitKeeper file.", name);
133 }
134 else
135 {
136 errormsg("%s: This is a BitKeeper file, and only read-only "
137 "access to BitKeeper files is supported at the moment, "
138 "sorry.\n",
139 name);
140 (void)fclose(f_local);
141 return NULL;
142 }
143 // Inform the caller that this is a BK file.
144 // NB: this is the parameter, not member variable
145 *isBKFile = true;
146 }
147 else if (magicMarker != 'h')
148 {
149 badMagic = true;
150 }
151 }
152
153 if (badMagic)
154 {
155 (void)fclose(f_local);
156 s_corrupt_quit("%s: No SCCS-file magic number. "
157 "Did you specify the right file?", name);
158 /*NOTEACHED*/
159 return NULL;
160 }
161
162
163 int c;
164 errno = 0;
165 while ( (c=getc(f_local)) != CONFIG_EOL_CHARACTER)
166 {
167 if (EOF == c)
168 {
169 const int saved_errno = errno;
170 (void)fclose(f_local);
171 errno = saved_errno;
172 if (errno)
173 {
174 perror(name);
175 }
176 else
177 {
178 s_corrupt_quit("%s: Unexpected EOF.", name);
179 /*NOTEACHED*/
180 }
181 return NULL;
182 }
183 }
184
185 int sum = 0u;
186
187 while ((c=getc(f_local)) != EOF)
188 sum += (char) c; // Yes, I mean plain char, not signed, not unsigned.
189
190 if (ferror(f_local))
191 {
192 perror(name);
193 (void)fclose(f_local);
194 return NULL;
195 }
196
197
198 *sump = sum & 0xFFFFu;
199
200 #ifdef CONFIG_OPEN_SCCS_FILES_IN_BINARY_MODE
201 fclose(f_local);
202 if (mode == UPDATE)
203 f_local = fopen(name, "r+");
204 else
205 f_local = fopen(name, "r");
206
207 if (NULL == f_local)
208 {
209 perror(name);
210 return NULL;
211 }
212
213 #else
214 rewind(f_local);
215 if (ferror(f_local))
216 {
217 perror(name);
218 (void)fclose(f_local);
219 return NULL;
220 }
221 #endif
222 return f_local;
223 }
224
225
226
227 /*
228 * Reads a line from the SCCS file.
229 * Result:
230 * true if we read a line. false for EOF or failure.
231 * Output params:
232 * control_char: 0 if this is not a control (^A) line, otherwise the line type.
233 */
234 bool
read_line(char * line_type)235 sccs_file::read_line(char* line_type)
236 {
237 if (read_line_param(f))
238 {
239 if (ferror(f))
240 {
241 errormsg_with_errno("%s: Read error.", name.c_str());
242 }
243 return false;
244 }
245
246 lineno++;
247 if ( bufchar(0) == '\001')
248 *line_type = bufchar(1);
249 else
250 *line_type = char(0);
251 return true;
252 }
253
254
255 /* Quits with a message saying that SCCS file is corrupt. */
256
257 NORETURN
corrupt(const char * fmt,...) const258 sccs_file::corrupt(const char *fmt, ...) const {
259 char buf[80];
260 const char *p;
261
262 va_list ap;
263 va_start(ap, fmt);
264 if (-1 == vsnprintf(buf, sizeof(buf), fmt, ap))
265 {
266 warning("%s: error message too long for buffer, so the "
267 "next message will lack some relevant detail",
268 name.c_str());
269 p = fmt; // best effort
270 }
271 else
272 {
273 p = buf;
274 }
275 s_corrupt_quit("%s: line %d: Corrupted SCCS file. (%s)",
276 name.c_str(), lineno, p);
277 }
278
279
280 /* Checks that a control line has at least one argument. */
281
282 void
check_arg() const283 sccs_file::check_arg() const {
284 if (bufchar(2) != ' ') {
285 corrupt("Missing arg");
286 }
287 }
288
289
290 /* Checks the a control line has no arguments. */
291
292 void
check_noarg() const293 sccs_file::check_noarg() const {
294 if (bufchar(2) != '\0') {
295 corrupt("Unexpected arg");
296 }
297 }
298
299
300 /* Converts an ASCII string to an unsigned short, quiting if the
301 string isn't a valid number. */
302
303 unsigned short
strict_atous(const char * s) const304 sccs_file::strict_atous(const char *s) const
305 {
306 long n = 0;
307
308 char c;
309 while ( 0 != (c=*s++) )
310 {
311 if (!isdigit((unsigned char)c))
312 {
313 corrupt("Invalid number");
314 }
315 n = n * 10 + (c - '0');
316 if (n > 65535L)
317 {
318 corrupt("Number too big");
319 }
320 }
321
322 return (unsigned short) n;
323 }
324
325 // Convert a number field in an SCCS file to a
326 // number. Fields representing numbers in
327 // SCCS files should top out at 9999.
328
329 unsigned long
strict_atoul_idu(const char * s) const330 sccs_file::strict_atoul_idu(const char *s) const
331 {
332 unsigned long n = 0;
333 bool found_ws = false;
334 const unsigned long limit = 99999uL;
335
336 /* Unix System III pads to the left with spaces in the
337 * numbers, while more modern versions of SCCS pad to
338 * the left with zeroes. We don't allow left-pad with
339 * whitespace characters other than an actual space.
340 */
341 while (' ' == *s)
342 {
343 ++s;
344 found_ws = true;
345 }
346
347 if (found_ws)
348 {
349 warning("%s contains spaces in the line counts in its delta table.",
350 name.c_str());
351 if ((UPDATE == mode) || (FIX_CHECKSUM == mode))
352 {
353 warning("These leading spaces will be converted to leading zeroes.");
354 }
355 }
356
357 if ('-' == *s)
358 {
359 corrupt ("Line counts should be positive");
360 }
361 else
362 {
363 char *end;
364 n = strtoul (s, &end, 10);
365 if (*end && (*end) != ' ')
366 {
367 corrupt ("Unexpected suffix %s on line number count", end);
368 }
369 }
370
371 if (n > limit)
372 {
373 warning("%s: line %d: number field exceeds %lu.",
374 name.c_str(), lineno, limit);
375 }
376
377 return n;
378 }
379
380 /* Reads a delta from the SCCS file's delta table and adds it to the
381 delta table. */
382
383 void
read_delta()384 sccs_file::read_delta() {
385
386 /* The current line should be an 's' control line */
387
388 ASSERT(bufchar(1) == 's');
389 check_arg();
390
391 char *args[7]; /* Stores the result of spliting a line */
392
393 if (plinebuf->split(3, args, 3, '/') != 3)
394 {
395 corrupt("Two /'s expected");
396 }
397
398 // TODO: use constructor here?
399 delta tmp; /* The new delta */
400 tmp.set_idu(strict_atoul_idu(args[0]),
401 strict_atoul_idu(args[1]),
402 strict_atoul_idu(args[2]));
403
404 char line_type;
405 if (!read_line(&line_type) || (line_type != 'd'))
406 {
407 corrupt("Expected '@d'");
408 }
409
410 check_arg();
411
412 plinebuf->split(3, args, 7, ' ');
413
414 if (delta::is_valid_delta_type(args[0][0])
415 && (args[0][1] == 0))
416 {
417 tmp.set_type(args[0][0]);
418 }
419 else
420 {
421 corrupt("Bad delta type");
422 }
423
424 tmp.set_id(sid(args[1]));
425 tmp.set_date(sccs_date(args[2], args[3]));
426 tmp.set_user(args[4]);
427 tmp.set_seq(strict_atous(args[5]));
428 tmp.set_prev_seq(strict_atous(args[6]));
429
430 if (!tmp.id().valid()) {
431 corrupt("Bad SID");
432 }
433 if (!tmp.date().valid()) {
434 corrupt("Bad Date/Time");
435 }
436
437 /* Read in any lists of included, excluded or ignored seq. no's. */
438
439 char c;
440 if (!read_line(&c))
441 {
442 corrupt("Unexpected end-of-file");
443 }
444
445 int i;
446 const char *start;
447 bool bDebug = getenv("CSSC_SHOW_SEQSTATE") ? true : false;
448 for(i = 0; i < 3; i++) {
449 if (c == "ixg"[i]) {
450
451 switch (c)
452 {
453 case 'i':
454 tmp.set_has_includes(true);
455 break;
456
457 case 'x':
458 {
459 warning("feature not fully tested: "
460 "excluded delta in SID %s ",
461 tmp.id().as_string().c_str());
462 tmp.set_has_excludes(true);
463 }
464 break;
465
466 case 'g':
467 tmp.set_has_ignores(true);
468 break;
469 }
470
471 if (bufchar(2) != ' ')
472 {
473 // throw line away.
474 read_line(&c); // FIXME: missing EOF check here.
475 continue;
476 }
477
478 check_arg();
479
480 start = plinebuf->c_str() + 3;
481 do {
482 // In C++, strchr() is overloaded so that
483 // it returns const char* if the first
484 // argument is const char*, and char* only if
485 // the first argument is char*.
486 const char *end = strchr(start, ' ');
487 if (end != NULL) {
488 //*end++ = '\0';
489 const char *p = plinebuf->c_str();
490 plinebuf->set_char(end-p, 0);
491 ASSERT(*end == 0);
492 ++end;
493 }
494 seq_no seq = strict_atous(start);
495 switch (c) {
496 case 'i':
497 if (bDebug)
498 {
499 fprintf(stderr, "Including seq %lu\n",
500 (unsigned long)seq);
501 }
502
503 tmp.add_include(seq);
504 break;
505
506 case 'x':
507 if (bDebug)
508 {
509 fprintf(stderr, "Excluding seq %lu\n",
510 (unsigned long)seq);
511 }
512 tmp.add_exclude(seq);
513 break;
514
515 case 'g':
516 tmp.add_ignore(seq);
517 break;
518 }
519 start = end;
520 } while (start != NULL);
521
522 read_line(&c); // FIXME: unchecked EOF.
523 }
524 }
525
526 // According to Hyman Rosen <hymie@jyacc.com>, it is possible
527 // to have a ^A m line which has no argument. Therefore we don't
528 // use check_arg().
529
530 // According to Hyman Rosen <hymie@jyacc.com>, it is sometimes
531 // possible to have ^Am lines after ^Ac lines, as well as the
532 // more usual before. Hence we now cope with both.
533
534 while (c == 'm' || c == 'c')
535 {
536 if (c == 'm')
537 {
538 if (bufchar(2) == ' ')
539 {
540 tmp.add_mr(plinebuf->c_str() + 3);
541 }
542 }
543 else if (c == 'c')
544 {
545 /* Larry McVoy's extensions for BitKeeper and BitSCCS
546 * add in extra stuff like "^AcSyadayada". Real SCCS
547 * doesn't mind about that, so at Larry's request, we
548 * tolerate it too. No idea what these lines mean though.
549 * Ask <lm@bitmover.com> for more information. Anyway,
550 * normal comment lines look like "^Ac yadayada" instead,
551 * and check_arg() exists to check for the space. Hence,
552 * to support Larry's extensions, we don't call check_arg()
553 * here.
554 */
555 if (is_bk_file)
556 {
557 check_bk_comment(c, bufchar(2));
558 }
559 else
560 {
561 if (bufchar(2) == '\0')
562 {
563 /* Some historic versions of SCCS emit totally empty
564 * comment lines. We accept those.
565 */
566 }
567 else if (bufchar(2) != ' ')
568 {
569 saw_unknown_feature("Unknown special comment "
570 "intro '%c%c'",
571 c, bufchar(2));
572 }
573 }
574 tmp.add_comment(plinebuf->c_str() + 3);
575 }
576
577 read_line(&c); // FIXME: check for EOF
578 }
579
580
581 if (c != 'e') {
582 corrupt("Expected '@e'");
583 }
584
585 check_noarg();
586
587 ASSERT(0 != delta_table);
588 delta_table->add(tmp);
589 }
590
591
592 /* Check for BK flags */
593 void
check_bk_flag(char flagchar) const594 sccs_file::check_bk_flag(char flagchar) const
595 {
596 switch (flagchar)
597 {
598 case 'x':
599 return;
600
601 default:
602 corrupt("Unknown flag '%c'.", flagchar);
603 }
604 }
605
606
607 /* Find out if it is OK to change the file - called by cdc, rmdel, get -e
608 */
609 bool
edit_mode_ok(bool editing) const610 sccs_file::edit_mode_ok(bool editing) const
611 {
612 if (editing && is_bk_file)
613 {
614 errormsg("%s: This is a BitKeeper file. Checking BitKeeper files out "
615 "for editing (or otherwise modifying them) is not supported "
616 "at the moment, sorry.\n",
617 name.c_str());
618 return false;
619 }
620 else
621 {
622 return true;
623 }
624 }
625
626
627 /* Check for BK comments */
628 void
check_bk_comment(char ch,char arg) const629 sccs_file::check_bk_comment(char ch, char arg) const
630 {
631 ASSERT(is_bk_file);
632
633 switch (arg)
634 {
635 case 'B':
636 case 'C':
637 case 'F':
638 case 'H':
639 case 'K':
640 case 'M':
641 case 'O':
642 case 'P':
643 case 'R':
644 case 'S':
645 case 'T':
646 case 'V':
647 case 'X':
648 case 'Z':
649 return;
650
651 case ' ': // this is the normal SCCS case.
652 return;
653
654 default:
655 saw_unknown_feature("Unknown special comment intro '%c%c' "
656 "in BitKeeper file\n",
657 ch, arg);
658 }
659 }
660
661
662 /* Seeks on the SCCS file to the start of the body. This function
663 may be rewritten as fseek() doesn't always work too well on
664 text files. */
665 // JAY: use fgetpos()/fsetpos() instead?
666 bool
seek_to_body()667 sccs_file::seek_to_body()
668 {
669 if (fseek(f, body_offset, SEEK_SET) != 0)
670 {
671 // this quit should NOT be fatal; we should proceed
672 // to the next file if we can.
673 errormsg("%s: fseek() failed!", name.c_str());
674 return false;
675 }
676 lineno = body_lineno;
677 return true;
678 }
679
checksum_ok() const680 bool sccs_file::checksum_ok() const
681 {
682 return checksum_valid;
683 }
684
685
686 /* Returns the module name of the SCCS file. */
687
688 std::string
get_module_name() const689 sccs_file::get_module_name() const
690 {
691 if (flags.module)
692 return *flags.module;
693 else
694 return name.gfile();
695 }
696
697 /* Constructor for the class sccs_file. Unless the SCCS file is being
698 created it reads in the all but the body of the file. The file is
699 locked if it isn't only being read. */
700
sccs_file(sccs_name & n,enum _mode m)701 sccs_file::sccs_file(sccs_name &n, enum _mode m)
702 : name(n), f(0), mode(m), lineno(0), xfile_created(false), is_bk_file(false)
703 {
704 delta_table = new cssc_delta_table;
705 plinebuf = new cssc_linebuf;
706 ASSERT(0 != delta_table);
707
708 if (!name.valid())
709 {
710 ctor_fail(-1,
711 "%s: Not an SCCS file. Did you specify the right file?",
712 name.c_str());
713 }
714
715 flags.no_id_keywords_is_fatal = 0;
716 flags.branch = 0;
717 flags.floor = (short)0;
718 flags.ceiling = (short)0;
719 flags.default_sid = sid::null_sid();
720 flags.null_deltas = 0;
721 flags.joint_edit = 0;
722 flags.all_locked = 0;
723 flags.encoded = 0;
724 flags.executable = 0;
725 flags.mr_checker = 0;
726 flags.module = 0;
727 flags.type = 0;
728 flags.reserved = 0;
729 flags.user_def = 0;
730
731 ASSERT(!flags.default_sid.valid());
732
733 if (mode != READ)
734 {
735 if (name.lock())
736 {
737 ctor_fail(-1, "%s: SCCS file is locked. Try again later.",
738 name.c_str());
739 }
740 }
741
742 if (mode == CREATE)
743 {
744 /* f is NULL in this case. */
745 return;
746 }
747
748 // Even if we are going to change the s-file, we do it by writing
749 // a new x-file and then renaming it. This means that we open
750 // the s-file read-only.
751 signed int sum = 0;
752 f = open_sccs_file(name.c_str(), READ, &sum, &is_bk_file);
753
754 if (mode != READ)
755 {
756 if (!edit_mode_ok(true))
757 {
758 ctor_fail(-1,
759 "%s: Editing is not supported for BitKeeper files.\n",
760 name.c_str());
761 }
762 }
763
764 /* open_sccs_file() returns normally if everything went OK, or if
765 * there was an IO error on an apparently valid file. If this is
766 * the case, perror() will already have been called.
767 */
768 if (NULL == f)
769 {
770 ctor_fail(-1, "%s: Cannot open SCCS file.\n", name.c_str());
771 }
772
773 // If the history file is executable, remember this fact.
774 bool is_executable;
775 if (get_open_file_xbits(f, &is_executable))
776 {
777 set_sfile_executable (is_executable);
778 }
779
780 char c;
781 read_line(&c); // FIXME: check for EOF
782
783 // open_sccs_file() should have already checked that the first line
784 // is ^Ah or ^Ah, so this assertion is really just checking that
785 // open_sccs_file() did the right thing.
786 if (is_bk_file)
787 {
788 ASSERT(c == 'H');
789 }
790 else
791 {
792 ASSERT(c == 'h');
793 }
794
795 /* the checksum is represented in the file as decimal.
796 */
797 signed int given_sum = 0;
798 const char *format;
799 if (is_bk_file)
800 format = "%*cH%d";
801 else
802 format = "%*ch%d";
803
804 if (1 != sscanf(plinebuf->c_str(), format, &given_sum))
805 {
806 errormsg("Expected checksum line, found line beginning '%.3s'\n",
807 plinebuf->c_str());
808 checksum_valid = false;
809 }
810 else
811 {
812 given_sum &= 0xFFFFu;
813 checksum_valid = (given_sum == sum);
814
815 if (false == checksum_valid)
816 {
817 if (FIX_CHECKSUM == mode)
818 {
819 // This supports the -z option of admin.
820 checksum_valid = true;
821 }
822 else
823 {
824 warning("%s: bad checksum "
825 "(expected=%d, calculated %d).\n",
826 name.c_str(), given_sum, sum);
827 }
828 }
829 }
830 if (!checksum_valid)
831 {
832 // Todo: throw exception here?
833 }
834
835 read_line(&c); // FIXME: detect eof
836 while (c == 's')
837 {
838 read_delta();
839 read_line(&c); // FIXME: detect eof
840 }
841
842 if (c != 'u')
843 {
844 corrupt("Expected '@u'");
845 }
846
847 check_noarg();
848
849 read_line(&c); // FIXME: detect eof
850 while (c != 'U')
851 {
852 if (c != 0)
853 {
854 corrupt("User name expected.");
855 }
856 users.push_back(plinebuf->c_str());
857 read_line(&c); // FIXME: detect eof
858 }
859
860 /* Sun's Code Manager sometimes emits lines of the form "^AU 0" and
861 * so these lines fail the "no argument" check. So we no longer do
862 * that check for "^AU" lines. A file including lines of this type
863 * was provided by Marko Rauhamaa <marko@tekelec.com>.
864 */
865 /* check_noarg(); */
866
867 read_line(&c); // FIXME: detect eof
868 while (c == 'f')
869 {
870 check_arg();
871
872 if (bufchar(3) == '\0'
873 || (bufchar(4) != '\0' && bufchar(4) != ' '))
874 {
875 corrupt("Bad flag arg.");
876 }
877
878 // We have to be careful to not crash on input lines like
879 // "^Af v". That is, bufchar[4] may well be zero!
880 // Thanks to William W. Austin <bill@baustin.alph.att.com>
881 // for this diagnosis.
882 const char *arg = 0;
883 bool got_arg = false;
884 if (bufchar(4) == ' ')
885 {
886 arg = plinebuf->c_str() + 5;
887 got_arg = true;
888 }
889 else
890 {
891 arg = "";
892 }
893
894 switch (bufchar(3)) {
895 case 't':
896 set_type_flag(arg);
897 break;
898
899 case 'v':
900 set_mr_checker_flag(arg);
901 break;
902
903 case 'i':
904 flags.no_id_keywords_is_fatal = 1;
905 break;
906
907 case 'b':
908 flags.branch = 1;
909 break;
910
911 case 'm':
912 set_module_flag(arg);
913 break;
914
915 case 'f':
916 flags.floor = release(arg);
917 if (!flags.floor.valid())
918 {
919 corrupt("Bad 'f' flag");
920 }
921 break;
922
923 case 'c':
924 flags.ceiling = release(arg);
925 if (!flags.ceiling.valid())
926 {
927 corrupt("Bad 'c' flag");
928 }
929 break;
930
931 case 'd':
932 flags.default_sid = sid(arg);
933 if (!flags.default_sid.valid())
934 {
935 corrupt("Bad 'd' flag");
936 }
937 break;
938
939 case 'n':
940 flags.null_deltas = 1;
941 break;
942
943 case 'j':
944 flags.joint_edit = 1;
945 break;
946
947 case 'l':
948 if (got_arg && strcmp(arg, "a") == 0)
949 {
950 flags.all_locked = 1;
951 }
952 else
953 {
954 flags.locked = release_list(arg);
955 }
956 break;
957
958 case 'q':
959 set_user_flag(arg);
960 break;
961
962 case 'z':
963 set_reserved_flag(arg);
964 break;
965
966 case 'x':
967 // The 'x' flag is supported by SCO's version of SCCS.
968 // When this flag is set, the g-file is marked executable.
969 // The g-file is also executable when the s-file is executable
970 // (to follow the example of Solaris).
971 flags.executable = 1;
972 break;
973
974 case 'y':
975 // The 'y' flag is supported by Solaris 8 and above.
976 // It controls the expansion of '%' keywords. If the
977 // y flag is set, its value is a list of keywords that will
978 // be expanded. Otherwise, all known keywords will be expanded.
979 set_expanded_keyword_flag(arg);
980 break;
981
982 case 'e':
983 if (got_arg && '1' == *arg)
984 flags.encoded = 1;
985 else if (got_arg && '0' == *arg)
986 flags.encoded = 0;
987 else
988 corrupt("Bad value '%c' for 'e' flag.", arg[0]);
989 break;
990
991 default:
992 if (is_bk_file)
993 {
994 check_bk_flag(bufchar(3));
995 }
996 else
997 {
998 corrupt("Unknown flag '%c'.", bufchar(3));
999 }
1000 }
1001
1002 read_line(&c); // FIXME: eof detection
1003 }
1004
1005 if (c != 't')
1006 {
1007 corrupt("Expected '@t'");
1008 }
1009
1010 /* Sun's Code Manager sometimes emits lines of the form "^At 0" and
1011 * so these lines fail the "no argument" check. So we no longer do
1012 * that check for "^At" lines. A file including lines of this type
1013 * was provided by Marko Rauhamaa <marko@tekelec.com>.
1014 */
1015 /*check_noarg();*/
1016
1017 read_line(&c); // FIXME: eof detection
1018 while (c == 0)
1019 {
1020 comments.push_back(plinebuf->c_str());
1021 read_line(&c); // FIXME: eof detection
1022 }
1023
1024 if (c != 'T')
1025 {
1026 corrupt("Expected '@T'");
1027 }
1028
1029 /* Sun's Code Manager sometimes emits lines of the form "^AT 0" and
1030 * so these lines fail the "no argument" check. So we no longer do
1031 * that check for "^AT" lines. A file including lines of this type
1032 * was provided by Marko Rauhamaa <marko@tekelec.com>.
1033 */
1034 /*check_noarg();*/
1035
1036 body_offset = ftell(f);
1037 if (body_offset == -1L)
1038 {
1039 ctor_fail(errno, "ftell() failed.");
1040 }
1041
1042 body_lineno = lineno;
1043 }
1044
1045
1046 /* Find the SID of the most recently created delta with the same release
1047 and level as the requested SID. */
1048
1049 sid
find_most_recent_sid(sid id) const1050 sccs_file::find_most_recent_sid(sid id) const {
1051 sccs_date newest;
1052 sid found;
1053
1054 ASSERT(0 != delta_table);
1055 const_delta_iterator iter(delta_table);
1056
1057 while (iter.next()) {
1058 if (id.trunk_match(iter->id())) {
1059 if (found.is_null() || newest < iter->date()) {
1060 newest = iter->date();
1061 found = iter->id();
1062 }
1063 }
1064 }
1065 return found;
1066 }
1067
1068 bool
find_most_recent_sid(sid & s,sccs_date & d) const1069 sccs_file::find_most_recent_sid(sid& s, sccs_date& d) const
1070 {
1071 s = sid();
1072 d = sccs_date();
1073 bool found = false;
1074
1075 ASSERT(0 != delta_table);
1076
1077 const_delta_iterator iter(delta_table);
1078 while (iter.next())
1079 {
1080 if (!found || iter->date() > d)
1081 {
1082 d = iter->date();
1083 s = iter->id();
1084 found = true;
1085 }
1086 }
1087 return found;
1088 }
1089
1090 void sccs_file::
set_mr_checker_flag(const char * s)1091 set_mr_checker_flag(const char *s)
1092 {
1093 if (flags.mr_checker)
1094 delete flags.mr_checker;
1095
1096 flags.mr_checker = new std::string(s);
1097 }
1098
1099 void sccs_file::
set_module_flag(const char * s)1100 set_module_flag(const char *s)
1101 {
1102 if (flags.module)
1103 delete flags.module;
1104
1105 flags.module = new std::string(s);
1106 }
1107
1108 void sccs_file::
set_user_flag(const char * s)1109 set_user_flag(const char *s)
1110 {
1111 if (flags.user_def)
1112 delete flags.user_def;
1113
1114 flags.user_def = new std::string(s);
1115 }
1116
1117 void sccs_file::
set_type_flag(const char * s)1118 set_type_flag(const char *s)
1119 {
1120 if (flags.type)
1121 delete flags.type;
1122
1123 flags.type = new std::string(s);
1124 }
1125
1126 void sccs_file::
set_reserved_flag(const char * s)1127 set_reserved_flag(const char *s)
1128 {
1129 if (flags.reserved)
1130 delete flags.reserved;
1131
1132 flags.reserved = new std::string(s);
1133 }
1134
1135
1136 void sccs_file::
set_expanded_keyword_flag(const char * s)1137 set_expanded_keyword_flag(const char *s)
1138 {
1139 const size_t len = strlen(s);
1140 std::set<char> tmp_letters(s, s + len);
1141 std::swap(tmp_letters, flags.substitued_flag_letters);
1142 }
1143
1144 int
read_line_param(FILE * fp)1145 sccs_file::read_line_param(FILE *fp)
1146 {
1147 if (plinebuf->read_line(fp))
1148 {
1149 return 1;
1150 }
1151 // chomp the newline from the end of the line.
1152 // TODO: make me 8-bit clean!
1153 (*plinebuf)[strlen(plinebuf->c_str()) - 1] = '\0';
1154 return 0;
1155 }
1156
1157 int
is_delta_creator(const char * user,sid id) const1158 sccs_file::is_delta_creator(const char *user, sid id) const
1159 {
1160 const delta *d = find_delta(id);
1161 return (d != 0) && (strcmp(d->user().c_str(), user) == 0);
1162 }
1163
1164
find_delta(sid id) const1165 const delta * sccs_file::find_delta(sid id) const
1166 {
1167 ASSERT(0 != delta_table);
1168 return delta_table->find(id);
1169 }
1170
find_any_delta(sid id) const1171 const delta * sccs_file::find_any_delta(sid id) const
1172 {
1173 ASSERT(0 != delta_table);
1174 return delta_table->find_any(id);
1175 }
1176
find_delta(sid id)1177 delta * sccs_file::find_delta(sid id)
1178 {
1179 ASSERT(0 != delta_table);
1180 return delta_table->find(id);
1181 }
1182
highest_delta_seqno() const1183 seq_no sccs_file::highest_delta_seqno() const
1184 {
1185 ASSERT(0 != delta_table);
1186 return delta_table->highest_seqno();
1187 }
1188
highest_delta_release() const1189 sid sccs_file::highest_delta_release() const
1190 {
1191 ASSERT(0 != delta_table);
1192 return delta_table->highest_release();
1193 }
1194
seq_to_sid(seq_no seq) const1195 sid sccs_file::seq_to_sid(seq_no seq) const
1196 {
1197 ASSERT(0 != delta_table);
1198 return delta_table->delta_at_seq(seq).id();
1199 }
1200
1201
1202 /* Destructor for class sccs_file. */
1203
~sccs_file()1204 sccs_file::~sccs_file()
1205 {
1206 if (mode != READ)
1207 {
1208 name.unlock();
1209 }
1210
1211 if (mode != CREATE)
1212 {
1213 ASSERT(0 != f); // catch multiple destruction.
1214 fclose(f);
1215 f = 0;
1216 }
1217
1218 if (xfile_created)
1219 {
1220 remove(name.xfile().c_str());
1221 }
1222
1223 ASSERT(0 != delta_table); // catch multiple destruction.
1224 delete delta_table;
1225 delta_table = 0;
1226
1227 ASSERT(0 != plinebuf); // catch multiple destruction.
1228 delete plinebuf;
1229 plinebuf = 0;
1230 }
1231
1232
bufchar(int pos) const1233 char sccs_file::bufchar(int pos) const
1234 {
1235 return (*plinebuf)[pos];
1236 }
1237
1238
branches_allowed() const1239 bool sccs_file::branches_allowed() const
1240 {
1241 return 0 != flags.branch;
1242 }
1243
1244 /* There are some features that we don't properly understand.
1245 * If we see them, we should abandon any attempt to modify the
1246 * file. We call saw_unknown_feature() when we see one. It
1247 * checks what mode we're using the SCCS file in, and reacts
1248 * accordingly.
1249 */
saw_unknown_feature(const char * fmt,...) const1250 void sccs_file::saw_unknown_feature(const char *fmt, ...) const
1251 {
1252 va_list ap;
1253
1254 va_start(ap, fmt);
1255
1256 /* If we are not modifying the file, just issue a warning. Otherwise,
1257 * abandon the attempt to edit it.
1258 */
1259 switch (mode)
1260 {
1261 case READ:
1262 case FIX_CHECKSUM:
1263 v_unknown_feature_warning(fmt, ap);
1264 break;
1265
1266 case UPDATE:
1267 case CREATE:
1268 s_unrecognised_feature_quit(fmt, ap);
1269 /*NOTREACHED*/
1270 break;
1271 }
1272 }
1273
1274 bool sccs_file::
print_subsituted_flags_list(FILE * out,const char * separator) const1275 print_subsituted_flags_list(FILE *out, const char* separator) const
1276 {
1277 bool first = true;
1278 for (auto flagletter : flags.substitued_flag_letters)
1279 {
1280 if (!first)
1281 {
1282 // A space separator is required.
1283 if (printf_failed(fprintf(out, "%s", separator)))
1284 return false;
1285 }
1286 first = false;
1287
1288 // print the keyword letter.
1289 if (printf_failed(fprintf(out, "%c", flagletter)))
1290 return false;
1291 }
1292 return true;
1293 }
1294
1295 bool sccs_file::
is_known_keyword_char(char c)1296 is_known_keyword_char(char c)
1297 {
1298 return strchr("MIRLBSDHTEGUYFPQCZWA", c) != NULL;
1299 }
1300
1301 void sccs_file::
set_sfile_executable(bool state)1302 set_sfile_executable(bool state)
1303 {
1304 sfile_executable = state;
1305 }
1306
1307 bool sccs_file::
gfile_should_be_executable() const1308 gfile_should_be_executable() const
1309 {
1310 return sfile_executable || flags.executable;
1311 }
1312
1313
1314 bool sccs_file::
sfile_should_be_executable() const1315 sfile_should_be_executable() const
1316 {
1317 return sfile_executable;
1318 }
1319
1320
1321
1322 /* Local variables: */
1323 /* mode: c++ */
1324 /* End: */
1325