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