inp.c revision 246074
1246074Sgabor/* $FreeBSD: head/usr.bin/patch/inp.c 246074 2013-01-29 17:03:18Z gabor $ */ 2246074Sgabor/*- 3246074Sgabor * 4246074Sgabor * Copyright 1986, Larry Wall 5246074Sgabor * 6246074Sgabor * Redistribution and use in source and binary forms, with or without 7246074Sgabor * modification, are permitted provided that the following condition is met: 8246074Sgabor * 1. Redistributions of source code must retain the above copyright notice, 9246074Sgabor * this condition and the following disclaimer. 10246074Sgabor * 11246074Sgabor * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY 12246074Sgabor * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 13246074Sgabor * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 14246074Sgabor * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 15246074Sgabor * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 16246074Sgabor * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 17246074Sgabor * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 18246074Sgabor * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 19246074Sgabor * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 20246074Sgabor * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 21246074Sgabor * SUCH DAMAGE. 22246074Sgabor * 23246074Sgabor * patch - a program to apply diffs to original files 24246074Sgabor * 25246074Sgabor * -C option added in 1998, original code by Marc Espie, based on FreeBSD 26246074Sgabor * behaviour 27246074Sgabor * 28246074Sgabor * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $ 29246074Sgabor */ 30246074Sgabor 31246074Sgabor#include <sys/types.h> 32246074Sgabor#include <sys/file.h> 33246074Sgabor#include <sys/stat.h> 34246074Sgabor#include <sys/mman.h> 35246074Sgabor 36246074Sgabor#include <ctype.h> 37246074Sgabor#include <libgen.h> 38246074Sgabor#include <limits.h> 39246074Sgabor#include <stddef.h> 40246074Sgabor#include <stdio.h> 41246074Sgabor#include <stdlib.h> 42246074Sgabor#include <string.h> 43246074Sgabor#include <unistd.h> 44246074Sgabor 45246074Sgabor#include "common.h" 46246074Sgabor#include "util.h" 47246074Sgabor#include "pch.h" 48246074Sgabor#include "inp.h" 49246074Sgabor 50246074Sgabor 51246074Sgabor/* Input-file-with-indexable-lines abstract type */ 52246074Sgabor 53246074Sgaborstatic size_t i_size; /* size of the input file */ 54246074Sgaborstatic char *i_womp; /* plan a buffer for entire file */ 55246074Sgaborstatic char **i_ptr; /* pointers to lines in i_womp */ 56246074Sgaborstatic char empty_line[] = { '\0' }; 57246074Sgabor 58246074Sgaborstatic int tifd = -1; /* plan b virtual string array */ 59246074Sgaborstatic char *tibuf[2]; /* plan b buffers */ 60246074Sgaborstatic LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */ 61246074Sgaborstatic LINENUM lines_per_buf; /* how many lines per buffer */ 62246074Sgaborstatic int tireclen; /* length of records in tmp file */ 63246074Sgabor 64246074Sgaborstatic bool rev_in_string(const char *); 65246074Sgaborstatic bool reallocate_lines(size_t *); 66246074Sgabor 67246074Sgabor/* returns false if insufficient memory */ 68246074Sgaborstatic bool plan_a(const char *); 69246074Sgabor 70246074Sgaborstatic void plan_b(const char *); 71246074Sgabor 72246074Sgabor/* New patch--prepare to edit another file. */ 73246074Sgabor 74246074Sgaborvoid 75246074Sgaborre_input(void) 76246074Sgabor{ 77246074Sgabor if (using_plan_a) { 78246074Sgabor free(i_ptr); 79246074Sgabor i_ptr = NULL; 80246074Sgabor if (i_womp != NULL) { 81246074Sgabor munmap(i_womp, i_size); 82246074Sgabor i_womp = NULL; 83246074Sgabor } 84246074Sgabor i_size = 0; 85246074Sgabor } else { 86246074Sgabor using_plan_a = true; /* maybe the next one is smaller */ 87246074Sgabor close(tifd); 88246074Sgabor tifd = -1; 89246074Sgabor free(tibuf[0]); 90246074Sgabor free(tibuf[1]); 91246074Sgabor tibuf[0] = tibuf[1] = NULL; 92246074Sgabor tiline[0] = tiline[1] = -1; 93246074Sgabor tireclen = 0; 94246074Sgabor } 95246074Sgabor} 96246074Sgabor 97246074Sgabor/* Construct the line index, somehow or other. */ 98246074Sgabor 99246074Sgaborvoid 100246074Sgaborscan_input(const char *filename) 101246074Sgabor{ 102246074Sgabor if (!plan_a(filename)) 103246074Sgabor plan_b(filename); 104246074Sgabor if (verbose) { 105246074Sgabor say("Patching file %s using Plan %s...\n", filename, 106246074Sgabor (using_plan_a ? "A" : "B")); 107246074Sgabor } 108246074Sgabor} 109246074Sgabor 110246074Sgaborstatic bool 111246074Sgaborreallocate_lines(size_t *lines_allocated) 112246074Sgabor{ 113246074Sgabor char **p; 114246074Sgabor size_t new_size; 115246074Sgabor 116246074Sgabor new_size = *lines_allocated * 3 / 2; 117246074Sgabor p = realloc(i_ptr, (new_size + 2) * sizeof(char *)); 118246074Sgabor if (p == NULL) { /* shucks, it was a near thing */ 119246074Sgabor munmap(i_womp, i_size); 120246074Sgabor i_womp = NULL; 121246074Sgabor free(i_ptr); 122246074Sgabor i_ptr = NULL; 123246074Sgabor *lines_allocated = 0; 124246074Sgabor return false; 125246074Sgabor } 126246074Sgabor *lines_allocated = new_size; 127246074Sgabor i_ptr = p; 128246074Sgabor return true; 129246074Sgabor} 130246074Sgabor 131246074Sgabor/* Try keeping everything in memory. */ 132246074Sgabor 133246074Sgaborstatic bool 134246074Sgaborplan_a(const char *filename) 135246074Sgabor{ 136246074Sgabor int ifd, statfailed; 137246074Sgabor char *p, *s, lbuf[INITLINELEN]; 138246074Sgabor struct stat filestat; 139246074Sgabor ptrdiff_t sz; 140246074Sgabor size_t i; 141246074Sgabor size_t iline, lines_allocated; 142246074Sgabor 143246074Sgabor#ifdef DEBUGGING 144246074Sgabor if (debug & 8) 145246074Sgabor return false; 146246074Sgabor#endif 147246074Sgabor 148246074Sgabor if (filename == NULL || *filename == '\0') 149246074Sgabor return false; 150246074Sgabor 151246074Sgabor statfailed = stat(filename, &filestat); 152246074Sgabor if (statfailed && ok_to_create_file) { 153246074Sgabor if (verbose) 154246074Sgabor say("(Creating file %s...)\n", filename); 155246074Sgabor 156246074Sgabor /* 157246074Sgabor * in check_patch case, we still display `Creating file' even 158246074Sgabor * though we're not. The rule is that -C should be as similar 159246074Sgabor * to normal patch behavior as possible 160246074Sgabor */ 161246074Sgabor if (check_only) 162246074Sgabor return true; 163246074Sgabor makedirs(filename, true); 164246074Sgabor close(creat(filename, 0666)); 165246074Sgabor statfailed = stat(filename, &filestat); 166246074Sgabor } 167246074Sgabor if (statfailed && check_only) 168246074Sgabor fatal("%s not found, -C mode, can't probe further\n", filename); 169246074Sgabor /* For nonexistent or read-only files, look for RCS or SCCS versions. */ 170246074Sgabor if (statfailed || 171246074Sgabor /* No one can write to it. */ 172246074Sgabor (filestat.st_mode & 0222) == 0 || 173246074Sgabor /* I can't write to it. */ 174246074Sgabor ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) { 175246074Sgabor const char *cs = NULL, *filebase, *filedir; 176246074Sgabor struct stat cstat; 177246074Sgabor char *tmp_filename1, *tmp_filename2; 178246074Sgabor 179246074Sgabor tmp_filename1 = strdup(filename); 180246074Sgabor tmp_filename2 = strdup(filename); 181246074Sgabor if (tmp_filename1 == NULL || tmp_filename2 == NULL) 182246074Sgabor fatal("strdupping filename"); 183246074Sgabor filebase = basename(tmp_filename1); 184246074Sgabor filedir = dirname(tmp_filename2); 185246074Sgabor 186246074Sgabor /* Leave room in lbuf for the diff command. */ 187246074Sgabor s = lbuf + 20; 188246074Sgabor 189246074Sgabor#define try(f, a1, a2, a3) \ 190246074Sgabor (snprintf(s, buf_size - 20, f, a1, a2, a3), stat(s, &cstat) == 0) 191246074Sgabor 192246074Sgabor if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || 193246074Sgabor try("%s/RCS/%s%s", filedir, filebase, "") || 194246074Sgabor try("%s/%s%s", filedir, filebase, RCSSUFFIX)) { 195246074Sgabor snprintf(buf, buf_size, CHECKOUT, filename); 196246074Sgabor snprintf(lbuf, sizeof lbuf, RCSDIFF, filename); 197246074Sgabor cs = "RCS"; 198246074Sgabor } else if (try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || 199246074Sgabor try("%s/%s%s", filedir, SCCSPREFIX, filebase)) { 200246074Sgabor snprintf(buf, buf_size, GET, s); 201246074Sgabor snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename); 202246074Sgabor cs = "SCCS"; 203246074Sgabor } else if (statfailed) 204246074Sgabor fatal("can't find %s\n", filename); 205246074Sgabor 206246074Sgabor free(tmp_filename1); 207246074Sgabor free(tmp_filename2); 208246074Sgabor 209246074Sgabor /* 210246074Sgabor * else we can't write to it but it's not under a version 211246074Sgabor * control system, so just proceed. 212246074Sgabor */ 213246074Sgabor if (cs) { 214246074Sgabor if (!statfailed) { 215246074Sgabor if ((filestat.st_mode & 0222) != 0) 216246074Sgabor /* The owner can write to it. */ 217246074Sgabor fatal("file %s seems to be locked " 218246074Sgabor "by somebody else under %s\n", 219246074Sgabor filename, cs); 220246074Sgabor /* 221246074Sgabor * It might be checked out unlocked. See if 222246074Sgabor * it's safe to check out the default version 223246074Sgabor * locked. 224246074Sgabor */ 225246074Sgabor if (verbose) 226246074Sgabor say("Comparing file %s to default " 227246074Sgabor "%s version...\n", 228246074Sgabor filename, cs); 229246074Sgabor if (system(lbuf)) 230246074Sgabor fatal("can't check out file %s: " 231246074Sgabor "differs from default %s version\n", 232246074Sgabor filename, cs); 233246074Sgabor } 234246074Sgabor if (verbose) 235246074Sgabor say("Checking out file %s from %s...\n", 236246074Sgabor filename, cs); 237246074Sgabor if (system(buf) || stat(filename, &filestat)) 238246074Sgabor fatal("can't check out file %s from %s\n", 239246074Sgabor filename, cs); 240246074Sgabor } 241246074Sgabor } 242246074Sgabor filemode = filestat.st_mode; 243246074Sgabor if (!S_ISREG(filemode)) 244246074Sgabor fatal("%s is not a normal file--can't patch\n", filename); 245246074Sgabor if ((uint64_t)filestat.st_size > SIZE_MAX) { 246246074Sgabor say("block too large to mmap\n"); 247246074Sgabor return false; 248246074Sgabor } 249246074Sgabor i_size = (size_t)filestat.st_size; 250246074Sgabor if (out_of_mem) { 251246074Sgabor set_hunkmax(); /* make sure dynamic arrays are allocated */ 252246074Sgabor out_of_mem = false; 253246074Sgabor return false; /* force plan b because plan a bombed */ 254246074Sgabor } 255246074Sgabor if ((ifd = open(filename, O_RDONLY)) < 0) 256246074Sgabor pfatal("can't open file %s", filename); 257246074Sgabor 258246074Sgabor if (i_size) { 259246074Sgabor i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0); 260246074Sgabor if (i_womp == MAP_FAILED) { 261246074Sgabor perror("mmap failed"); 262246074Sgabor i_womp = NULL; 263246074Sgabor close(ifd); 264246074Sgabor return false; 265246074Sgabor } 266246074Sgabor } else { 267246074Sgabor i_womp = NULL; 268246074Sgabor } 269246074Sgabor 270246074Sgabor close(ifd); 271246074Sgabor if (i_size) 272246074Sgabor madvise(i_womp, i_size, MADV_SEQUENTIAL); 273246074Sgabor 274246074Sgabor /* estimate the number of lines */ 275246074Sgabor lines_allocated = i_size / 25; 276246074Sgabor if (lines_allocated < 100) 277246074Sgabor lines_allocated = 100; 278246074Sgabor 279246074Sgabor if (!reallocate_lines(&lines_allocated)) 280246074Sgabor return false; 281246074Sgabor 282246074Sgabor /* now scan the buffer and build pointer array */ 283246074Sgabor iline = 1; 284246074Sgabor i_ptr[iline] = i_womp; 285246074Sgabor /* test for NUL too, to maintain the behavior of the original code */ 286246074Sgabor for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) { 287246074Sgabor if (*s == '\n') { 288246074Sgabor if (iline == lines_allocated) { 289246074Sgabor if (!reallocate_lines(&lines_allocated)) 290246074Sgabor return false; 291246074Sgabor } 292246074Sgabor /* these are NOT NUL terminated */ 293246074Sgabor i_ptr[++iline] = s + 1; 294246074Sgabor } 295246074Sgabor } 296246074Sgabor /* if the last line contains no EOL, append one */ 297246074Sgabor if (i_size > 0 && i_womp[i_size - 1] != '\n') { 298246074Sgabor last_line_missing_eol = true; 299246074Sgabor /* fix last line */ 300246074Sgabor sz = s - i_ptr[iline]; 301246074Sgabor p = malloc(sz + 1); 302246074Sgabor if (p == NULL) { 303246074Sgabor free(i_ptr); 304246074Sgabor i_ptr = NULL; 305246074Sgabor munmap(i_womp, i_size); 306246074Sgabor i_womp = NULL; 307246074Sgabor return false; 308246074Sgabor } 309246074Sgabor 310246074Sgabor memcpy(p, i_ptr[iline], sz); 311246074Sgabor p[sz] = '\n'; 312246074Sgabor i_ptr[iline] = p; 313246074Sgabor /* count the extra line and make it point to some valid mem */ 314246074Sgabor i_ptr[++iline] = empty_line; 315246074Sgabor } else 316246074Sgabor last_line_missing_eol = false; 317246074Sgabor 318246074Sgabor input_lines = iline - 1; 319246074Sgabor 320246074Sgabor /* now check for revision, if any */ 321246074Sgabor 322246074Sgabor if (revision != NULL) { 323246074Sgabor if (!rev_in_string(i_womp)) { 324246074Sgabor if (force) { 325246074Sgabor if (verbose) 326246074Sgabor say("Warning: this file doesn't appear " 327246074Sgabor "to be the %s version--patching anyway.\n", 328246074Sgabor revision); 329246074Sgabor } else if (batch) { 330246074Sgabor fatal("this file doesn't appear to be the " 331246074Sgabor "%s version--aborting.\n", 332246074Sgabor revision); 333246074Sgabor } else { 334246074Sgabor ask("This file doesn't appear to be the " 335246074Sgabor "%s version--patch anyway? [n] ", 336246074Sgabor revision); 337246074Sgabor if (*buf != 'y') 338246074Sgabor fatal("aborted\n"); 339246074Sgabor } 340246074Sgabor } else if (verbose) 341246074Sgabor say("Good. This file appears to be the %s version.\n", 342246074Sgabor revision); 343246074Sgabor } 344246074Sgabor return true; /* plan a will work */ 345246074Sgabor} 346246074Sgabor 347246074Sgabor/* Keep (virtually) nothing in memory. */ 348246074Sgabor 349246074Sgaborstatic void 350246074Sgaborplan_b(const char *filename) 351246074Sgabor{ 352246074Sgabor FILE *ifp; 353246074Sgabor size_t i = 0, j, maxlen = 1; 354246074Sgabor char *p; 355246074Sgabor bool found_revision = (revision == NULL); 356246074Sgabor 357246074Sgabor using_plan_a = false; 358246074Sgabor if ((ifp = fopen(filename, "r")) == NULL) 359246074Sgabor pfatal("can't open file %s", filename); 360246074Sgabor unlink(TMPINNAME); 361246074Sgabor if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0) 362246074Sgabor pfatal("can't open file %s", TMPINNAME); 363246074Sgabor while (fgets(buf, buf_size, ifp) != NULL) { 364246074Sgabor if (revision != NULL && !found_revision && rev_in_string(buf)) 365246074Sgabor found_revision = true; 366246074Sgabor if ((i = strlen(buf)) > maxlen) 367246074Sgabor maxlen = i; /* find longest line */ 368246074Sgabor } 369246074Sgabor last_line_missing_eol = i > 0 && buf[i - 1] != '\n'; 370246074Sgabor if (last_line_missing_eol && maxlen == i) 371246074Sgabor maxlen++; 372246074Sgabor 373246074Sgabor if (revision != NULL) { 374246074Sgabor if (!found_revision) { 375246074Sgabor if (force) { 376246074Sgabor if (verbose) 377246074Sgabor say("Warning: this file doesn't appear " 378246074Sgabor "to be the %s version--patching anyway.\n", 379246074Sgabor revision); 380246074Sgabor } else if (batch) { 381246074Sgabor fatal("this file doesn't appear to be the " 382246074Sgabor "%s version--aborting.\n", 383246074Sgabor revision); 384246074Sgabor } else { 385246074Sgabor ask("This file doesn't appear to be the %s " 386246074Sgabor "version--patch anyway? [n] ", 387246074Sgabor revision); 388246074Sgabor if (*buf != 'y') 389246074Sgabor fatal("aborted\n"); 390246074Sgabor } 391246074Sgabor } else if (verbose) 392246074Sgabor say("Good. This file appears to be the %s version.\n", 393246074Sgabor revision); 394246074Sgabor } 395246074Sgabor fseek(ifp, 0L, SEEK_SET); /* rewind file */ 396246074Sgabor lines_per_buf = BUFFERSIZE / maxlen; 397246074Sgabor tireclen = maxlen; 398246074Sgabor tibuf[0] = malloc(BUFFERSIZE + 1); 399246074Sgabor if (tibuf[0] == NULL) 400246074Sgabor fatal("out of memory\n"); 401246074Sgabor tibuf[1] = malloc(BUFFERSIZE + 1); 402246074Sgabor if (tibuf[1] == NULL) 403246074Sgabor fatal("out of memory\n"); 404246074Sgabor for (i = 1;; i++) { 405246074Sgabor p = tibuf[0] + maxlen * (i % lines_per_buf); 406246074Sgabor if (i % lines_per_buf == 0) /* new block */ 407246074Sgabor if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) 408246074Sgabor pfatal("can't write temp file"); 409246074Sgabor if (fgets(p, maxlen + 1, ifp) == NULL) { 410246074Sgabor input_lines = i - 1; 411246074Sgabor if (i % lines_per_buf != 0) 412246074Sgabor if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) 413246074Sgabor pfatal("can't write temp file"); 414246074Sgabor break; 415246074Sgabor } 416246074Sgabor j = strlen(p); 417246074Sgabor /* These are '\n' terminated strings, so no need to add a NUL */ 418246074Sgabor if (j == 0 || p[j - 1] != '\n') 419246074Sgabor p[j] = '\n'; 420246074Sgabor } 421246074Sgabor fclose(ifp); 422246074Sgabor close(tifd); 423246074Sgabor if ((tifd = open(TMPINNAME, O_RDONLY)) < 0) 424246074Sgabor pfatal("can't reopen file %s", TMPINNAME); 425246074Sgabor} 426246074Sgabor 427246074Sgabor/* 428246074Sgabor * Fetch a line from the input file, \n terminated, not necessarily \0. 429246074Sgabor */ 430246074Sgaborchar * 431246074Sgaborifetch(LINENUM line, int whichbuf) 432246074Sgabor{ 433246074Sgabor if (line < 1 || line > input_lines) { 434246074Sgabor if (warn_on_invalid_line) { 435246074Sgabor say("No such line %ld in input file, ignoring\n", line); 436246074Sgabor warn_on_invalid_line = false; 437246074Sgabor } 438246074Sgabor return NULL; 439246074Sgabor } 440246074Sgabor if (using_plan_a) 441246074Sgabor return i_ptr[line]; 442246074Sgabor else { 443246074Sgabor LINENUM offline = line % lines_per_buf; 444246074Sgabor LINENUM baseline = line - offline; 445246074Sgabor 446246074Sgabor if (tiline[0] == baseline) 447246074Sgabor whichbuf = 0; 448246074Sgabor else if (tiline[1] == baseline) 449246074Sgabor whichbuf = 1; 450246074Sgabor else { 451246074Sgabor tiline[whichbuf] = baseline; 452246074Sgabor 453246074Sgabor if (lseek(tifd, (off_t) (baseline / lines_per_buf * 454246074Sgabor BUFFERSIZE), SEEK_SET) < 0) 455246074Sgabor pfatal("cannot seek in the temporary input file"); 456246074Sgabor 457246074Sgabor if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0) 458246074Sgabor pfatal("error reading tmp file %s", TMPINNAME); 459246074Sgabor } 460246074Sgabor return tibuf[whichbuf] + (tireclen * offline); 461246074Sgabor } 462246074Sgabor} 463246074Sgabor 464246074Sgabor/* 465246074Sgabor * True if the string argument contains the revision number we want. 466246074Sgabor */ 467246074Sgaborstatic bool 468246074Sgaborrev_in_string(const char *string) 469246074Sgabor{ 470246074Sgabor const char *s; 471246074Sgabor size_t patlen; 472246074Sgabor 473246074Sgabor if (revision == NULL) 474246074Sgabor return true; 475246074Sgabor patlen = strlen(revision); 476246074Sgabor if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen])) 477246074Sgabor return true; 478246074Sgabor for (s = string; *s; s++) { 479246074Sgabor if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) && 480246074Sgabor isspace((unsigned char)s[patlen + 1])) { 481246074Sgabor return true; 482246074Sgabor } 483246074Sgabor } 484246074Sgabor return false; 485246074Sgabor} 486