inp.c revision 285976
140090Smsmith/*- 240090Smsmith * Copyright 1986, Larry Wall 340090Smsmith * 440090Smsmith * Redistribution and use in source and binary forms, with or without 540090Smsmith * modification, are permitted provided that the following condition is met: 640090Smsmith * 1. Redistributions of source code must retain the above copyright notice, 740090Smsmith * this condition and the following disclaimer. 840090Smsmith * 940090Smsmith * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY 1040090Smsmith * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 1140090Smsmith * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 1240090Smsmith * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 1340090Smsmith * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1440090Smsmith * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 1540090Smsmith * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 1640090Smsmith * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 1740090Smsmith * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 1840090Smsmith * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 1940090Smsmith * SUCH DAMAGE. 2040090Smsmith * 2140090Smsmith * patch - a program to apply diffs to original files 2240090Smsmith * 2340090Smsmith * -C option added in 1998, original code by Marc Espie, based on FreeBSD 2440090Smsmith * behaviour 2540116Sjkh * 2640090Smsmith * $OpenBSD: inp.c,v 1.36 2012/04/10 14:46:34 ajacoutot Exp $ 2740090Smsmith * $FreeBSD: stable/10/usr.bin/patch/inp.c 285976 2015-07-28 19:58:44Z delphij $ 2840090Smsmith */ 2994936Smux 3094936Smux#include <sys/types.h> 3140090Smsmith#include <sys/file.h> 3294936Smux#include <sys/stat.h> 3394936Smux#include <sys/mman.h> 3494936Smux#include <sys/wait.h> 3540090Smsmith 3640090Smsmith#include <ctype.h> 37116182Sobrien#include <errno.h> 38116182Sobrien#include <libgen.h> 39116182Sobrien#include <limits.h> 4094936Smux#include <stddef.h> 4140090Smsmith#include <stdio.h> 4294936Smux#include <stdlib.h> 4394936Smux#include <string.h> 4494936Smux#include <unistd.h> 4594936Smux 4694936Smux#include "common.h" 47164033Srwatson#include "util.h" 4840090Smsmith#include "pch.h" 4940090Smsmith#include "inp.h" 5094936Smux 5194936Smux 5240090Smsmith/* Input-file-with-indexable-lines abstract type */ 5394936Smux 5440090Smsmithstatic size_t i_size; /* size of the input file */ 55163606Srwatsonstatic char *i_womp; /* plan a buffer for entire file */ 56163606Srwatsonstatic char **i_ptr; /* pointers to lines in i_womp */ 57141616Sphkstatic char empty_line[] = { '\0' }; 5840090Smsmith 5994936Smuxstatic int tifd = -1; /* plan b virtual string array */ 6040090Smsmithstatic char *tibuf[2]; /* plan b buffers */ 6194936Smuxstatic LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */ 6294936Smuxstatic LINENUM lines_per_buf; /* how many lines per buffer */ 63202050Simpstatic int tireclen; /* length of records in tmp file */ 64202050Simp 6594936Smuxstatic bool rev_in_string(const char *); 6694936Smuxstatic bool reallocate_lines(size_t *); 6794936Smux 6894936Smux/* returns false if insufficient memory */ 69160217Sscottlstatic bool plan_a(const char *); 7094936Smux 7185385Sjhbstatic void plan_b(const char *); 72167232Srwatson 7394936Smux/* New patch--prepare to edit another file. */ 7494936Smux 7594936Smuxvoid 7694936Smuxre_input(void) 7794936Smux{ 7894936Smux if (using_plan_a) { 7994936Smux free(i_ptr); 80225617Skmacy i_ptr = NULL; 8194936Smux if (i_womp != NULL) { 8294936Smux munmap(i_womp, i_size); 83107850Salfred i_womp = NULL; 84107850Salfred } 85107850Salfred i_size = 0; 86107850Salfred } else { 8794936Smux using_plan_a = true; /* maybe the next one is smaller */ 8894936Smux close(tifd); 89160217Sscottl tifd = -1; 90190301Scperciva free(tibuf[0]); 9194936Smux free(tibuf[1]); 9295839Speter tibuf[0] = tibuf[1] = NULL; 9394936Smux tiline[0] = tiline[1] = -1; 9494936Smux tireclen = 0; 9594936Smux } 96107849Salfred} 97106308Srwatson 98172930Srwatson/* Construct the line index, somehow or other. */ 99106308Srwatson 100106308Srwatsonvoid 101106308Srwatsonscan_input(const char *filename) 102128697Sdas{ 103190301Scperciva if (!plan_a(filename)) 104190301Scperciva plan_b(filename); 105190301Scperciva if (verbose) { 106190301Scperciva say("Patching file %s using Plan %s...\n", filename, 107160217Sscottl (using_plan_a ? "A" : "B")); 108190301Scperciva } 109160217Sscottl} 110128697Sdas 111128697Sdasstatic bool 112128697Sdasreallocate_lines(size_t *lines_allocated) 113190301Scperciva{ 114128697Sdas char **p; 115128697Sdas size_t new_size; 116128697Sdas 117128697Sdas new_size = *lines_allocated * 3 / 2; 118160217Sscottl p = realloc(i_ptr, (new_size + 2) * sizeof(char *)); 119160217Sscottl if (p == NULL) { /* shucks, it was a near thing */ 120128697Sdas munmap(i_womp, i_size); 12194936Smux i_womp = NULL; 12294936Smux free(i_ptr); 123160217Sscottl i_ptr = NULL; 124160217Sscottl *lines_allocated = 0; 125160217Sscottl return false; 126160217Sscottl } 127160217Sscottl *lines_allocated = new_size; 128128697Sdas i_ptr = p; 129128697Sdas return true; 13094936Smux} 13194936Smux 132164033Srwatson/* Try keeping everything in memory. */ 133164033Srwatson 134164033Srwatsonstatic bool 13594936Smuxplan_a(const char *filename) 13694936Smux{ 137164033Srwatson int ifd, statfailed, devnull, pstat; 138164033Srwatson char *p, *s, lbuf[INITLINELEN]; 139164033Srwatson struct stat filestat; 140164033Srwatson ptrdiff_t sz; 141164033Srwatson size_t i; 142164033Srwatson size_t iline, lines_allocated; 143164033Srwatson pid_t pid; 14494936Smux char *argp[4] = {NULL}; 14594936Smux 146241222Sjh#ifdef DEBUGGING 14794936Smux if (debug & 8) 148241222Sjh return false; 14994936Smux#endif 15094936Smux 15194936Smux if (filename == NULL || *filename == '\0') 152107849Salfred return false; 15394936Smux 154106308Srwatson statfailed = stat(filename, &filestat); 155172930Srwatson if (statfailed && ok_to_create_file) { 156106308Srwatson if (verbose) 157106308Srwatson say("(Creating file %s...)\n", filename); 158106308Srwatson 15994936Smux /* 16094936Smux * in check_patch case, we still display `Creating file' even 16194936Smux * though we're not. The rule is that -C should be as similar 16294936Smux * to normal patch behavior as possible 16394936Smux */ 16494936Smux if (check_only) 165107849Salfred return true; 166107849Salfred makedirs(filename, true); 167107849Salfred close(creat(filename, 0666)); 16894936Smux statfailed = stat(filename, &filestat); 16994936Smux } 17094936Smux if (statfailed && check_only) 17194936Smux fatal("%s not found, -C mode, can't probe further\n", filename); 17294936Smux /* For nonexistent or read-only files, look for RCS versions. */ 17394936Smux 174107849Salfred if (statfailed || 17594936Smux /* No one can write to it. */ 17694936Smux (filestat.st_mode & 0222) == 0 || 17794936Smux /* I can't write to it. */ 17894936Smux ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) { 179241222Sjh char *filebase, *filedir; 180241222Sjh struct stat cstat; 181111119Simp char *tmp_filename1, *tmp_filename2; 182107849Salfred 18394936Smux tmp_filename1 = strdup(filename); 18494936Smux tmp_filename2 = strdup(filename); 18594936Smux if (tmp_filename1 == NULL || tmp_filename2 == NULL) 18694936Smux fatal("strdupping filename"); 187106308Srwatson 188172930Srwatson filebase = basename(tmp_filename1); 189106308Srwatson filedir = dirname(tmp_filename2); 190106308Srwatson 191106308Srwatson#define try(f, a1, a2, a3) \ 19294936Smux (snprintf(lbuf, sizeof(lbuf), f, a1, a2, a3), stat(lbuf, &cstat) == 0) 19394936Smux 19494936Smux /* 195106308Srwatson * else we can't write to it but it's not under a version 196172930Srwatson * control system, so just proceed. 197106308Srwatson */ 198106308Srwatson if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || 199106308Srwatson try("%s/RCS/%s%s", filedir, filebase, "") || 20094936Smux try("%s/%s%s", filedir, filebase, RCSSUFFIX)) { 20194936Smux if (!statfailed) { 20294936Smux if ((filestat.st_mode & 0222) != 0) 20394936Smux /* The owner can write to it. */ 20494936Smux fatal("file %s seems to be locked " 20594936Smux "by somebody else under RCS\n", 20694936Smux filename); 20794936Smux /* 20894936Smux * It might be checked out unlocked. See if 20994936Smux * it's safe to check out the default version 21094936Smux * locked. 21194936Smux */ 21294936Smux if (verbose) 213202050Simp say("Comparing file %s to default " 214202050Simp "RCS version...\n", filename); 215202050Simp 216202050Simp switch (pid = fork()) { 217202050Simp case -1: 218202050Simp fatal("can't fork: %s\n", 219202050Simp strerror(errno)); 220202050Simp case 0: 22194936Smux devnull = open("/dev/null", O_RDONLY); 22294936Smux if (devnull == -1) { 22394936Smux fatal("can't open /dev/null: %s", 22494936Smux strerror(errno)); 22594936Smux } 22694936Smux (void)dup2(devnull, STDOUT_FILENO); 22794936Smux argp[0] = strdup(RCSDIFF); 228222216Sjh argp[1] = strdup(filename); 229222216Sjh execv(RCSDIFF, argp); 23094936Smux exit(127); 231148585Snetchild } 232148585Snetchild pid = waitpid(pid, &pstat, 0); 23394936Smux if (pid == -1 || WEXITSTATUS(pstat) != 0) { 23494936Smux fatal("can't check out file %s: " 23594936Smux "differs from default RCS version\n", 236222216Sjh filename); 237222216Sjh } 238222216Sjh } 239222216Sjh 240222216Sjh if (verbose) 241156748Snetchild say("Checking out file %s from RCS...\n", 242156748Snetchild filename); 243156482Sphk 244156748Snetchild switch (pid = fork()) { 245156482Sphk case -1: 246156482Sphk fatal("can't fork: %s\n", strerror(errno)); 247156482Sphk case 0: 24894936Smux argp[0] = strdup(CHECKOUT); 24994936Smux argp[1] = strdup("-l"); 25094936Smux argp[2] = strdup(filename); 251160217Sscottl execv(CHECKOUT, argp); 25294936Smux exit(127); 25394936Smux } 25494936Smux pid = waitpid(pid, &pstat, 0); 25594936Smux if (pid == -1 || WEXITSTATUS(pstat) != 0 || 25694936Smux stat(filename, &filestat)) { 25794936Smux fatal("can't check out file %s from RCS\n", 25894936Smux filename); 25994936Smux } 26094936Smux } else if (statfailed) { 26194936Smux fatal("can't find %s\n", filename); 26294936Smux } 26394936Smux free(tmp_filename1); 26494936Smux free(tmp_filename2); 26594936Smux } 26694936Smux 26794936Smux filemode = filestat.st_mode; 26894936Smux if (!S_ISREG(filemode)) 26994936Smux fatal("%s is not a normal file--can't patch\n", filename); 27094936Smux if ((uint64_t)filestat.st_size > SIZE_MAX) { 27194936Smux say("block too large to mmap\n"); 27294936Smux return false; 273160217Sscottl } 27494936Smux i_size = (size_t)filestat.st_size; 27594936Smux if (out_of_mem) { 276150568Sdavidxu set_hunkmax(); /* make sure dynamic arrays are allocated */ 277150568Sdavidxu out_of_mem = false; 27894936Smux return false; /* force plan b because plan a bombed */ 27994936Smux } 28094936Smux if ((ifd = open(filename, O_RDONLY)) < 0) 28194936Smux pfatal("can't open file %s", filename); 28294936Smux 28394936Smux if (i_size) { 28494936Smux i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0); 28594936Smux if (i_womp == MAP_FAILED) { 28694936Smux perror("mmap failed"); 28794936Smux i_womp = NULL; 28894936Smux close(ifd); 28994936Smux return false; 29094936Smux } 29194936Smux } else { 29294936Smux i_womp = NULL; 29394936Smux } 29494936Smux 29595467Sbde close(ifd); 29695467Sbde if (i_size) 29794936Smux madvise(i_womp, i_size, MADV_SEQUENTIAL); 29895467Sbde 29995467Sbde /* estimate the number of lines */ 30094936Smux lines_allocated = i_size / 25; 30194936Smux if (lines_allocated < 100) 30294936Smux lines_allocated = 100; 30394936Smux 30494936Smux if (!reallocate_lines(&lines_allocated)) 30594936Smux return false; 30685385Sjhb 30794936Smux /* now scan the buffer and build pointer array */ 30894936Smux iline = 1; 30994936Smux i_ptr[iline] = i_womp; 31085385Sjhb /* test for NUL too, to maintain the behavior of the original code */ 31140090Smsmith for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) { 31278247Speter if (*s == '\n') { 31340090Smsmith if (iline == lines_allocated) { 31494959Smux if (!reallocate_lines(&lines_allocated)) 31594936Smux return false; 31694936Smux } 31794936Smux /* these are NOT NUL terminated */ 31894936Smux i_ptr[++iline] = s + 1; 319160217Sscottl } 32094936Smux } 32194936Smux /* if the last line contains no EOL, append one */ 32294959Smux if (i_size > 0 && i_womp[i_size - 1] != '\n') { 323160217Sscottl last_line_missing_eol = true; 32494959Smux /* fix last line */ 325111119Simp sz = s - i_ptr[iline]; 32694959Smux p = malloc(sz + 1); 32794959Smux if (p == NULL) { 328160217Sscottl free(i_ptr); 32994936Smux i_ptr = NULL; 330221607Sjh munmap(i_womp, i_size); 331221607Sjh i_womp = NULL; 33294959Smux return false; 33394936Smux } 33494936Smux 33594936Smux memcpy(p, i_ptr[iline], sz); 33640090Smsmith p[sz] = '\n'; 33740090Smsmith i_ptr[iline] = p; 33842706Smsmith /* count the extra line and make it point to some valid mem */ 33994936Smux i_ptr[++iline] = empty_line; 34094936Smux } else 34194936Smux last_line_missing_eol = false; 34294936Smux 34394936Smux input_lines = iline - 1; 34494936Smux 34594936Smux /* now check for revision, if any */ 34694936Smux 347160217Sscottl if (revision != NULL) { 34894936Smux if (!rev_in_string(i_womp)) { 349160217Sscottl if (force) { 35094936Smux if (verbose) 35194936Smux say("Warning: this file doesn't appear " 35294936Smux "to be the %s version--patching anyway.\n", 35394936Smux revision); 35494936Smux } else if (batch) { 35594936Smux fatal("this file doesn't appear to be the " 35694936Smux "%s version--aborting.\n", 357202050Simp revision); 358202050Simp } else { 359202050Simp ask("This file doesn't appear to be the " 360202050Simp "%s version--patch anyway? [n] ", 361202050Simp revision); 362202050Simp if (*buf != 'y') 363202050Simp fatal("aborted\n"); 364202050Simp } 365202050Simp } else if (verbose) 366202050Simp say("Good. This file appears to be the %s version.\n", 367202050Simp revision); 368202050Simp } 369202050Simp return true; /* plan a will work */ 370202050Simp} 371202050Simp 372202050Simp/* Keep (virtually) nothing in memory. */ 373202050Simp 374202050Simpstatic void 375202050Simpplan_b(const char *filename) 376202050Simp{ 37794936Smux FILE *ifp; 37894936Smux size_t i = 0, j, maxlen = 1; 37994936Smux char *p; 38094959Smux bool found_revision = (revision == NULL); 38194936Smux 38294936Smux using_plan_a = false; 38394959Smux if ((ifp = fopen(filename, "r")) == NULL) 38494959Smux pfatal("can't open file %s", filename); 38594936Smux unlink(TMPINNAME); 386202050Simp if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0) 387202050Simp pfatal("can't open file %s", TMPINNAME); 388202050Simp while (fgets(buf, buf_size, ifp) != NULL) { 38994936Smux if (revision != NULL && !found_revision && rev_in_string(buf)) 39094936Smux found_revision = true; 39194959Smux if ((i = strlen(buf)) > maxlen) 392241222Sjh maxlen = i; /* find longest line */ 39394959Smux } 39494959Smux last_line_missing_eol = i > 0 && buf[i - 1] != '\n'; 395241222Sjh if (last_line_missing_eol && maxlen == i) 39694959Smux maxlen++; 397111119Simp 39894936Smux if (revision != NULL) { 39994936Smux if (!found_revision) { 400160217Sscottl if (force) { 40194936Smux if (verbose) 40294936Smux say("Warning: this file doesn't appear " 40394959Smux "to be the %s version--patching anyway.\n", 40494936Smux revision); 405160217Sscottl } else if (batch) { 40694959Smux fatal("this file doesn't appear to be the " 40794936Smux "%s version--aborting.\n", 40894936Smux revision); 40994936Smux } else { 41094936Smux ask("This file doesn't appear to be the %s " 411148585Snetchild "version--patch anyway? [n] ", 412148585Snetchild revision); 413148585Snetchild if (*buf != 'y') 414148585Snetchild fatal("aborted\n"); 415160217Sscottl } 416148585Snetchild } else if (verbose) 417148585Snetchild say("Good. This file appears to be the %s version.\n", 418148585Snetchild revision); 41994936Smux } 42094936Smux fseek(ifp, 0L, SEEK_SET); /* rewind file */ 421160217Sscottl lines_per_buf = BUFFERSIZE / maxlen; 42294936Smux tireclen = maxlen; 42394959Smux tibuf[0] = malloc(BUFFERSIZE + 1); 42494936Smux if (tibuf[0] == NULL) 42594936Smux fatal("out of memory\n"); 42694936Smux tibuf[1] = malloc(BUFFERSIZE + 1); 42794936Smux if (tibuf[1] == NULL) 42894936Smux fatal("out of memory\n"); 42994936Smux for (i = 1;; i++) { 43094936Smux p = tibuf[0] + maxlen * (i % lines_per_buf); 43194936Smux if (i % lines_per_buf == 0) /* new block */ 43294959Smux if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) 43394936Smux pfatal("can't write temp file"); 43494936Smux if (fgets(p, maxlen + 1, ifp) == NULL) { 43594936Smux input_lines = i - 1; 43694936Smux if (i % lines_per_buf != 0) 437160217Sscottl if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) 43894936Smux pfatal("can't write temp file"); 43994936Smux break; 44094959Smux } 44194936Smux j = strlen(p); 44294936Smux /* These are '\n' terminated strings, so no need to add a NUL */ 44394936Smux if (j == 0 || p[j - 1] != '\n') 444160217Sscottl p[j] = '\n'; 44594959Smux } 44694936Smux fclose(ifp); 44794936Smux close(tifd); 448160217Sscottl if ((tifd = open(TMPINNAME, O_RDONLY)) < 0) 44994936Smux pfatal("can't reopen file %s", TMPINNAME); 45094936Smux} 45194936Smux 45294936Smux/* 45385385Sjhb * Fetch a line from the input file, \n terminated, not necessarily \0. 45485385Sjhb */ 45585385Sjhbchar * 45685385Sjhbifetch(LINENUM line, int whichbuf) 45785385Sjhb{ 45895839Speter if (line < 1 || line > input_lines) { 45985385Sjhb if (warn_on_invalid_line) { 46095839Speter say("No such line %ld in input file, ignoring\n", line); 46195839Speter warn_on_invalid_line = false; 462105354Srobert } 46395839Speter return NULL; 46495839Speter } 46595839Speter if (using_plan_a) 46695839Speter return i_ptr[line]; 46785385Sjhb else { 46885385Sjhb LINENUM offline = line % lines_per_buf; 46985385Sjhb LINENUM baseline = line - offline; 47042706Smsmith 47142706Smsmith if (tiline[0] == baseline) 47242706Smsmith whichbuf = 0; 47378247Speter else if (tiline[1] == baseline) 47442706Smsmith whichbuf = 1; 47595839Speter else { 47695839Speter tiline[whichbuf] = baseline; 47752947Smjacob 47895839Speter if (lseek(tifd, (off_t) (baseline / lines_per_buf * 47995839Speter BUFFERSIZE), SEEK_SET) < 0) 48095839Speter pfatal("cannot seek in the temporary input file"); 48195839Speter 48252947Smjacob if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0) 48352947Smjacob pfatal("error reading tmp file %s", TMPINNAME); 48452947Smjacob } 485172612Sdes return tibuf[whichbuf] + (tireclen * offline); 486172612Sdes } 487172612Sdes} 488172612Sdes 489172612Sdes/* 490172612Sdes * True if the string argument contains the revision number we want. 491172612Sdes */ 492172612Sdesstatic bool 493172612Sdesrev_in_string(const char *string) 494172612Sdes{ 495172612Sdes const char *s; 496172612Sdes size_t patlen; 497172612Sdes 498172612Sdes if (revision == NULL) 499172612Sdes return true; 500137099Sdes patlen = strlen(revision); 501137099Sdes if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen])) 502172612Sdes return true; 503137099Sdes for (s = string; *s; s++) { 504137099Sdes if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) && 505137099Sdes isspace((unsigned char)s[patlen + 1])) { 506172612Sdes return true; 507137099Sdes } 508137099Sdes } 509137099Sdes return false; 510137099Sdes} 511137099Sdes