Home | History | Annotate | Line # | Download | only in patch
inp.c revision 1.12
      1 /*	$NetBSD: inp.c,v 1.12 2003/05/30 18:14:13 kristerw Exp $	*/
      2 #include <sys/cdefs.h>
      3 #ifndef lint
      4 __RCSID("$NetBSD: inp.c,v 1.12 2003/05/30 18:14:13 kristerw Exp $");
      5 #endif /* not lint */
      6 
      7 #include "EXTERN.h"
      8 #include "backupfile.h"
      9 #include "common.h"
     10 #include "util.h"
     11 #include "pch.h"
     12 #include "INTERN.h"
     13 #include "inp.h"
     14 
     15 #include <stdlib.h>
     16 #include <unistd.h>
     17 #include <fcntl.h>
     18 
     19 static bool plan_a(char *);
     20 static void plan_b(char *);
     21 static bool rev_in_string(char *);
     22 
     23 /* Input-file-with-indexable-lines abstract type. */
     24 
     25 static size_t i_size;			/* Size of the input file */
     26 static char *i_womp;			/* Plan a buffer for entire file */
     27 static char **i_ptr;			/* Pointers to lines in i_womp */
     28 
     29 static int tifd = -1;			/* Plan b virtual string array */
     30 static char *tibuf[2];			/* Plan b buffers */
     31 static LINENUM tiline[2] = {-1, -1};	/* 1st line in each buffer */
     32 static LINENUM lines_per_buf;		/* How many lines per buffer */
     33 static int tireclen;			/* Length of records in tmp file */
     34 
     35 /*
     36  * New patch--prepare to edit another file.
     37  */
     38 void
     39 re_input(void)
     40 {
     41 	if (using_plan_a) {
     42 		i_size = 0;
     43 
     44 		if (i_ptr != NULL)
     45 			free(i_ptr);
     46 		if (i_womp != NULL)
     47 			free(i_womp);
     48 		i_womp = NULL;
     49 		i_ptr = NULL;
     50 	} else {
     51 		using_plan_a = TRUE;	/* maybe the next one is smaller */
     52 		Close(tifd);
     53 		tifd = -1;
     54 		free(tibuf[0]);
     55 		free(tibuf[1]);
     56 		tibuf[0] = tibuf[1] = NULL;
     57 		tiline[0] = tiline[1] = -1;
     58 		tireclen = 0;
     59 	}
     60 }
     61 
     62 /*
     63  * Constuct the line index, somehow or other.
     64  */
     65 void
     66 scan_input(char *filename)
     67 {
     68 	if (!plan_a(filename))
     69 		plan_b(filename);
     70 	if (verbose) {
     71 		say("Patching file %s using Plan %s...\n", filename,
     72 		    (using_plan_a ? "A" : "B") );
     73 	}
     74 }
     75 
     76 /*
     77  * Try keeping everything in memory.
     78  */
     79 static bool
     80 plan_a(char *filename)
     81 {
     82 	int ifd, statfailed;
     83 	char *s;
     84 	LINENUM iline;
     85 	char lbuf[MAXLINELEN];
     86 
     87 	statfailed = stat(filename, &filestat);
     88 	if (statfailed && ok_to_create_file) {
     89 		if (verbose)
     90 			say("(Creating file %s...)\n",filename);
     91 		makedirs(filename, TRUE);
     92 		close(creat(filename, 0666));
     93 		statfailed = stat(filename, &filestat);
     94 	}
     95 	/*
     96 	 * For nonexistent or read-only files, look for RCS or SCCS
     97 	 * versions.
     98 	 */
     99 	if (statfailed ||
    100 	    /* No one can write to it. */
    101 	    (filestat.st_mode & 0222) == 0 ||
    102 	    /* I can't write to it. */
    103 	    ((filestat.st_mode & 0022) == 0 && filestat.st_uid != myuid)) {
    104 		struct stat cstat;
    105 		char *cs = NULL;
    106 		char *filebase;
    107 		size_t pathlen;
    108 
    109 		filebase = basename(filename);
    110 		pathlen = filebase - filename;
    111 
    112 		/*
    113 		 * Put any leading path into `s'.
    114 		 * Leave room in lbuf for the diff command.
    115 		 */
    116 		s = lbuf + 20;
    117 		strncpy(s, filename, pathlen);
    118 
    119 #define try(f, a1, a2) (Sprintf(s + pathlen, f, a1, a2), stat(s, &cstat) == 0)
    120 #define try1(f, a1) (Sprintf(s + pathlen, f, a1), stat(s, &cstat) == 0)
    121 		if (try("RCS/%s%s", filebase, RCSSUFFIX) ||
    122 		    try1("RCS/%s"  , filebase) ||
    123 		    try("%s%s", filebase, RCSSUFFIX)) {
    124 			Sprintf(buf, CHECKOUT, filename);
    125 			Sprintf(lbuf, RCSDIFF, filename);
    126 			cs = "RCS";
    127 		} else if (try("SCCS/%s%s", SCCSPREFIX, filebase) ||
    128 			   try("%s%s", SCCSPREFIX, filebase)) {
    129 			Sprintf(buf, GET, s);
    130 			Sprintf(lbuf, SCCSDIFF, s, filename);
    131 			cs = "SCCS";
    132 		} else if (statfailed)
    133 			fatal("can't find %s\n", filename);
    134 		/*
    135 		 * else we can't write to it but it's not under a version
    136 		 * control system, so just proceed.
    137 		 */
    138 		if (cs) {
    139 			if (!statfailed) {
    140 				if ((filestat.st_mode & 0222) != 0)
    141 					/* The owner can write to it.  */
    142 					fatal(
    143 "file %s seems to be locked by somebody else under %s\n",
    144 					      filename, cs);
    145 				/*
    146 				 * It might be checked out unlocked.  See if
    147 				 * it's safe to check out the default version
    148 				 * locked.
    149 				 */
    150 				if (verbose)
    151 					say(
    152 "Comparing file %s to default %s version...\n",
    153 					    filename, cs);
    154 				if (system(lbuf))
    155 					fatal(
    156 "can't check out file %s: differs from default %s version\n",
    157 					      filename, cs);
    158 			}
    159 			if (verbose)
    160 				say("Checking out file %s from %s...\n",
    161 				    filename, cs);
    162 			if (system(buf) || stat(filename, &filestat))
    163 				fatal("can't check out file %s from %s\n",
    164 				      filename, cs);
    165 		}
    166 	}
    167 	if (old_file_is_dev_null &&
    168 	    ok_to_create_file &&
    169 	    (filestat.st_size != 0)) {
    170 		fatal(
    171 "patch creates new file but existing file %s not empty\n",
    172 		      filename);
    173 	}
    174 
    175 	filemode = filestat.st_mode;
    176 	if (!S_ISREG(filemode))
    177 		fatal("%s is not a normal file--can't patch\n", filename);
    178 	i_size = filestat.st_size;
    179 
    180 	i_womp = xmalloc(i_size + 2);
    181 	if ((ifd = open(filename, 0)) < 0)
    182 		pfatal("can't open file %s", filename);
    183 	if (read(ifd, i_womp, i_size) != i_size) {
    184 		/*
    185 		 * This probably means i_size > 15 or 16 bits worth at this
    186 		 *  point it doesn't matter if i_womp was undersized.
    187 		 */
    188 		Close(ifd);
    189 		free(i_womp);
    190 		return FALSE;
    191 	}
    192 	Close(ifd);
    193 	if (i_size && i_womp[i_size - 1] != '\n')
    194 		i_womp[i_size++] = '\n';
    195 	i_womp[i_size] = '\0';
    196 
    197 	/*
    198 	 * Count the lines in the buffer so we know how many pointers we
    199 	 * need.
    200 	 */
    201 	iline = 0;
    202 	for (s = i_womp; *s; s++) {
    203 		if (*s == '\n')
    204 			iline++;
    205 	}
    206 	i_ptr = xmalloc((iline + 2) * sizeof(char *));
    207 
    208 	/* Now scan the buffer and build pointer array. */
    209 	iline = 1;
    210 	i_ptr[iline] = i_womp;
    211 	for (s = i_womp; *s; s++) {
    212 		if (*s == '\n') {
    213 			/* These are NOT null terminated. */
    214 			i_ptr[++iline] = s + 1;
    215 		}
    216 	}
    217 	input_lines = iline - 1;
    218 
    219 	/* Now check for revision, if any. */
    220 	if (revision != NULL) {
    221 		if (!rev_in_string(i_womp)) {
    222 			if (force) {
    223 				if (verbose)
    224 					say(
    225 "Warning: this file doesn't appear to be the %s version--patching anyway.\n",
    226 			revision);
    227 			} else if (batch) {
    228 				fatal(
    229 "this file doesn't appear to be the %s version--aborting.\n", revision);
    230 			} else {
    231 				ask(
    232 "This file doesn't appear to be the %s version--patch anyway? [n] ",
    233 		    revision);
    234 				if (*buf != 'y')
    235 					fatal("aborted\n");
    236 			}
    237 		} else if (verbose)
    238 			say("Good.  This file appears to be the %s version.\n",
    239 			    revision);
    240 	}
    241 
    242     return TRUE;		/* Plan a will work. */
    243 }
    244 
    245 /*
    246  * Keep (virtually) nothing in memory.
    247  */
    248 static void
    249 plan_b(char *filename)
    250 {
    251 	FILE *ifp;
    252 	int i = 0;
    253 	int maxlen = 1;
    254 	bool found_revision = (revision == NULL);
    255 
    256 	using_plan_a = FALSE;
    257 	if ((ifp = fopen(filename, "r")) == NULL)
    258 		pfatal("can't open file %s", filename);
    259 	if ((tifd = creat(TMPINNAME, 0666)) < 0)
    260 		pfatal("can't open file %s", TMPINNAME);
    261 	while (fgets(buf, sizeof buf, ifp) != NULL) {
    262 		if (revision != NULL && !found_revision && rev_in_string(buf))
    263 			found_revision = TRUE;
    264 		if ((i = strlen(buf)) > maxlen)
    265 			maxlen = i;		/* Find longest line. */
    266 	}
    267 	if (revision != NULL) {
    268 		if (!found_revision) {
    269 			if (force) {
    270 				if (verbose)
    271 					say(
    272 "Warning: this file doesn't appear to be the %s version--patching anyway.\n",
    273 					    revision);
    274 			} else if (batch) {
    275 				fatal(
    276 "this file doesn't appear to be the %s version--aborting.\n", revision);
    277 			} else {
    278 				ask(
    279 "This file doesn't appear to be the %s version--patch anyway? [n] ",
    280 				    revision);
    281 				if (*buf != 'y')
    282 					fatal("aborted\n");
    283 			}
    284 		} else if (verbose)
    285 			say("Good.  This file appears to be the %s version.\n",
    286 			    revision);
    287 	}
    288 	Fseek(ifp, 0L, 0);		/* Rewind file. */
    289 	lines_per_buf = BUFFERSIZE / maxlen;
    290 	tireclen = maxlen;
    291 	tibuf[0] = xmalloc(BUFFERSIZE + 1);
    292 	tibuf[1] = xmalloc(BUFFERSIZE + 1);
    293 	for (i = 1; ; i++) {
    294 		if (! (i % lines_per_buf))	/* New block. */
    295 			if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
    296 				pfatal("can't write temp file");
    297 		if (fgets(tibuf[0] + maxlen * (i % lines_per_buf),
    298 			  maxlen + 1, ifp) == NULL) {
    299 			input_lines = i - 1;
    300 			if (i % lines_per_buf)
    301 				if (write(tifd, tibuf[0], BUFFERSIZE)
    302 				    < BUFFERSIZE)
    303 					pfatal("can't write temp file");
    304 			break;
    305 		}
    306 	}
    307 	Fclose(ifp);
    308 	Close(tifd);
    309 	if ((tifd = open(TMPINNAME, 0)) < 0) {
    310 		pfatal("can't reopen file %s", TMPINNAME);
    311 	}
    312 }
    313 
    314 /*
    315  * Fetch a line from the input file, \n terminated, not necessarily \0.
    316  */
    317 char *
    318 ifetch(LINENUM line, int whichbuf)
    319 {
    320 	if (line < 1 || line > input_lines)
    321 		return "";
    322 	if (using_plan_a)
    323 		return i_ptr[line];
    324 	else {
    325 		LINENUM offline = line % lines_per_buf;
    326 		LINENUM baseline = line - offline;
    327 
    328 		if (tiline[0] == baseline)
    329 			whichbuf = 0;
    330 		else if (tiline[1] == baseline)
    331 			whichbuf = 1;
    332 		else {
    333 			tiline[whichbuf] = baseline;
    334 			Lseek(tifd, baseline / lines_per_buf * BUFFERSIZE, 0);
    335 			if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
    336 				pfatal("error reading tmp file %s", TMPINNAME);
    337 		}
    338 		return tibuf[whichbuf] + (tireclen * offline);
    339 	}
    340 }
    341 
    342 /*
    343  * True if the string argument contains the revision number we want.
    344  */
    345 static bool
    346 rev_in_string(char *string)
    347 {
    348 	char *s;
    349 	size_t patlen;
    350 
    351 	if (revision == NULL)
    352 		return TRUE;
    353 	patlen = strlen(revision);
    354 	if (strnEQ(string,revision,patlen) &&
    355 	    isspace((unsigned char)string[patlen]))
    356 		return TRUE;
    357 	for (s = string; *s; s++) {
    358 		if (isspace((unsigned char)*s) &&
    359 		    strnEQ(s + 1, revision, patlen) &&
    360 		    isspace((unsigned char)s[patlen + 1] )) {
    361 			return TRUE;
    362 		}
    363 	}
    364     return FALSE;
    365 }
    366