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