1 /*
2  * bodyio.cc: Part of GNU CSSC.
3  *
4  *  Copyright (C) 1997, 1998, 1999, 2001, 2007, 2008, 2009, 2010, 2011,
5  *  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  *
21  * Code for performing I/O on the body of an SCCS file.
22  * See also sf-admin.cc and encoding.cc.
23  *
24  */
25 
26 #include <config.h>
27 
28 #include <cstdio>
29 #include <cstring>
30 
31 #include "cssc.h"
32 #include "bodyio.h"
33 #include "quit.h"
34 #include "cssc-assert.h"
35 #include "filepos.h"
36 #include "except.h"
37 #include "linebuf.h"
38 #include "ioerr.h"
39 #include "file.h"
40 
41 /* Check if we have exceeded the maximum line length.
42  */
check_line_len(const char * iname,long int len_max,int column,int ch,FILE * in,bool * binary)43 static bool check_line_len(const char *iname,
44 			   long int len_max,
45 			   int column,
46 			   int ch,
47 			   FILE *in,
48 			   bool *binary)
49 {
50   if (0 == len_max || column < len_max)
51     {
52       return true;		// no maximum.
53     }
54   else
55     {
56       ungetc(ch, in);	// push back the character.
57 
58       if (binary_file_creation_allowed())
59 	{
60 	  errormsg("%s: line length exceeds %ld characters, "
61 		   "treating as binary.\n",
62 		   iname, len_max );
63 	  *binary = true;
64 	}
65       else
66 	{
67 	  errormsg("%s: line length exceeds %d characters, "
68 		   "and binary file support is disabled.\n",
69 		   iname, len_max );
70 	}
71       return false;
72     }
73 }
74 
75 
76 
77 
78 /* body_insert_text()
79  *
80  * Insert a file into an SCCS file (e.g. for admin).
81  * return false (and restore initial file positions)
82  * for failure.
83  *
84  * We fail if the input contains a ^A immediately following
85  * a newline (which is special to SCCS), or if the input
86  * does not end with a newline.
87  *
88  *  1) otherwise the control-character (^A) immediately
89  *   following will not be recognised; SCCS utils
90  *   only look for them at the beginning of a line
91  *
92  *  2) many diff(1) programs only cope with text files
93  *   that end with a newline.
94  */
95 bool
body_insert_text(const char iname[],const char oname[],FILE * in,FILE * out,unsigned long int * lines,bool * idkw,bool * binary,bool * io_failure)96 body_insert_text(const char iname[], const char oname[],
97 		 FILE *in, FILE *out,
98 		 unsigned long int *lines,
99 		 bool *idkw,
100 		 bool *binary,
101 		 bool *io_failure)
102 {
103   int ch, last;
104   unsigned long int nl;		// number of lines.
105   const long int len_max = max_sfile_line_len();
106   bool found_id;
107   int column;			// current column in ouutput file.
108 
109   // If we fail, rewind these files to try binary encoding.
110   FilePosSaver o_saver(out);
111 
112   *idkw = found_id = false;
113   nl = 0uL;
114   last = '\n';
115   *io_failure = false;
116 
117   // Make sure we don't already think it is binary -- if so, this
118   // function should never have been called.
119   ASSERT(false == *binary);
120 
121   column = 0;
122   while ( EOF != (ch=getc(in)) )
123     {
124       if (CONFIG_EOL_CHARACTER == ch)
125 	++nl;
126 
127       // check for ^A at start of line.
128       if ('\n' == last)
129 	{
130 	  column = 0;
131 
132 	  if ('\001' == ch)
133 	    {
134 	      errormsg("%s: control character at start of line, "
135 		       "treating as binary.\n",
136 		       iname);
137 	      ungetc(ch, in);	// push back the control character.
138 	      *binary = true;
139 	      return false;	// output file pointer implicitly rewound
140 	    }
141 	}
142       else
143 	{
144 	  ++column;
145 	  if (!check_line_len(iname, len_max, ++column, ch, in, binary))
146 	    {
147 	      return false; // output file pointer implicitly rewound
148 	    }
149 	}
150 
151 
152       // FIXME TODO: if we get "disk full" while trying to write the
153       // body of an SCCS file, we will retry with binary (which of
154       // course uses even more space)
155       if (putc_failed(putc(ch, out)))
156 	{
157 	  errormsg_with_errno("%s: Write error.", oname);
158 	  *io_failure = true;
159 	  return false;
160 	}
161 
162       if (!found_id)		// Check for ID keywords.
163 	{
164 	  if ('%' == last && is_id_keyword_letter(static_cast<char>(ch)))
165 	    {
166 	      const int peek = getc(in);
167 	      if ('%' == peek)
168 		*idkw = found_id = true;
169 	      if (EOF != peek)	// Can't put the genie back in the bottle!
170 		ungetc(peek, in);
171 	    }
172 	}
173 
174       last = ch;
175     }
176 
177   if (ferror(in))
178     {
179       errormsg_with_errno("%s: Read error.", iname);
180       *io_failure = true;
181       return false;
182     }
183 
184 
185   // Make sure the file ended with a newline.
186   if ('\n' != last)
187     {
188       errormsg("%s: no newline at end of file, treating as binary\n",
189 	       iname);
190       *binary = true;
191       return false;		// file pointers implicitly rewound
192     }
193 
194   // Success; do not rewind the output file.
195   o_saver.disarm();
196   *lines = nl;
197   return true;
198 }
199 
200 // Keywords: a file containing the octal characters
201 // 0026 0021 0141 (hex 0x16 0x11 0x61) will produce
202 // begin 0644 x
203 // #%A%A
204 // `
205 // end
206 //
207 //
208 // Stupidly imho, SCCS checks the ENCODED form of the file
209 // in order to support the "no ID keywords" warning.  Still,
210 // at least it doesn't expand those on output!
211 
212 
213 bool
body_insert_binary(const char iname[],const char oname[],FILE * in,FILE * out,unsigned long int * lines,bool * idkw)214 body_insert_binary(const char iname[], const char oname[],
215 		   FILE *in, FILE *out,
216 		   unsigned long int *lines,
217 		   bool *idkw)
218 {
219   const int max_chunk = 45;
220   char inbuf[max_chunk], outbuf[80];
221   unsigned long int nl;
222   size_t len;
223   bool kw;
224   *idkw = kw = false;
225 
226   nl = 0;
227   while ( 0 < (len = fread(inbuf, sizeof(char), max_chunk, in)) )
228     {
229       encode_line(inbuf, outbuf, len); // see encoding.cc.
230 
231       if (!kw)
232 	{
233 	  // For some odd reason, SCCS seems to check
234 	  // the encoded form for ID keywords!  We know
235 	  // that strlen() on the UUENCODEd data is safe.
236 	  if (::check_id_keywords(outbuf, strlen(outbuf)))	// XXX used to check inbuf
237 	    *idkw = kw = true;
238 	}
239 
240       if (fputs_failed(fputs(outbuf, out)))
241 	{
242 	  errormsg_with_errno("%s: Write error.", oname);
243 	  return false;
244 	}
245 
246       ++nl;
247     }
248   // A space character indicates a count of zero bytes and hence
249   // the end of the encoded file.
250   if (fputs_failed(fputs(" \n", out)))
251     {
252       errormsg_with_errno("%s: Write error.", oname);
253       return false;
254     }
255 
256   ++nl;
257 
258   if (ferror(in))
259     {
260       errormsg_with_errno("%s: Read error.", iname);
261       return false;
262     }
263 
264   *lines = nl;
265   return true;
266 }
267 
268 
269 static bool
copy_data(FILE * in,FILE * out)270 copy_data(FILE *in, FILE *out)
271 {
272   char buf[BUFSIZ];
273   size_t n, nout;
274 
275   while ( 0u < (n=fread(buf, 1, BUFSIZ, in)))
276     {
277       nout = fwrite(buf, 1, n, out);
278       if (nout < n)
279 	{
280 	  errormsg_with_errno("copy_data: write error.");
281 	  return false;
282 	}
283     }
284   if (ferror(in))
285     {
286       errormsg_with_errno("copy_data: read error.");
287       return false;
288     }
289   else
290     {
291       return true;		// success
292     }
293 }
294 
295 
296 bool
body_insert(bool * binary,const char iname[],const char oname[],FILE * in,FILE * out,unsigned long int * lines,bool * idkw)297 body_insert(bool *binary,
298 	    const char iname[], const char oname[],
299 	    FILE *in, FILE *out,
300 	    unsigned long int *lines,
301 	    bool *idkw)
302 {
303   // If binary mode has not been forced, try text mode.
304   if (*binary)
305     {
306       return body_insert_binary(iname, oname, in, out, lines, idkw);
307     }
308   else
309     {
310       // body_insert_text() takes care of rewinding the output
311       // file; we may not even be able to rewind the input file.
312       bool io_failure = false;
313       if (body_insert_text(iname, oname, in, out, lines, idkw, binary,
314 			   &io_failure))
315 	{
316 	  return true;		// Success.
317 	}
318       else
319 	{
320 	  if (io_failure)
321 	    return false;
322 
323 	  if (!binary_file_creation_allowed())
324 	    {
325 	      // We can't try again with a binary file, because that
326 	      // feature is disabled.
327 	      return false;
328 	    }
329 
330 	  // It wasn't text after all.  We may be reading from
331 	  // stdin, so we can't seek on it.  But we have
332 	  // the first segment of the file written to the x-file
333 	  // already, and the remainder is still waiting to be
334 	  // read, so we can recover all the data.
335 	  *binary = true;
336 	  FILE *tmp = tmpfile();
337 	  if (tmp)
338 	    {
339 	      bool ret = true;
340 
341 	      try
342 		{
343 		  // Recover the data already written to the output
344 		  // file and then rewind it, so that we can overwrite
345 		  // it with the encoded version.
346 		  FilePosSaver *fp_out = new FilePosSaver(out);
347 
348 		  if (copy_data(out, tmp) && copy_data(in,  tmp))
349 		    {
350 		      delete fp_out;	// rewind the file OUT.
351 		      rewind(tmp);
352 
353 		      ret = body_insert_binary("temporary file",
354 					       oname, tmp, out, lines, idkw);
355 		    }
356 		  else
357 		    {
358 		      ret = false;
359 		    }
360 		}
361 	      catch (CsscException)
362 		{
363 		  fclose(tmp);
364 		  throw;
365 		}
366 
367 	      fclose(tmp);
368 	      return ret;
369 	    }
370 	  else
371 	    {
372 	      errormsg_with_errno("Could not create temporary file\n");
373 	      return false;
374 	    }
375 	}
376     }
377 }
378 
output_body_line_text(FILE * fp,const cssc_linebuf * plb)379 int output_body_line_text(FILE *fp, const cssc_linebuf* plb)
380 {
381   return plb->write(fp) || fputc_failed(fputc('\n', fp));
382 }
383 
output_body_line_binary(FILE * fp,const cssc_linebuf * plb)384 int output_body_line_binary(FILE *fp, const cssc_linebuf* plb)
385 {
386   // Curiously, if the file is encoded, we know that
387   // the encoded form is only about 60 characters
388   // and contains no 8-bit or zero data.
389   size_t n;
390   char outbuf[80];
391 
392   n = decode_line(plb->c_str(), outbuf); // see encoding.cc
393   return fwrite_failed(fwrite(outbuf, sizeof(char), n, fp), n);
394 }
395 
396 
397 int
encode_file(const char * nin,const char * nout)398 encode_file(const char *nin, const char *nout)
399 {
400   int retval = 0;
401 
402   FILE *fin = fopen_as_real_user(nin, "rb");	// binary
403   if (0 == fin)
404     {
405       errormsg_with_errno("Failed to open \"%s\" for reading.\n", nin);
406       return -1;
407     }
408 
409 //  FILE *fout = fopen(nout, "w"); // text
410   FILE *fout = fcreate(nout, CREATE_EXCLUSIVE); // text
411 
412   if (0 == fout)
413     {
414       errormsg_with_errno("Failed to open \"%s\" for writing.\n", nout);
415       retval = -1;
416     }
417   else
418     {
419       try
420 	{
421 	  encode_stream(fin, fout);
422 
423 	  if (ferror(fin) || fclose_failed(fclose(fin)))
424 	    {
425 	      errormsg_with_errno("%s: Read error.\n", nin);
426 	      retval = -1;
427 	    }
428 	  else
429 	    {
430 	      if (ferror(fout) || fclose_failed(fclose(fout)))
431 		{
432 		  errormsg_with_errno("%s: Write error.\n", nout);
433 		  retval = -1;
434 		}
435 	    }
436 	}
437       catch (CsscException)
438 	{
439 	  remove(nout);
440 	  throw;
441 	}
442       if (0 != retval)
443 	remove(nout);
444     }
445   return retval;
446 }
447