xref: /openbsd/usr.bin/patch/ed.c (revision d89ec533)
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