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