1 /*
2  * sf-val.cc: Part of GNU CSSC.
3  *
4  *
5  *  Copyright (C) 1997, 1998, 2001, 2002, 2004, 2007, 2008, 2009, 2010,
6  *  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  *
22  * Members of class sccs_file used by "val".
23  *
24  */
25 #include <config.h>
26 
27 #include <vector>
28 
29 #include "cssc.h"
30 #include "sccsfile.h"
31 #include "delta.h"
32 #include "delta-table.h"
33 #include "delta-iterator.h"
34 
35 
36 namespace {
37 
38   // Check that there are no loops on the way from s to the first delta.
check_loop_free(const char * name,cssc_delta_table * t,seq_no starting_seq,std::vector<bool> & loopfree,std::vector<bool> & seen)39   bool check_loop_free(const char *name,
40 		       cssc_delta_table* t,
41 		       seq_no starting_seq,
42 		       std::vector<bool>& loopfree,
43 		       std::vector<bool>& seen)
44   {
45     bool ok = true;
46     const seq_no highest_seqno = t->highest_seqno();
47     seq_no lowest_dirty_seq = highest_seqno;
48 
49     std::vector<bool>::iterator i;
50 
51     // Check there are no loops.
52     for (seq_no s=starting_seq;
53 	 s;
54 	 s = t->delta_at_seq(s).prev_seq())
55       {
56 	if (s < lowest_dirty_seq)
57 	  lowest_dirty_seq = s;
58 
59 	if (loopfree[s])
60 	  {
61 	    break;
62 	  }
63 	else if (seen[s])
64 	  {
65 	    errormsg("%s: loop in predecessors at sequence number %u\n",
66 		     name, (unsigned)s);
67 	    ok = false;
68 	    break;
69 	  }
70 	else
71 	  {
72 	    seen[s] = true;
73 	  }
74       }
75 
76     // Mark the newly-explored loop-free graph as being loop-free.
77     if (ok)
78       {
79 	for (seq_no s=starting_seq;
80 	     s;
81 	     s = t->delta_at_seq(s).prev_seq())
82 	  {
83 	    if (loopfree[s])
84 	      break;
85 	    else if (seen[s])
86 	      loopfree[s] = true;
87 	  }
88       }
89     for (i = seen.begin()+lowest_dirty_seq; i != seen.end(); ++i)
90       *i = false;
91 
92     return ok;
93   }
94 }
95 
96 
97 
98 const std::string
get_module_type_flag()99 sccs_file::get_module_type_flag()
100 {
101   if (flags.type)
102     return *(flags.type);
103   else
104     return std::string("");
105 }
106 
107 namespace {
validate_seq_numbers(const string & name,const std::vector<seq_no> & seqs,const char * sz_sid,seq_no limit,const char * seq_type)108   bool validate_seq_numbers (const string& name, const std::vector<seq_no>& seqs, const char *sz_sid,
109 			     seq_no limit, const char* seq_type)
110   {
111     for (auto s : seqs)
112       {
113 	if (s > limit)
114 	{
115 	  errormsg ("%s: SID %s: %s seqno %u does not exist\n",
116 		    name.c_str(), sz_sid, seq_type, (unsigned)s);
117 	  return false;
118 	}
119       }
120     return true;
121   }
122 }
123 
124 bool
validate_seq_lists(const delta_iterator & d) const125 sccs_file::validate_seq_lists(const delta_iterator& d) const
126 {
127   const char *sz_sid = d->id().as_string().c_str();
128   const seq_no highest_seq = delta_table->highest_seqno();
129   const string& sname = name.sfile();
130   return (validate_seq_numbers(sname, d->get_included_seqnos(),
131 			       sz_sid, highest_seq, "included") &&
132 	  validate_seq_numbers(sname, d->get_excluded_seqnos(),
133 			       sz_sid, highest_seq, "excluded") &&
134 	  validate_seq_numbers(sname, d->get_ignored_seqnos(),
135 			       sz_sid, highest_seq, "ignored"));
136 }
137 
138 bool
validate_isomorphism() const139 sccs_file::validate_isomorphism() const
140 {
141   // TODO: write a bug-free version
142   return true;
143 }
144 
145 static bool
validate_substituted_flags_list(const std::vector<char> entries)146 validate_substituted_flags_list(const std::vector<char> entries)
147 {
148   // TODO: write this later.
149   return true;
150 }
151 
152 
153 bool
validate() const154 sccs_file::validate() const
155 {
156   bool retval = true;
157 
158   if (!checksum_ok())
159     {
160       return false;
161     }
162 
163   // for each delta:-
164   delta_iterator iter(delta_table);
165   const seq_no highest_seq = delta_table->highest_seqno();
166   int *seen_ever = new int[highest_seq];
167   std::vector<bool> seen(highest_seq+1, false);
168   std::vector<bool> loopfree(highest_seq+1, false);
169 
170 
171   for (seq_no i=0; i<highest_seq; ++i)
172     {
173       seen_ever[i] = 0;
174     }
175 
176   while ( retval && iter.next())
177     {
178       seq_no s = iter->seq();
179 
180       const char *sz_sid = iter->id().as_string().c_str();
181 
182       // validate that the included/excluded/unchanged line counts are valid.
183       if (iter->inserted() > 99999uL)
184 	{
185 	  errormsg("%s: SID %s: out-of-range inserted line count %lu",
186 		   name.c_str(), sz_sid, iter->inserted());
187 	  retval = false;
188 	}
189       if (iter->deleted() > 99999uL)
190 	{
191 	  errormsg("%s: SID %s: out-of-range deleted line count %lu",
192 		   name.c_str(), sz_sid, iter->deleted());
193 	  retval = false;
194 	}
195       if (iter->unchanged() > 99999uL)
196 	{
197 	  errormsg("%s: SID %s: out-of-range unchanged line count %lu",
198 		   name.c_str(), sz_sid, iter->unchanged());
199 	  retval = false;
200 	}
201 
202       if (!iter->date().valid())
203 	{
204 	  errormsg("%s: SID %s: invalid date", name.c_str(), sz_sid);
205 	  retval = false;
206 	}
207 
208       // check that username contains no colon.
209       if (iter->user().empty())
210 	{
211 	  errormsg("%s: SID %s: empty username", name.c_str(), sz_sid);
212 	  retval = false;
213 	}
214       else if (iter->user().find_last_of(':') != std::string::npos)
215 	{
216 	  errormsg("%s: SID %s: invalid username '%s'",
217 		   name.c_str(), sz_sid, iter->user().c_str());
218 	  retval = false;
219 	}
220 
221       // check seqno is valid - loops, dangling references.
222       if (s > highest_seq)
223 	{
224 	  errormsg("%s: SID %s: invalid seqno %d",
225 		   name.c_str(), sz_sid, (int)s);
226 	  retval = false;
227 	}
228 
229       if (iter->prev_seq() > highest_seq)
230 	{
231 	  errormsg("%s: SID %s: invalid predecessor seqno %d",
232 		   name.c_str(), sz_sid, (int)iter->prev_seq());
233 	  retval = false;
234 	}
235 
236       if (seen_ever[s - 1] > 1)
237 	{
238 	  errormsg("%s: seqno %d appears more than once (%d times)",
239 		   name.c_str(), (int)s, seen_ever[s - 1]);
240 	  retval = false;		// seqno appears more than once.
241 	}
242 
243       ++seen_ever[s - 1];
244 
245       if (!check_loop_free(name.c_str(),
246 			   delta_table, delta_table->delta_at_seq(s).seq(),
247 			   loopfree, seen))
248 	{
249 	  // We already issued an error message.
250 	  retval = false;
251 	}
252 
253       // check time doesn't go backward (warning only, because this is
254       // possible if developers in different timezones are
255       // collaborating on the same file).
256       if (0 != iter->prev_seq())
257 	{
258 	  const delta& ancestor(delta_table->delta_at_seq(iter->prev_seq()));
259 	  if (ancestor.date() > iter->date())
260 	    {
261 	      // Time has apparently gone backward...
262 	      warning("%s: date for version %s"
263 		       " is later than the date for version %s",
264 		       name.c_str(),
265 		      ancestor.id().as_string().c_str(),
266 		      iter->id().as_string().c_str());
267 	    }
268 	}
269 
270       // check included/excluded/ignored deltas actually exist.
271       validate_seq_lists(iter);
272     }
273   delete[] seen_ever;
274 
275   if (false == retval)
276     return retval;
277 
278   if (!validate_isomorphism())	// check that SIDs follow seqno structure.
279     {
280       return false;
281     }
282 
283   // Check username list for invalid entries.
284   for (const auto& username : users)
285     {
286       if (   (username.find_last_of(':')  != std::string::npos)
287 	  || (username.find_last_of(' ')  != std::string::npos)
288 	  || (username.find_last_of('\t') != std::string::npos))
289 	{
290 	  errormsg("%s: invalid username '%s'",
291 		   name.c_str(), username.c_str());
292 	  retval = false;
293 	}
294     }
295 
296 
297   // for the flags section:-
298 
299   // Check that the 'y' flag specifies only known keywords.
300   for (auto flag : flags.substitued_flag_letters)
301     {
302       if (!is_known_keyword_char(flag))
303 	{
304 	  warning("The 'y' flag specifies a keyword letter '%c', "
305 		  "but %%%c%% is not a recognised SCCS keyword" ,
306 		  flag, flag);
307 	}
308     }
309 
310   if (flags.floor > delta_table->highest_release())
311     {
312       warning("%s has a release floor of %d but the highest actual release "
313 	      "in the file is %d",
314 	      name.c_str(), (short)flags.floor,
315 	      delta_table->highest_release().as_string().c_str());
316     }
317 
318   if (!flags.default_sid.is_null())
319     {
320       const delta* pd = delta_table->find_any(flags.default_sid);
321       if (pd)
322 	{
323 	  if (pd->removed())
324 	    {
325 	      warning("%s has a default SID of %s, but that SID has "
326 		      "been removed",
327 		      name.c_str(), flags.default_sid.as_string().c_str());
328 	    }
329 	}
330       else
331 	{
332 	  warning("%s has a default SID of %s, but that SID is not present",
333 		  name.c_str(), flags.default_sid.as_string().c_str());
334 	}
335     }
336 
337 
338 
339   // TODO: check for unknown flags
340   // TODO: check for boolean flags with non-numeric value.
341 
342   // TODO: check the body (unclosed deltas, etc.)
343 
344   return retval;
345 }
346 
347 
348 /* Local variables: */
349 /* mode: c++ */
350 /* End: */
351