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
45static void		init_lines(void);
46static void		free_lines(void);
47static struct ed_line	*get_line(LINENUM);
48static struct ed_line	*create_line(off_t);
49static int		valid_addr(LINENUM, LINENUM);
50static int		get_command(void);
51static void		write_lines(char *);
52
53LIST_HEAD(ed_head, ed_line) head;
54struct 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
64static LINENUM		first_addr;
65static LINENUM		second_addr;
66static LINENUM		line_count;
67static struct ed_line	*cline;		/* current line */
68
69void
70do_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
158static int
159get_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
225static void
226write_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 */
269static void
270init_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
288static void
289free_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
300static struct ed_line *
301get_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
317static struct ed_line *
318create_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
332static int
333valid_addr(LINENUM lineno, LINENUM min)
334{
335	return lineno >= min && lineno <= line_count;
336}
337