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