1 /* $OpenBSD: inp.c,v 1.44 2015/07/26 14:32:19 millert 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_allocated) 110 { 111 char **p; 112 size_t new_size; 113 114 new_size = *lines_allocated * 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_allocated = 0; 122 return false; 123 } 124 *lines_allocated = 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 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 close(creat(filename, 0666)); 163 statfailed = stat(filename, &filestat); 164 } 165 if (statfailed) 166 fatal("can't find %s\n", filename); 167 filemode = filestat.st_mode; 168 if (!S_ISREG(filemode)) 169 fatal("%s is not a normal file--can't patch\n", filename); 170 i_size = filestat.st_size; 171 if (out_of_mem) { 172 set_hunkmax(); /* make sure dynamic arrays are allocated */ 173 out_of_mem = false; 174 return false; /* force plan b because plan a bombed */ 175 } 176 if (i_size > SIZE_MAX) { 177 say("block too large to mmap\n"); 178 return false; 179 } 180 if ((ifd = open(filename, O_RDONLY)) < 0) 181 pfatal("can't open file %s", filename); 182 183 if (i_size) { 184 i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0); 185 if (i_womp == MAP_FAILED) { 186 perror("mmap failed"); 187 i_womp = NULL; 188 close(ifd); 189 return false; 190 } 191 } else { 192 i_womp = NULL; 193 } 194 195 close(ifd); 196 if (i_size) 197 madvise(i_womp, i_size, MADV_SEQUENTIAL); 198 199 /* estimate the number of lines */ 200 lines_allocated = i_size / 25; 201 if (lines_allocated < 100) 202 lines_allocated = 100; 203 204 if (!reallocate_lines(&lines_allocated)) 205 return false; 206 207 /* now scan the buffer and build pointer array */ 208 iline = 1; 209 i_ptr[iline] = i_womp; 210 /* test for NUL too, to maintain the behavior of the original code */ 211 for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) { 212 if (*s == '\n') { 213 if (iline == lines_allocated) { 214 if (!reallocate_lines(&lines_allocated)) 215 return false; 216 } 217 /* these are NOT NUL terminated */ 218 i_ptr[++iline] = s + 1; 219 } 220 } 221 /* if the last line contains no EOL, append one */ 222 if (i_size > 0 && i_womp[i_size - 1] != '\n') { 223 last_line_missing_eol = true; 224 /* fix last line */ 225 sz = s - i_ptr[iline]; 226 p = malloc(sz + 1); 227 if (p == NULL) { 228 free(i_ptr); 229 i_ptr = NULL; 230 munmap(i_womp, i_size); 231 i_womp = NULL; 232 return false; 233 } 234 235 memcpy(p, i_ptr[iline], sz); 236 p[sz] = '\n'; 237 i_ptr[iline] = p; 238 /* count the extra line and make it point to some valid mem */ 239 i_ptr[++iline] = ""; 240 } else 241 last_line_missing_eol = false; 242 243 input_lines = iline - 1; 244 245 /* now check for revision, if any */ 246 247 if (revision != NULL) { 248 if (i_womp == NULL || !rev_in_string(i_womp)) { 249 if (force) { 250 if (verbose) 251 say("Warning: this file doesn't appear " 252 "to be the %s version--patching anyway.\n", 253 revision); 254 } else if (batch) { 255 fatal("this file doesn't appear to be the " 256 "%s version--aborting.\n", 257 revision); 258 } else { 259 ask("This file doesn't appear to be the " 260 "%s version--patch anyway? [n] ", 261 revision); 262 if (*buf != 'y') 263 fatal("aborted\n"); 264 } 265 } else if (verbose) 266 say("Good. This file appears to be the %s version.\n", 267 revision); 268 } 269 return true; /* plan a will work */ 270 } 271 272 /* Keep (virtually) nothing in memory. */ 273 274 static void 275 plan_b(const char *filename) 276 { 277 FILE *ifp; 278 size_t i = 0, j, len, maxlen = 1; 279 char *lbuf = NULL, *p; 280 bool found_revision = (revision == NULL); 281 282 using_plan_a = false; 283 if ((ifp = fopen(filename, "r")) == NULL) 284 pfatal("can't open file %s", filename); 285 (void) unlink(TMPINNAME); 286 if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0) 287 pfatal("can't open file %s", TMPINNAME); 288 while ((p = fgetln(ifp, &len)) != NULL) { 289 if (p[len - 1] == '\n') 290 p[len - 1] = '\0'; 291 else { 292 /* EOF without EOL, copy and add the NUL */ 293 if ((lbuf = malloc(len + 1)) == NULL) 294 fatal("out of memory\n"); 295 memcpy(lbuf, p, len); 296 lbuf[len] = '\0'; 297 p = lbuf; 298 299 last_line_missing_eol = true; 300 len++; 301 } 302 if (revision != NULL && !found_revision && rev_in_string(p)) 303 found_revision = true; 304 if (len > maxlen) 305 maxlen = len; /* find longest line */ 306 } 307 free(lbuf); 308 if (ferror(ifp)) 309 pfatal("can't read file %s", filename); 310 311 if (revision != NULL) { 312 if (!found_revision) { 313 if (force) { 314 if (verbose) 315 say("Warning: this file doesn't appear " 316 "to be the %s version--patching anyway.\n", 317 revision); 318 } else if (batch) { 319 fatal("this file doesn't appear to be the " 320 "%s version--aborting.\n", 321 revision); 322 } else { 323 ask("This file doesn't appear to be the %s " 324 "version--patch anyway? [n] ", 325 revision); 326 if (*buf != 'y') 327 fatal("aborted\n"); 328 } 329 } else if (verbose) 330 say("Good. This file appears to be the %s version.\n", 331 revision); 332 } 333 fseek(ifp, 0L, SEEK_SET); /* rewind file */ 334 tireclen = maxlen; 335 tibuflen = maxlen > BUFFERSIZE ? maxlen : BUFFERSIZE; 336 lines_per_buf = tibuflen / maxlen; 337 tibuf[0] = malloc(tibuflen + 1); 338 if (tibuf[0] == NULL) 339 fatal("out of memory\n"); 340 tibuf[1] = malloc(tibuflen + 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], tibuflen) != 347 (ssize_t) tibuflen) 348 pfatal("can't write temp file"); 349 if (fgets(p, maxlen + 1, ifp) == NULL) { 350 input_lines = i - 1; 351 if (i % lines_per_buf != 0) 352 if (write(tifd, tibuf[0], tibuflen) != 353 (ssize_t) tibuflen) 354 pfatal("can't write temp file"); 355 break; 356 } 357 j = strlen(p); 358 /* These are '\n' terminated strings, so no need to add a NUL */ 359 if (j == 0 || p[j - 1] != '\n') 360 p[j] = '\n'; 361 } 362 fclose(ifp); 363 close(tifd); 364 if ((tifd = open(TMPINNAME, O_RDONLY)) < 0) 365 pfatal("can't reopen file %s", TMPINNAME); 366 } 367 368 /* 369 * Fetch a line from the input file, \n terminated, not necessarily \0. 370 */ 371 char * 372 ifetch(LINENUM line, int whichbuf) 373 { 374 if (line < 1 || line > input_lines) { 375 if (warn_on_invalid_line) { 376 say("No such line %ld in input file, ignoring\n", line); 377 warn_on_invalid_line = false; 378 } 379 return NULL; 380 } 381 if (using_plan_a) 382 return i_ptr[line]; 383 else { 384 LINENUM offline = line % lines_per_buf; 385 LINENUM baseline = line - offline; 386 387 if (tiline[0] == baseline) 388 whichbuf = 0; 389 else if (tiline[1] == baseline) 390 whichbuf = 1; 391 else { 392 tiline[whichbuf] = baseline; 393 394 if (lseek(tifd, (off_t) (baseline / lines_per_buf * 395 tibuflen), SEEK_SET) < 0) 396 pfatal("cannot seek in the temporary input file"); 397 398 if (read(tifd, tibuf[whichbuf], tibuflen) 399 != (ssize_t) tibuflen) 400 pfatal("error reading tmp file %s", TMPINNAME); 401 } 402 return tibuf[whichbuf] + (tireclen * offline); 403 } 404 } 405 406 /* 407 * True if the string argument contains the revision number we want. 408 */ 409 static bool 410 rev_in_string(const char *string) 411 { 412 const char *s; 413 size_t patlen; 414 415 if (revision == NULL) 416 return true; 417 patlen = strlen(revision); 418 if (strnEQ(string, revision, patlen) && 419 isspace((unsigned char)string[patlen])) 420 return true; 421 for (s = string; *s; s++) { 422 if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) && 423 isspace((unsigned char)s[patlen + 1])) { 424 return true; 425 } 426 } 427 return false; 428 } 429