1/* inputting files to be patched */
2
3/* $Id: inp.c 8008 2004-06-16 21:22:10Z korli $ */
4
5/* Copyright 1986, 1988 Larry Wall
6   Copyright 1991, 1992-1993, 1997-1998, 1999 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 2, or (at your option)
11   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; see the file COPYING.
20   If not, write to the Free Software Foundation,
21   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22
23#define XTERN extern
24#include <common.h>
25#include <backupfile.h>
26#include <pch.h>
27#include <quotearg.h>
28#include <util.h>
29#include <xalloc.h>
30#undef XTERN
31#define XTERN
32#include <inp.h>
33
34/* Input-file-with-indexable-lines abstract type */
35
36static char *i_buffer;			/* plan A buffer */
37static char const **i_ptr;		/* pointers to lines in plan A buffer */
38
39static size_t tibufsize;		/* size of plan b buffers */
40#ifndef TIBUFSIZE_MINIMUM
41#define TIBUFSIZE_MINIMUM (8 * 1024)	/* minimum value for tibufsize */
42#endif
43static int tifd = -1;			/* plan b virtual string array */
44static char *tibuf[2];			/* plan b buffers */
45static LINENUM tiline[2] = {-1, -1};	/* 1st line in each buffer */
46static LINENUM lines_per_buf;		/* how many lines per buffer */
47static size_t tireclen;			/* length of records in tmp file */
48static size_t last_line_size;		/* size of last input line */
49
50static bool plan_a PARAMS ((char const *));/* yield FALSE if memory runs out */
51static void plan_b PARAMS ((char const *));
52static void report_revision PARAMS ((int));
53static void too_many_lines PARAMS ((char const *)) __attribute__((noreturn));
54
55/* New patch--prepare to edit another file. */
56
57void
58re_input (void)
59{
60    if (using_plan_a) {
61      if (i_buffer)
62	{
63	  free (i_buffer);
64	  i_buffer = 0;
65	  free (i_ptr);
66	}
67    }
68    else {
69	close (tifd);
70	tifd = -1;
71	if (tibuf[0])
72	  {
73	    free (tibuf[0]);
74	    tibuf[0] = 0;
75	  }
76	tiline[0] = tiline[1] = -1;
77	tireclen = 0;
78    }
79}
80
81/* Construct the line index, somehow or other. */
82
83void
84scan_input (char *filename)
85{
86    using_plan_a = ! (debug & 16) && plan_a (filename);
87    if (!using_plan_a)
88	plan_b(filename);
89
90    if (verbosity != SILENT)
91      {
92	filename = quotearg (filename);
93
94	if (verbosity == VERBOSE)
95	  say ("Patching file %s using Plan %s...\n",
96	       filename, using_plan_a ? "A" : "B");
97	else
98	  say ("patching file %s\n", filename);
99      }
100}
101
102/* Report whether a desired revision was found.  */
103
104static void
105report_revision (int found_revision)
106{
107  revision = quotearg (revision);
108
109  if (found_revision)
110    {
111      if (verbosity == VERBOSE)
112	say ("Good.  This file appears to be the %s version.\n", revision);
113    }
114  else if (force)
115    {
116      if (verbosity != SILENT)
117	say ("Warning: this file doesn't appear to be the %s version -- patching anyway.\n",
118	     revision);
119    }
120  else if (batch)
121    fatal ("This file doesn't appear to be the %s version -- aborting.",
122	   revision);
123  else
124    {
125      ask ("This file doesn't appear to be the %s version -- patch anyway? [n] ",
126	   revision);
127      if (*buf != 'y')
128	fatal ("aborted");
129    }
130}
131
132
133static void
134too_many_lines (char const *filename)
135{
136  fatal ("File %s has too many lines", quotearg (filename));
137}
138
139
140void
141get_input_file (char const *filename, char const *outname)
142{
143    int elsewhere = strcmp (filename, outname);
144    char const *cs;
145    char *diffbuf;
146    char *getbuf;
147
148    if (inerrno == -1)
149      inerrno = stat (inname, &instat) == 0 ? 0 : errno;
150
151    /* Perhaps look for RCS or SCCS versions.  */
152    if (patch_get
153	&& invc != 0
154	&& (inerrno
155	    || (! elsewhere
156		&& (/* No one can write to it.  */
157		    (instat.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) == 0
158		    /* Only the owner (who's not me) can write to it.  */
159		    || ((instat.st_mode & (S_IWGRP|S_IWOTH)) == 0
160			&& instat.st_uid != geteuid ()))))
161	&& (invc = !! (cs = (version_controller
162			     (filename, elsewhere,
163			      inerrno ? (struct stat *) 0 : &instat,
164			      &getbuf, &diffbuf))))) {
165
166	    if (!inerrno) {
167		if (!elsewhere
168		    && (instat.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) != 0)
169		    /* Somebody can write to it.  */
170		  fatal ("File %s seems to be locked by somebody else under %s",
171			 quotearg (filename), cs);
172		if (diffbuf)
173		  {
174		    /* It might be checked out unlocked.  See if it's safe to
175		       check out the default version locked.  */
176
177		    if (verbosity == VERBOSE)
178		      say ("Comparing file %s to default %s version...\n",
179			   quotearg (filename), cs);
180
181		    if (systemic (diffbuf) != 0)
182		      {
183			say ("warning: Patching file %s, which does not match default %s version\n",
184			     quotearg (filename), cs);
185			cs = 0;
186		      }
187		  }
188	    }
189
190	    if (cs && version_get (filename, cs, ! inerrno, elsewhere, getbuf,
191				   &instat))
192	      inerrno = 0;
193
194	    free (getbuf);
195	    if (diffbuf)
196	      free (diffbuf);
197
198    } else if (inerrno && !pch_says_nonexistent (reverse))
199      {
200	errno = inerrno;
201	pfatal ("Can't find file %s", quotearg (filename));
202      }
203
204    if (inerrno)
205      {
206	instat.st_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
207	instat.st_size = 0;
208      }
209    else if (! S_ISREG (instat.st_mode))
210      fatal ("File %s is not a regular file -- can't patch",
211	     quotearg (filename));
212}
213
214
215/* Try keeping everything in memory. */
216
217static bool
218plan_a (char const *filename)
219{
220  register char const *s;
221  register char const *lim;
222  register char const **ptr;
223  register char *buffer;
224  register LINENUM iline;
225  size_t size = instat.st_size;
226
227  /* Fail if the file size doesn't fit in a size_t,
228     or if storage isn't available.  */
229  if (! (size == instat.st_size
230	 && (buffer = malloc (size ? size : (size_t) 1))))
231    return FALSE;
232
233  /* Read the input file, but don't bother reading it if it's empty.
234     When creating files, the files do not actually exist.  */
235  if (size)
236    {
237      int ifd = open (filename, O_RDONLY|binary_transput);
238      size_t buffered = 0, n;
239      if (ifd < 0)
240	pfatal ("can't open file %s", quotearg (filename));
241
242      while (size - buffered != 0)
243	{
244	  n = read (ifd, buffer + buffered, size - buffered);
245	  if (n == 0)
246	    {
247	      /* Some non-POSIX hosts exaggerate st_size in text mode;
248		 or the file may have shrunk!  */
249	      size = buffered;
250	      break;
251	    }
252	  if (n == (size_t) -1)
253	    {
254	      /* Perhaps size is too large for this host.  */
255	      close (ifd);
256	      free (buffer);
257	      return FALSE;
258	    }
259	  buffered += n;
260	}
261
262      if (close (ifd) != 0)
263	read_fatal ();
264    }
265
266  /* Scan the buffer and build array of pointers to lines.  */
267  lim = buffer + size;
268  iline = 3; /* 1 unused, 1 for SOF, 1 for EOF if last line is incomplete */
269  for (s = buffer;  (s = (char *) memchr (s, '\n', lim - s));  s++)
270    if (++iline < 0)
271      too_many_lines (filename);
272  if (! (iline == (size_t) iline
273	 && (size_t) iline * sizeof *ptr / sizeof *ptr == (size_t) iline
274	 && (ptr = (char const **) malloc ((size_t) iline * sizeof *ptr))))
275    {
276      free (buffer);
277      return FALSE;
278    }
279  iline = 0;
280  for (s = buffer;  ;  s++)
281    {
282      ptr[++iline] = s;
283      if (! (s = (char *) memchr (s, '\n', lim - s)))
284	break;
285    }
286  if (size && lim[-1] != '\n')
287    ptr[++iline] = lim;
288  input_lines = iline - 1;
289
290  if (revision)
291    {
292      char const *rev = revision;
293      int rev0 = rev[0];
294      int found_revision = 0;
295      size_t revlen = strlen (rev);
296
297      if (revlen <= size)
298	{
299	  char const *limrev = lim - revlen;
300
301	  for (s = buffer;  (s = (char *) memchr (s, rev0, limrev - s));  s++)
302	    if (memcmp (s, rev, revlen) == 0
303		&& (s == buffer || ISSPACE ((unsigned char) s[-1]))
304		&& (s + 1 == limrev || ISSPACE ((unsigned char) s[revlen])))
305	      {
306		found_revision = 1;
307		break;
308	      }
309	}
310
311      report_revision (found_revision);
312    }
313
314  /* Plan A will work.  */
315  i_buffer = buffer;
316  i_ptr = ptr;
317  return TRUE;
318}
319
320/* Keep (virtually) nothing in memory. */
321
322static void
323plan_b (char const *filename)
324{
325  register FILE *ifp;
326  register int c;
327  register size_t len;
328  register size_t maxlen;
329  register int found_revision;
330  register size_t i;
331  register char const *rev;
332  register size_t revlen;
333  register LINENUM line = 1;
334  int exclusive;
335
336  if (instat.st_size == 0)
337    filename = NULL_DEVICE;
338  if (! (ifp = fopen (filename, binary_transput ? "rb" : "r")))
339    pfatal ("Can't open file %s", quotearg (filename));
340  exclusive = TMPINNAME_needs_removal ? 0 : O_EXCL;
341  TMPINNAME_needs_removal = 1;
342  tifd = create_file (TMPINNAME, O_RDWR | O_BINARY | exclusive, (mode_t) 0);
343  i = 0;
344  len = 0;
345  maxlen = 1;
346  rev = revision;
347  found_revision = !rev;
348  revlen = rev ? strlen (rev) : 0;
349
350  while ((c = getc (ifp)) != EOF)
351    {
352      len++;
353
354      if (c == '\n')
355	{
356	  if (++line < 0)
357	    too_many_lines (filename);
358	  if (maxlen < len)
359	      maxlen = len;
360	  len = 0;
361	}
362
363      if (!found_revision)
364	{
365	  if (i == revlen)
366	    {
367	      found_revision = ISSPACE ((unsigned char) c);
368	      i = (size_t) -1;
369	    }
370	  else if (i != (size_t) -1)
371	    i = rev[i]==c ? i + 1 : (size_t) -1;
372
373	  if (i == (size_t) -1  &&  ISSPACE ((unsigned char) c))
374	    i = 0;
375	}
376    }
377
378  if (revision)
379    report_revision (found_revision);
380  Fseek (ifp, (off_t) 0, SEEK_SET);		/* rewind file */
381  for (tibufsize = TIBUFSIZE_MINIMUM;  tibufsize < maxlen;  tibufsize <<= 1)
382    continue;
383  lines_per_buf = tibufsize / maxlen;
384  tireclen = maxlen;
385  tibuf[0] = xmalloc (2 * tibufsize);
386  tibuf[1] = tibuf[0] + tibufsize;
387
388  for (line = 1; ; line++)
389    {
390      char *p = tibuf[0] + maxlen * (line % lines_per_buf);
391      char const *p0 = p;
392      if (! (line % lines_per_buf))	/* new block */
393	if (write (tifd, tibuf[0], tibufsize) != tibufsize)
394	  write_fatal ();
395      if ((c = getc (ifp)) == EOF)
396	break;
397
398      for (;;)
399	{
400	  *p++ = c;
401	  if (c == '\n')
402	    {
403	      last_line_size = p - p0;
404	      break;
405	    }
406
407	  if ((c = getc (ifp)) == EOF)
408	    {
409	      last_line_size = p - p0;
410	      line++;
411	      goto EOF_reached;
412	    }
413	}
414    }
415 EOF_reached:
416  if (ferror (ifp)  ||  fclose (ifp) != 0)
417    read_fatal ();
418
419  if (line % lines_per_buf  !=  0)
420    if (write (tifd, tibuf[0], tibufsize) != tibufsize)
421      write_fatal ();
422  input_lines = line - 1;
423}
424
425/* Fetch a line from the input file.
426   WHICHBUF is ignored when the file is in memory.  */
427
428char const *
429ifetch (LINENUM line, int whichbuf, size_t *psize)
430{
431    register char const *q;
432    register char const *p;
433
434    if (line < 1 || line > input_lines) {
435	*psize = 0;
436	return "";
437    }
438    if (using_plan_a) {
439	p = i_ptr[line];
440	*psize = i_ptr[line + 1] - p;
441	return p;
442    } else {
443	LINENUM offline = line % lines_per_buf;
444	LINENUM baseline = line - offline;
445
446	if (tiline[0] == baseline)
447	    whichbuf = 0;
448	else if (tiline[1] == baseline)
449	    whichbuf = 1;
450	else {
451	    tiline[whichbuf] = baseline;
452	    if (lseek (tifd, (off_t) (baseline/lines_per_buf * tibufsize),
453		       SEEK_SET) == -1
454		|| read (tifd, tibuf[whichbuf], tibufsize) < 0)
455	      read_fatal ();
456	}
457	p = tibuf[whichbuf] + (tireclen*offline);
458	if (line == input_lines)
459	    *psize = last_line_size;
460	else {
461	    for (q = p;  *q++ != '\n';  )
462		continue;
463	    *psize = q - p;
464	}
465	return p;
466    }
467}
468