xref: /openbsd-src/usr.bin/patch/ed.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: ed.c,v 1.2 2016/02/22 19:31:38 tobias 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 (pgets(buf, sizeof buf, pfp) == NULL)
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 				fsm = (fsm == FSM_C) ? FSM_I : FSM_CMD;
107 				break;
108 			case FSM_S:
109 				cline->subst++;
110 				fsm = FSM_CMD;
111 				break;
112 			default:
113 				break;
114 			}
115 
116 			continue;
117 		}
118 
119 		if (strcmp(buf, ".\n") == 0) {
120 			fsm = FSM_CMD;
121 			continue;
122 		}
123 
124 		nline = create_line(linepos);
125 		if (cline == NULL)
126 			LIST_INSERT_HEAD(&head, nline, entries);
127 		else if (fsm == FSM_A)
128 			LIST_INSERT_AFTER(cline, nline, entries);
129 		else
130 			LIST_INSERT_BEFORE(cline, nline, entries);
131 		cline = nline;
132 		line_count++;
133 		fsm = FSM_A;
134 	}
135 
136 	next_intuit_at(linepos, p_input_line);
137 
138 	if (skip_rest_of_patch) {
139 		free_lines();
140 		return;
141 	}
142 
143 	write_lines(TMPOUTNAME);
144 	free_lines();
145 
146 	ignore_signals();
147 	if (!check_only) {
148 		if (move_file(TMPOUTNAME, outname) < 0) {
149 			toutkeep = true;
150 			chmod(TMPOUTNAME, filemode);
151 		} else
152 			chmod(outname, filemode);
153 	}
154 	set_signals(1);
155 }
156 
157 static int
158 get_command(void)
159 {
160 	char *p;
161 	LINENUM min_addr;
162 	int fsm;
163 
164 	min_addr = 0;
165 	fsm = -1;
166 	p = buf;
167 
168 	/* maybe garbage encountered at end of patch */
169 	if (!isdigit((unsigned char)*p))
170 		return -1;
171 
172 	first_addr = strtolinenum(buf, &p);
173 	second_addr = (*p == ',') ? strtolinenum(p + 1, &p) : -1;
174 
175 	switch (*p++) {
176 	case 'a':
177 		if (second_addr != -1)
178 			fatal("invalid address at line %ld: %s",
179 			    p_input_line, buf);
180 		fsm = FSM_A;
181 		break;
182 	case 'c':
183 		fsm = FSM_C;
184 		min_addr = 1;
185 		break;
186 	case 'd':
187 		fsm = FSM_D;
188 		min_addr = 1;
189 		break;
190 	case 'i':
191 		if (second_addr != -1)
192 			fatal("invalid address at line %ld: %s",
193 			    p_input_line, buf);
194 		fsm = FSM_I;
195 		break;
196 	case 's':
197 		if (second_addr != -1)
198 			fatal("unsupported address range at line %ld: %s",
199 			    p_input_line, buf);
200 		if (strncmp(p, S_PATTERN, sizeof(S_PATTERN) - 1) != 0)
201 			fatal("unsupported substitution at "
202 			    "line %ld: %s", p_input_line, buf);
203 		p += sizeof(S_PATTERN) - 1;
204 		fsm = FSM_S;
205 		min_addr = 1;
206 		break;
207 	default:
208 		return -1;
209 		/* NOTREACHED */
210 	}
211 
212 	if (*p != '\n')
213 		return -1;
214 
215 	if (!valid_addr(first_addr, min_addr) ||
216 	    (second_addr != -1 && !valid_addr(second_addr, first_addr)))
217 		fatal("invalid address at line %ld: %s", p_input_line, buf);
218 
219 	cline = get_line(first_addr);
220 
221 	return fsm;
222 }
223 
224 static void
225 write_lines(char *filename)
226 {
227 	FILE *ofp;
228 	char *p;
229 	struct ed_line *line;
230 	off_t linepos;
231 
232 	linepos = ftello(pfp);
233 	ofp = fopen(filename, "w");
234 	if (ofp == NULL)
235 		pfatal("can't create %s", filename);
236 
237 	LIST_FOREACH(line, &head, entries) {
238 		if (line->src == SRC_INP) {
239 			p = ifetch(line->pos.lineno, 0);
240 			/* Note: string is not NUL terminated. */
241 			for (; *p != '\n'; p++)
242 				if (line->subst != 0)
243 					line->subst--;
244 				else
245 					putc(*p, ofp);
246 			putc('\n', ofp);
247 		} else if (line->src == SRC_PCH) {
248 			fseeko(pfp, line->pos.seek, SEEK_SET);
249 			if (pgets(buf, sizeof buf, pfp) == NULL)
250 				fatal("unexpected end of file");
251 			p = buf;
252 			if (line->subst != 0)
253 				for (; *p != '\0' && *p != '\n'; p++)
254 					if (line->subst-- == 0)
255 						break;
256 			fputs(p, ofp);
257 			if (strchr(p, '\n') == NULL)
258 				putc('\n', ofp);
259 		}
260 	}
261 	fclose(ofp);
262 
263 	/* restore patch file position to match p_input_line */
264 	fseeko(pfp, linepos, SEEK_SET);
265 }
266 
267 /* initialize list with input file */
268 static void
269 init_lines(void)
270 {
271 	struct ed_line *line;
272 	LINENUM i;
273 
274 	LIST_INIT(&head);
275 	for (i = input_lines; i > 0; i--) {
276 		line = malloc(sizeof(*line));
277 		if (line == NULL)
278 			fatal("cannot allocate memory");
279 		line->src = SRC_INP;
280 		line->subst = 0;
281 		line->pos.lineno = i;
282 		LIST_INSERT_HEAD(&head, line, entries);
283 	}
284 	line_count = input_lines;
285 }
286 
287 static void
288 free_lines(void)
289 {
290 	struct ed_line *line;
291 
292 	while (!LIST_EMPTY(&head)) {
293 		line = LIST_FIRST(&head);
294 		LIST_REMOVE(line, entries);
295 		free(line);
296 	}
297 }
298 
299 static struct ed_line *
300 get_line(LINENUM lineno)
301 {
302 	struct ed_line *line;
303 	LINENUM i;
304 
305 	if (lineno == 0)
306 		return NULL;
307 
308 	i = 0;
309 	LIST_FOREACH(line, &head, entries)
310 		if (++i == lineno)
311 			return line;
312 
313 	return NULL;
314 }
315 
316 static struct ed_line *
317 create_line(off_t seek)
318 {
319 	struct ed_line *line;
320 
321 	line = malloc(sizeof(*line));
322 	if (line == NULL)
323 		fatal("cannot allocate memory");
324 	line->src = SRC_PCH;
325 	line->subst = 0;
326 	line->pos.seek = seek;
327 
328 	return line;
329 }
330 
331 static int
332 valid_addr(LINENUM lineno, LINENUM min)
333 {
334 	return lineno >= min && lineno <= line_count;
335 }
336