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