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