1 /*
2  * sf-prt.cc: Part of GNU CSSC.
3  *
4  *  Copyright (C) 1997, 1998, 1999, 2001, 2004, 2007, 2008, 2009, 2010,
5  *  2011, 2014, 2019 Free Software Foundation, Inc.
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  * CSSC was originally Based on MySC, by Ross Ridge, which was
21  * placed in the Public Domain.
22  *
23  *
24  * Members of the class sccs_file for doing sccs-prt.
25  */
26 
27 
28 #include <config.h>
29 
30 #include "cssc.h"
31 #include "sccsfile.h"
32 #include "seqstate.h"
33 #include "filepos.h"
34 #include "delta.h"
35 #include "delta-iterator.h"
36 
37 
38 #include <unistd.h>		// SEEK_SET on SunOS.
39 
40 
41 template<class Container>
42 static void
print_string_list(FILE * out,const Container & l,const char * pre,const char * post,const char * dflt)43 print_string_list(FILE *out,
44 		  const Container& l,
45 		  const char* pre,
46 		  const char* post,
47 		  const char* dflt)
48 {
49   const std::vector<std::string>::size_type len = l.size();
50 
51   if (0 == len)
52     {
53       fprintf(out, "%s%s", pre, dflt);
54     }
55   else
56     {
57       for (std::vector<std::string>::size_type i = 0; i < len; i++)
58 	{
59 	  fprintf(out, "%s%s", pre, l[i].c_str());
60 	  if (i < len-1)
61 	    {
62 	      fprintf(out, "%s", post);
63 	    }
64 
65 	}
66     }
67 }
68 
print_flag(FILE * out,const char * fmt,std::string flag,int & count)69 void print_flag(FILE *out, const char *fmt, std::string flag, int& count)
70 {
71   if (!flag.empty())
72     {
73       ++count;
74       fprintf(out, fmt, flag.c_str());
75     }
76 }
77 
print_flag(FILE * out,const char * fmt,const std::string * pflag,int & count)78 void print_flag(FILE *out, const char *fmt, const std::string* pflag, int& count)
79 {
80   // We consider a flag which is set to an empty string still to be set.
81   // An example is the v flag; lines of the form "^Af v" should still set
82   // the v flag to an empty string.
83   if (pflag)
84     {
85       ++count;
86       fprintf(out, fmt, pflag->c_str());
87     }
88 }
89 
print_flag(FILE * out,const char * fmt,int flag,int & count)90 void print_flag(FILE *out, const char *fmt,  int flag, int& count)
91 {
92   if (flag)
93     {
94       ++count;
95       fprintf(out, fmt, flag ? "yes" : "no");
96     }
97 }
98 
print_flag(FILE * out,const char * fmt,sid flag,int & count)99 void print_flag(FILE *out, const char *fmt,  sid flag, int& count)
100 {
101   if (flag.valid())
102     {
103       ++count;
104       fprintf(out, "%s", fmt);
105       flag.print(out);
106       putc('\n', out);
107     }
108 }
109 
print_flag(FILE * out,const char * fmt,release flag,int & count)110 void print_flag(FILE *out, const char *fmt,  release flag, int& count)
111 {
112   if (flag.valid())
113     {
114       ++count;
115       fprintf(out, "%s", fmt);
116       flag.print(out);
117       fprintf(out, "\n");
118     }
119 }
120 
excludes_delta(sid,sccs_date date,bool & stop_now) const121 bool sccs_file::cutoff::excludes_delta(sid /* s */,
122 				       sccs_date date,
123 				       bool& stop_now) const
124 {
125   stop_now = false;
126 
127   if (!enabled)
128     return false;
129 
130   if (first_accepted.valid())
131     {
132       // stop_now = stop_now || (date <= first_accepted);
133       if (date < first_accepted)
134 	return true;
135     }
136   if (last_accepted.valid())
137     {
138       // Don't set stop_now, since we have not even got to the first
139       // one that's not excluded, yet.
140       if (date > last_accepted)
141 	return true;
142     }
143   if (cutoff_delta)
144     {
145       stop_now = stop_now || (date <= cutoff_delta->date());
146       if (date < cutoff_delta->date())
147 	return true;
148     }
149   return false;
150 }
151 
152 void
print(FILE * out) const153 sccs_file::cutoff::print(FILE *out) const
154 {
155   fprintf(out, "cutoff: ");
156   if (enabled)
157     {
158       fputs("enabled ", out);
159       if (most_recent_sid_only)
160 	  fputs("most-recent-only ", out);
161       fputs("cutoff_sid='", out);
162       if (cutoff_sid.valid())
163 	cutoff_sid.print(out);
164       else
165 	fputs("(invalid)", out);
166 
167       fputs("' first_accepted='", out);
168       if (first_accepted.valid())
169 	{
170 	  first_accepted.printf(out, 'D');
171 	  fprintf(out, " ");
172 	  first_accepted.printf(out, 'T');
173 	}
174       else
175 	{
176 	fputs("(invalid)'", out);
177 	}
178       fputs("' last_accepted='", out);
179       if (last_accepted.valid())
180 	{
181 	  last_accepted.printf(out, 'D');
182 	  fprintf(out, " ");
183 	  last_accepted.printf(out, 'T');
184 	}
185       else
186 	{
187 	fputs("(invalid)'", out);
188 	}
189 
190       if (cutoff_delta)
191 	{
192 	  fputs("' cutoff_delta->date='", out);
193 	  if (cutoff_delta->date().valid())
194 	    {
195 	      last_accepted.printf(out, 'D');
196 	      fprintf(out, " ");
197 	      last_accepted.printf(out, 'T');
198 	    }
199 	  else
200 	    {
201 	      fputs("(invalid)'", out);
202 	    }
203 	}
204       fputs("'\n", out);
205     }
206   else
207     {
208       fputs("disabled\n", out);
209     }
210 }
211 
cutoff()212 sccs_file::cutoff::cutoff()
213   : enabled(false), most_recent_sid_only(false),
214     cutoff_sid(sid()),
215     cutoff_delta(NULL)
216 {
217   // all done above.
218 }
219 
220 // Print the body of an SCCS file, transforming all "^A"s
221 // into "*** "s.
222 static bool
do_print_body(const char * name,FILE * fp,long body_offset,FILE * out)223 do_print_body(const char *name, FILE *fp, long body_offset, FILE *out)
224 {
225   bool ret = true;
226 
227   // When pos_saver goes out of scope the file position on "fp" is restored.
228   FilePosSaver pos_saver(fp);
229 
230   if (0 != fseek(fp, body_offset, SEEK_SET))
231     {
232       errormsg_with_errno("%s: fseek() failed!", name);
233       return false;		// can't read body now, so just fail.
234     }
235 
236 
237   if (putc_failed(putc('\n', out)))
238     ret = false;
239 
240   int ch;
241   while ( ret && (ch=getc(fp)) != EOF )
242     {
243       if ('\001' == ch)
244 	{
245 	  if (fputs_failed(fputs("*** ", out)))
246 	    {
247 	      ret = false;	// write error
248 	      break;
249 	    }
250 	}
251       else if ('\n' == ch)
252 	{
253 	  int peek = getc(fp);
254 
255 	  if ('\001' == peek)
256 	    {
257 	      ungetc(peek, fp);
258 	      if (putc_failed(putc('\n', out)))
259 		ret = false;
260 	    }
261 	  else if (EOF == peek)
262 	    {
263 	      if (putc_failed(putc('\n', out)))
264 		ret = false;
265 	      break;
266 	    }
267 	  else
268 	    {
269 	      ungetc(peek, fp);
270 	      if (fputs_failed(fputs("\n\t", out)))
271 		{
272 		  ret = false;	// write error
273 		  break;
274 		}
275 	    }
276 	}
277       else
278 	{
279 	  if (putc_failed(putc(ch, out)))
280 	    {
281 	      ret = false;	// write error
282 	      break;
283 	    }
284 	}
285     }
286   if (ferror(fp))		// read error is fatal.
287     {
288       errormsg_with_errno("%s: read failed!", name);
289       ret = false;
290     }
291 
292   // When pos_saver goes out of scope the file position is restored.
293   return ret;
294 }
295 
296 static void
print_seq_list(FILE * out,std::vector<seq_no> const & list)297 print_seq_list(FILE *out, std::vector<seq_no> const &list)
298 {
299   bool first = true;
300   for (const auto& seq : list)
301     {
302       fprintf(out, "%s%u", (first ? "" : " "), seq);
303       first = false;
304     }
305 }
306 
307 
308 bool
prt(FILE * out,cutoff exclude,int all_deltas,int print_body,int print_delta_table,int print_flags,int incl_excl_ignore,int first_line_only,int print_desc,int print_users) const309 sccs_file::prt(FILE *out,
310 	       cutoff exclude,	      // -y, -c, -r
311 	       int all_deltas,	      // -a
312 	       int print_body,	      // -b
313 	       int print_delta_table, // -d
314 	       int print_flags,	      // -f
315 	       int incl_excl_ignore,  // -i
316 	       int first_line_only,   // -s
317 	       int print_desc,	      // -t
318 	       int print_users) const // -u
319 
320 {
321   const int suppress_newlines = exclude.enabled;
322   const char* nl_sep = suppress_newlines ? " " : "\n";
323 
324   if (print_delta_table)
325     {
326       if (exclude.enabled)
327 	{
328 	  if (exclude.most_recent_sid_only)
329 	    {
330 	      find_most_recent_sid(exclude.cutoff_sid,
331 				   exclude.first_accepted);
332 	    }
333 	  if (exclude.cutoff_sid.valid())
334 	    exclude.cutoff_delta = find_delta(exclude.cutoff_sid);
335 	}
336 
337       bool stop_now = false;
338       const_delta_iterator iter(delta_table);
339 
340       while (!stop_now && iter.next(all_deltas))
341 	{
342 	  if (exclude.excludes_delta(iter->id(), iter->date(), stop_now))
343 	    continue;
344 
345 	  // Unless -a was specified, don't print removed deltas.
346 	  if (!all_deltas && iter->removed())
347 	    continue;
348 
349 	  if (exclude.enabled)	// -y, -c, or -r option.
350 	    fprintf(out, "%s:\t", name.c_str());
351 	  else
352 	    putc('\n', out);
353 
354 	  // Print the stuff from the delta...
355 	  fprintf(out, "%c ", iter->get_type());
356 	  iter->id().print(out);
357 	  putc('\t', out);
358 	  iter->date().printf(out, 'D');
359 	  putc(' ', out);
360 	  iter->date().printf(out, 'T');
361 	  fprintf(out, " %s\t%hu %hu",
362 		  iter->user().c_str(),
363 		  (unsigned short)iter->seq(),
364 		  (unsigned short)iter->prev_seq());
365 	  fprintf(out, "\t%05lu/%05lu/%05lu",
366 		  iter->inserted(), iter->deleted(), iter->unchanged());
367 
368 
369 	  if (!first_line_only)
370 	    {
371 	      if (incl_excl_ignore)
372 		{
373 		  // TODO: find an elegant solution to this problem.
374 		  //
375 		  // REAL SCCS prints an "Included" line in the output
376 		  // if it sees "^Ai " and "an "Excluded" line if it
377 		  // sees "^Ax ", even if the rest of the lines was
378 		  // blank.  For CSSC, the rest of these lines is
379 		  // built into a list of "seq_no"s. and so if the
380 		  // line is blank the data structure is empty and
381 		  // nothing is printed.  This is a behavioural
382 		  // difference that to fix seems to require a real
383 		  // kludge.
384 		  if (!iter->get_included_seqnos().empty())
385 		    {
386 		      fputs(nl_sep, out);	// either newline or space.
387 		      fprintf(out, "Included:\t");
388 		      print_seq_list(out, iter->get_included_seqnos());
389 		    }
390 		  else if (iter->has_includes())
391 		    {
392 		      fputs(nl_sep, out);	// either newline or space.
393 		      fprintf(out, "Included:\t");
394 		    }
395 		  if (!iter->get_excluded_seqnos().empty())
396 		    {
397 		      fputs(nl_sep, out);	// either newline or space.
398 		      fprintf(out, "Excluded:\t");
399 		      print_seq_list(out, iter->get_excluded_seqnos());
400 		    }
401 		  else if (iter->has_excludes())
402 		    {
403 		      fputs(nl_sep, out);	// either newline or space.
404 		      fprintf(out, "Excluded:\t");
405 		    }
406 		}
407 	      // Print any MRs and then the comments.
408 	      if (!iter->mrs().empty())
409 		{
410 		  fputs(nl_sep, out);	// either newline or space.
411 		  print_string_list(out, iter->mrs(), "MRs:\t", nl_sep, "");
412 		}
413 	      if (!iter->comments().empty())
414 		{
415 		  fputs(nl_sep, out);	// either newline or space.
416 		  print_string_list(out, iter->comments(), "", nl_sep, "");
417 		}
418 	    }
419 	  putc('\n', out);
420 	}
421     }
422 
423   // global stuff.
424   if (print_users)
425     {
426       fprintf(out, "\n Users allowed to make deltas -- \n");
427       print_string_list(out, users, "\t", "\n", "everyone");
428       putc('\n', out);
429     }
430 
431   if (print_flags)
432     {
433       // PROBLEM:
434       //
435       // Those SCCS flags that have no "value" field, that is, those
436       // which are either on or off, have a tab printed immediately
437       // before their newlines...
438       //
439       // Except the "encoded" flag, which doesn't have a newline
440       // printed after it, for some reason.
441       //
442       // But we emulate even that bug :-)
443       //
444       int flag_count = 0;
445       fprintf(out, "\nFlags --\n");
446       if (flags.branch)
447 	{
448 	  // "Real" SCCS prints a TAB after "branch", so we do too.
449 	  fprintf(out, "\tbranch\t\n");
450 	  ++flag_count;
451 	}
452 
453       print_flag(out, "\tceiling\t", flags.ceiling, flag_count);
454       print_flag(out, "\tdefault SID\t", flags.default_sid, flag_count);
455 
456       // No newline after this one;  odd, but it's what "real"
457       // SCCS does.
458       if (flags.encoded)
459 	{
460 	  fprintf(out, "\tencoded");
461 	  ++flag_count;
462 	}
463 
464 #if 0
465       // The 'x' flag is a SCO OpenServer extension.
466       // SCO OpenServer 5.0.6 has no "prt" command, but
467       // SCO Unixware 8.0.0 does have it.  However, it
468       // does not print anything if the x flag is set.
469       // Hence for compatibility, we don't do that either.
470       print_flag(out, "\texecutable\t\n", flags.executable, flag_count);
471 #endif
472 
473       print_flag(out, "\tfloor\t", flags.floor, flag_count);
474       if (flags.no_id_keywords_is_fatal)
475 	{
476 	  fprintf(out, "\tid_keywd err/warn\t\n");
477 	  ++flag_count;
478 	}
479       if (flags.joint_edit)
480 	{
481 	  fprintf(out, "\tjoint edit\t\n");
482 	  ++flag_count;
483 	}
484       if (flags.all_locked)
485 	{
486 	  fprintf(out, "\tlocked releases\ta\n");
487 	  ++flag_count;
488 	}
489       else if (!flags.locked.empty())
490 	{
491 	  fprintf(out, "\tlocked releases\t");
492 	  flags.locked.print(out);
493 	  putc('\n', out);
494 	  ++flag_count;
495 	}
496       print_flag(out, "\tmodule\t%s\n", flags.module, flag_count);
497       print_flag(out, "\tnull delta\t\n", flags.null_deltas, flag_count);
498       print_flag(out, "\tcsect name\t%s\n", flags.user_def, flag_count);
499       print_flag(out, "\ttype\t%s\n", flags.type, flag_count);
500       print_flag(out, "\tvalidate MRs\t%s\n", flags.mr_checker, flag_count);
501 
502       if (!flags.substitued_flag_letters.empty())
503 	{
504 	  ++flag_count;
505 
506 	  // Unusually, Solaris "prt" just launches into the list of
507 	  // expanded keyletters, without a leading flag name.
508 	  fprintf(out, "\t\t");
509 	  (void) print_subsituted_flags_list(out, " ");
510 	  fprintf(out, "\n");
511 	}
512 
513 
514       if (0 == flag_count)
515 	fprintf(out, "\tnone\n");
516     }
517   if (print_desc)
518     {
519       fprintf(out, "\nDescription --\n");
520       print_string_list(out, comments, "\t", "\n", "none");
521       putc('\n', out);
522     }
523 
524   if (print_body)
525     {
526       // seek_to_body() is a non-const member, so we have this
527       // silly workaround.
528       do_print_body(name.c_str(), f, body_offset, out);
529     }
530 
531   return true;
532 }
533 
534 
535 /* Local variables: */
536 /* mode: c++ */
537 /* End: */
538