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