tcp: Fix comment, while I'm here.
[dragonfly.git] / usr.bin / patch / inp.c
1 /*-
2  * Copyright 1986, Larry Wall
3  * 
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following condition is met:
6  * 1. Redistributions of source code must retain the above copyright notice,
7  * this condition and the following disclaimer.
8  * 
9  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
10  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
12  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
13  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
14  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
15  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
16  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
17  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
18  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
19  * SUCH DAMAGE.
20  * 
21  * patch - a program to apply diffs to original files
22  *
23  * -C option added in 1998, original code by Marc Espie, based on FreeBSD
24  * behaviour
25  *
26  * $OpenBSD: inp.c,v 1.36 2012/04/10 14:46:34 ajacoutot Exp $
27  * $FreeBSD: head/usr.bin/patch/inp.c 246091 2013-01-29 20:05:16Z delphij $
28  */
29
30 #include <sys/types.h>
31 #include <sys/file.h>
32 #include <sys/stat.h>
33 #include <sys/mman.h>
34
35 #include <ctype.h>
36 #include <libgen.h>
37 #include <limits.h>
38 #include <stddef.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43
44 #include "common.h"
45 #include "util.h"
46 #include "pch.h"
47 #include "inp.h"
48
49
50 /* Input-file-with-indexable-lines abstract type */
51
52 static size_t   i_size;         /* size of the input file */
53 static char     *i_womp;        /* plan a buffer for entire file */
54 static char     **i_ptr;        /* pointers to lines in i_womp */
55 static char     empty_line[] = { '\0' };
56
57 static int      tifd = -1;      /* plan b virtual string array */
58 static char     *tibuf[2];      /* plan b buffers */
59 static LINENUM  tiline[2] = {-1, -1};   /* 1st line in each buffer */
60 static LINENUM  lines_per_buf;  /* how many lines per buffer */
61 static int      tireclen;       /* length of records in tmp file */
62
63 static bool     rev_in_string(const char *);
64 static bool     reallocate_lines(size_t *);
65
66 /* returns false if insufficient memory */
67 static bool     plan_a(const char *);
68
69 static void     plan_b(const char *);
70
71 /* New patch--prepare to edit another file. */
72
73 void
74 re_input(void)
75 {
76         if (using_plan_a) {
77                 free(i_ptr);
78                 i_ptr = NULL;
79                 if (i_womp != NULL) {
80                         munmap(i_womp, i_size);
81                         i_womp = NULL;
82                 }
83                 i_size = 0;
84         } else {
85                 using_plan_a = true;    /* maybe the next one is smaller */
86                 close(tifd);
87                 tifd = -1;
88                 free(tibuf[0]);
89                 free(tibuf[1]);
90                 tibuf[0] = tibuf[1] = NULL;
91                 tiline[0] = tiline[1] = -1;
92                 tireclen = 0;
93         }
94 }
95
96 /* Construct the line index, somehow or other. */
97
98 void
99 scan_input(const char *filename)
100 {
101         if (!plan_a(filename))
102                 plan_b(filename);
103         if (verbose) {
104                 say("Patching file %s using Plan %s...\n", filename,
105                     (using_plan_a ? "A" : "B"));
106         }
107 }
108
109 static bool
110 reallocate_lines(size_t *lines_allocated)
111 {
112         char    **p;
113         size_t  new_size;
114
115         new_size = *lines_allocated * 3 / 2;
116         p = realloc(i_ptr, (new_size + 2) * sizeof(char *));
117         if (p == NULL) {        /* shucks, it was a near thing */
118                 munmap(i_womp, i_size);
119                 i_womp = NULL;
120                 free(i_ptr);
121                 i_ptr = NULL;
122                 *lines_allocated = 0;
123                 return false;
124         }
125         *lines_allocated = new_size;
126         i_ptr = p;
127         return true;
128 }
129
130 /* Try keeping everything in memory. */
131
132 static bool
133 plan_a(const char *filename)
134 {
135         int             ifd, statfailed;
136         char            *p, *s;
137         struct stat     filestat;
138         ptrdiff_t       sz;
139         size_t          i;
140         size_t          iline, lines_allocated;
141
142 #ifdef DEBUGGING
143         if (debug & 8)
144                 return false;
145 #endif
146
147         if (filename == NULL || *filename == '\0')
148                 return false;
149
150         statfailed = stat(filename, &filestat);
151         if (statfailed && ok_to_create_file) {
152                 if (verbose)
153                         say("(Creating file %s...)\n", filename);
154
155                 /*
156                  * in check_patch case, we still display `Creating file' even
157                  * though we're not. The rule is that -C should be as similar
158                  * to normal patch behavior as possible
159                  */
160                 if (check_only)
161                         return true;
162                 makedirs(filename, true);
163                 close(creat(filename, 0666));
164                 statfailed = stat(filename, &filestat);
165         }
166         if (statfailed && check_only)
167                 fatal("%s not found, -C mode, can't probe further\n", filename);
168         /*
169          * For nonexistent or read-only files we no longer attempt to locate
170          * or checkout the file via RCS or SCCS, or do any call to system()
171          * whatsoever.  CVE-2015-1416.
172          */
173         if (statfailed ||
174             /* No one can write to it.  */
175             (filestat.st_mode & 0222) == 0 ||
176             /* I can't write to it.  */
177             ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
178                 if (statfailed)
179                         fatal("can't find %s\n", filename);
180         }
181         filemode = filestat.st_mode;
182         if (!S_ISREG(filemode))
183                 fatal("%s is not a normal file--can't patch\n", filename);
184         if ((uint64_t)filestat.st_size > SIZE_MAX) {
185                 say("block too large to mmap\n");
186                 return false;
187         }
188         i_size = (size_t)filestat.st_size;
189         if (out_of_mem) {
190                 set_hunkmax();  /* make sure dynamic arrays are allocated */
191                 out_of_mem = false;
192                 return false;   /* force plan b because plan a bombed */
193         }
194         if ((ifd = open(filename, O_RDONLY)) < 0)
195                 pfatal("can't open file %s", filename);
196
197         if (i_size) {
198                 i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0);
199                 if (i_womp == MAP_FAILED) {
200                         perror("mmap failed");
201                         i_womp = NULL;
202                         close(ifd);
203                         return false;
204                 }
205         } else {
206                 i_womp = NULL;
207         }
208
209         close(ifd);
210         if (i_size)
211                 madvise(i_womp, i_size, MADV_SEQUENTIAL);
212
213         /* estimate the number of lines */
214         lines_allocated = i_size / 25;
215         if (lines_allocated < 100)
216                 lines_allocated = 100;
217
218         if (!reallocate_lines(&lines_allocated))
219                 return false;
220
221         /* now scan the buffer and build pointer array */
222         iline = 1;
223         i_ptr[iline] = i_womp;
224         /* test for NUL too, to maintain the behavior of the original code */
225         for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) {
226                 if (*s == '\n') {
227                         if (iline == lines_allocated) {
228                                 if (!reallocate_lines(&lines_allocated))
229                                         return false;
230                         }
231                         /* these are NOT NUL terminated */
232                         i_ptr[++iline] = s + 1;
233                 }
234         }
235         /* if the last line contains no EOL, append one */
236         if (i_size > 0 && i_womp[i_size - 1] != '\n') {
237                 last_line_missing_eol = true;
238                 /* fix last line */
239                 sz = s - i_ptr[iline];
240                 p = malloc(sz + 1);
241                 if (p == NULL) {
242                         free(i_ptr);
243                         i_ptr = NULL;
244                         munmap(i_womp, i_size);
245                         i_womp = NULL;
246                         return false;
247                 }
248
249                 memcpy(p, i_ptr[iline], sz);
250                 p[sz] = '\n';
251                 i_ptr[iline] = p;
252                 /* count the extra line and make it point to some valid mem */
253                 i_ptr[++iline] = empty_line;
254         } else
255                 last_line_missing_eol = false;
256
257         input_lines = iline - 1;
258
259         /* now check for revision, if any */
260
261         if (revision != NULL) {
262                 if (!rev_in_string(i_womp)) {
263                         if (force) {
264                                 if (verbose)
265                                         say("Warning: this file doesn't appear "
266                                             "to be the %s version--patching anyway.\n",
267                                             revision);
268                         } else if (batch) {
269                                 fatal("this file doesn't appear to be the "
270                                     "%s version--aborting.\n",
271                                     revision);
272                         } else {
273                                 ask("This file doesn't appear to be the "
274                                     "%s version--patch anyway? [n] ",
275                                     revision);
276                                 if (*buf != 'y')
277                                         fatal("aborted\n");
278                         }
279                 } else if (verbose)
280                         say("Good.  This file appears to be the %s version.\n",
281                             revision);
282         }
283         return true;            /* plan a will work */
284 }
285
286 /* Keep (virtually) nothing in memory. */
287
288 static void
289 plan_b(const char *filename)
290 {
291         FILE    *ifp;
292         size_t  i = 0, j, maxlen = 1;
293         char    *p;
294         bool    found_revision = (revision == NULL);
295
296         using_plan_a = false;
297         if ((ifp = fopen(filename, "r")) == NULL)
298                 pfatal("can't open file %s", filename);
299         unlink(TMPINNAME);
300         if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
301                 pfatal("can't open file %s", TMPINNAME);
302         while (fgets(buf, buf_size, ifp) != NULL) {
303                 if (revision != NULL && !found_revision && rev_in_string(buf))
304                         found_revision = true;
305                 if ((i = strlen(buf)) > maxlen)
306                         maxlen = i;     /* find longest line */
307         }
308         last_line_missing_eol = i > 0 && buf[i - 1] != '\n';
309         if (last_line_missing_eol && maxlen == i)
310                 maxlen++;
311
312         if (revision != NULL) {
313                 if (!found_revision) {
314                         if (force) {
315                                 if (verbose)
316                                         say("Warning: this file doesn't appear "
317                                             "to be the %s version--patching anyway.\n",
318                                             revision);
319                         } else if (batch) {
320                                 fatal("this file doesn't appear to be the "
321                                     "%s version--aborting.\n",
322                                     revision);
323                         } else {
324                                 ask("This file doesn't appear to be the %s "
325                                     "version--patch anyway? [n] ",
326                                     revision);
327                                 if (*buf != 'y')
328                                         fatal("aborted\n");
329                         }
330                 } else if (verbose)
331                         say("Good.  This file appears to be the %s version.\n",
332                             revision);
333         }
334         fseek(ifp, 0L, SEEK_SET);       /* rewind file */
335         lines_per_buf = BUFFERSIZE / maxlen;
336         tireclen = maxlen;
337         tibuf[0] = malloc(BUFFERSIZE + 1);
338         if (tibuf[0] == NULL)
339                 fatal("out of memory\n");
340         tibuf[1] = malloc(BUFFERSIZE + 1);
341         if (tibuf[1] == NULL)
342                 fatal("out of memory\n");
343         for (i = 1;; i++) {
344                 p = tibuf[0] + maxlen * (i % lines_per_buf);
345                 if (i % lines_per_buf == 0)     /* new block */
346                         if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
347                                 pfatal("can't write temp file");
348                 if (fgets(p, maxlen + 1, ifp) == NULL) {
349                         input_lines = i - 1;
350                         if (i % lines_per_buf != 0)
351                                 if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
352                                         pfatal("can't write temp file");
353                         break;
354                 }
355                 j = strlen(p);
356                 /* These are '\n' terminated strings, so no need to add a NUL */
357                 if (j == 0 || p[j - 1] != '\n')
358                         p[j] = '\n';
359         }
360         fclose(ifp);
361         close(tifd);
362         if ((tifd = open(TMPINNAME, O_RDONLY)) < 0)
363                 pfatal("can't reopen file %s", TMPINNAME);
364 }
365
366 /*
367  * Fetch a line from the input file, \n terminated, not necessarily \0.
368  */
369 char *
370 ifetch(LINENUM line, int whichbuf)
371 {
372         if (line < 1 || line > input_lines) {
373                 if (warn_on_invalid_line) {
374                         say("No such line %ld in input file, ignoring\n", line);
375                         warn_on_invalid_line = false;
376                 }
377                 return NULL;
378         }
379         if (using_plan_a)
380                 return i_ptr[line];
381         else {
382                 LINENUM offline = line % lines_per_buf;
383                 LINENUM baseline = line - offline;
384
385                 if (tiline[0] == baseline)
386                         whichbuf = 0;
387                 else if (tiline[1] == baseline)
388                         whichbuf = 1;
389                 else {
390                         tiline[whichbuf] = baseline;
391
392                         if (lseek(tifd, (off_t) (baseline / lines_per_buf *
393                             BUFFERSIZE), SEEK_SET) < 0)
394                                 pfatal("cannot seek in the temporary input file");
395
396                         if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
397                                 pfatal("error reading tmp file %s", TMPINNAME);
398                 }
399                 return tibuf[whichbuf] + (tireclen * offline);
400         }
401 }
402
403 /*
404  * True if the string argument contains the revision number we want.
405  */
406 static bool
407 rev_in_string(const char *string)
408 {
409         const char      *s;
410         size_t          patlen;
411
412         if (revision == NULL)
413                 return true;
414         patlen = strlen(revision);
415         if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen]))
416                 return true;
417         for (s = string; *s; s++) {
418                 if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) &&
419                     isspace((unsigned char)s[patlen + 1])) {
420                         return true;
421                 }
422         }
423         return false;
424 }