1 /* $OpenBSD: ed.c,v 1.4 2019/12/02 22:17:32 jca Exp $ */ 2 3 /* 4 * Copyright (c) 2015 Tobias Stoeckmann <tobias@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/queue.h> 20 #include <sys/stat.h> 21 22 #include <ctype.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #include "common.h" 28 #include "util.h" 29 #include "pch.h" 30 #include "inp.h" 31 32 /* states of finite state machine */ 33 #define FSM_CMD 1 34 #define FSM_A 2 35 #define FSM_C 3 36 #define FSM_D 4 37 #define FSM_I 5 38 #define FSM_S 6 39 40 #define SRC_INP 1 /* line's origin is input file */ 41 #define SRC_PCH 2 /* line's origin is patch file */ 42 43 #define S_PATTERN "/.//" 44 45 static void init_lines(void); 46 static void free_lines(void); 47 static struct ed_line *get_line(LINENUM); 48 static struct ed_line *create_line(off_t); 49 static int valid_addr(LINENUM, LINENUM); 50 static int get_command(void); 51 static void write_lines(char *); 52 53 LIST_HEAD(ed_head, ed_line) head; 54 struct ed_line { 55 LIST_ENTRY(ed_line) entries; 56 int src; 57 unsigned long subst; 58 union { 59 LINENUM lineno; 60 off_t seek; 61 } pos; 62 }; 63 64 static LINENUM first_addr; 65 static LINENUM second_addr; 66 static LINENUM line_count; 67 static struct ed_line *cline; /* current line */ 68 69 void 70 do_ed_script(void) 71 { 72 off_t linepos; 73 struct ed_line *nline; 74 LINENUM i, range; 75 int fsm; 76 77 init_lines(); 78 cline = NULL; 79 fsm = FSM_CMD; 80 81 for (;;) { 82 linepos = ftello(pfp); 83 if (pgetline(&buf, &bufsz, pfp) == -1) 84 break; 85 p_input_line++; 86 87 if (fsm == FSM_CMD) { 88 if ((fsm = get_command()) == -1) 89 break; 90 91 switch (fsm) { 92 case FSM_C: 93 case FSM_D: 94 /* delete lines in specified range */ 95 if (second_addr == -1) 96 range = 1; 97 else 98 range = second_addr - first_addr + 1; 99 for (i = 0; i < range; i++) { 100 nline = LIST_NEXT(cline, entries); 101 LIST_REMOVE(cline, entries); 102 free(cline); 103 cline = nline; 104 line_count--; 105 } 106 cline = get_line(first_addr - 1); 107 fsm = (fsm == FSM_C) ? FSM_A : FSM_CMD; 108 break; 109 case FSM_S: 110 cline->subst++; 111 fsm = FSM_CMD; 112 break; 113 default: 114 break; 115 } 116 117 continue; 118 } 119 120 if (strcmp(buf, ".\n") == 0) { 121 fsm = FSM_CMD; 122 continue; 123 } 124 125 nline = create_line(linepos); 126 if (cline == NULL) 127 LIST_INSERT_HEAD(&head, nline, entries); 128 else if (fsm == FSM_A) 129 LIST_INSERT_AFTER(cline, nline, entries); 130 else 131 LIST_INSERT_BEFORE(cline, nline, entries); 132 cline = nline; 133 line_count++; 134 fsm = FSM_A; 135 } 136 137 next_intuit_at(linepos, p_input_line); 138 139 if (skip_rest_of_patch) { 140 free_lines(); 141 return; 142 } 143 144 write_lines(TMPOUTNAME); 145 free_lines(); 146 147 ignore_signals(); 148 if (!check_only) { 149 if (move_file(TMPOUTNAME, outname) < 0) { 150 toutkeep = true; 151 chmod(TMPOUTNAME, filemode); 152 } else 153 chmod(outname, filemode); 154 } 155 set_signals(1); 156 } 157 158 static int 159 get_command(void) 160 { 161 char *p; 162 LINENUM min_addr; 163 int fsm; 164 165 min_addr = 0; 166 fsm = -1; 167 p = buf; 168 169 /* maybe garbage encountered at end of patch */ 170 if (!isdigit((unsigned char)*p)) 171 return -1; 172 173 first_addr = strtolinenum(buf, &p); 174 second_addr = (*p == ',') ? strtolinenum(p + 1, &p) : -1; 175 176 switch (*p++) { 177 case 'a': 178 if (second_addr != -1) 179 fatal("invalid address at line %ld: %s", 180 p_input_line, buf); 181 fsm = FSM_A; 182 break; 183 case 'c': 184 fsm = FSM_C; 185 min_addr = 1; 186 break; 187 case 'd': 188 fsm = FSM_D; 189 min_addr = 1; 190 break; 191 case 'i': 192 if (second_addr != -1) 193 fatal("invalid address at line %ld: %s", 194 p_input_line, buf); 195 fsm = FSM_I; 196 break; 197 case 's': 198 if (second_addr != -1) 199 fatal("unsupported address range at line %ld: %s", 200 p_input_line, buf); 201 if (strncmp(p, S_PATTERN, sizeof(S_PATTERN) - 1) != 0) 202 fatal("unsupported substitution at " 203 "line %ld: %s", p_input_line, buf); 204 p += sizeof(S_PATTERN) - 1; 205 fsm = FSM_S; 206 min_addr = 1; 207 break; 208 default: 209 return -1; 210 /* NOTREACHED */ 211 } 212 213 if (*p != '\n') 214 return -1; 215 216 if (!valid_addr(first_addr, min_addr) || 217 (second_addr != -1 && !valid_addr(second_addr, first_addr))) 218 fatal("invalid address at line %ld: %s", p_input_line, buf); 219 220 cline = get_line(first_addr); 221 222 return fsm; 223 } 224 225 static void 226 write_lines(char *filename) 227 { 228 FILE *ofp; 229 char *p; 230 struct ed_line *line; 231 off_t linepos; 232 233 linepos = ftello(pfp); 234 ofp = fopen(filename, "w"); 235 if (ofp == NULL) 236 pfatal("can't create %s", filename); 237 238 LIST_FOREACH(line, &head, entries) { 239 if (line->src == SRC_INP) { 240 p = ifetch(line->pos.lineno, 0); 241 /* Note: string is not NUL terminated. */ 242 for (; *p != '\n'; p++) 243 if (line->subst != 0) 244 line->subst--; 245 else 246 putc(*p, ofp); 247 putc('\n', ofp); 248 } else if (line->src == SRC_PCH) { 249 fseeko(pfp, line->pos.seek, SEEK_SET); 250 if (pgetline(&buf, &bufsz, pfp) == -1) 251 fatal("unexpected end of file"); 252 p = buf; 253 if (line->subst != 0) 254 for (; *p != '\0' && *p != '\n'; p++) 255 if (line->subst-- == 0) 256 break; 257 fputs(p, ofp); 258 if (strchr(p, '\n') == NULL) 259 putc('\n', ofp); 260 } 261 } 262 fclose(ofp); 263 264 /* restore patch file position to match p_input_line */ 265 fseeko(pfp, linepos, SEEK_SET); 266 } 267 268 /* initialize list with input file */ 269 static void 270 init_lines(void) 271 { 272 struct ed_line *line; 273 LINENUM i; 274 275 LIST_INIT(&head); 276 for (i = input_lines; i > 0; i--) { 277 line = malloc(sizeof(*line)); 278 if (line == NULL) 279 fatal("cannot allocate memory"); 280 line->src = SRC_INP; 281 line->subst = 0; 282 line->pos.lineno = i; 283 LIST_INSERT_HEAD(&head, line, entries); 284 } 285 line_count = input_lines; 286 } 287 288 static void 289 free_lines(void) 290 { 291 struct ed_line *line; 292 293 while (!LIST_EMPTY(&head)) { 294 line = LIST_FIRST(&head); 295 LIST_REMOVE(line, entries); 296 free(line); 297 } 298 } 299 300 static struct ed_line * 301 get_line(LINENUM lineno) 302 { 303 struct ed_line *line; 304 LINENUM i; 305 306 if (lineno == 0) 307 return NULL; 308 309 i = 0; 310 LIST_FOREACH(line, &head, entries) 311 if (++i == lineno) 312 return line; 313 314 return NULL; 315 } 316 317 static struct ed_line * 318 create_line(off_t seek) 319 { 320 struct ed_line *line; 321 322 line = malloc(sizeof(*line)); 323 if (line == NULL) 324 fatal("cannot allocate memory"); 325 line->src = SRC_PCH; 326 line->subst = 0; 327 line->pos.seek = seek; 328 329 return line; 330 } 331 332 static int 333 valid_addr(LINENUM lineno, LINENUM min) 334 { 335 return lineno >= min && lineno <= line_count; 336 } 337