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