Edit

IABSD.fr/src/usr.bin/patch/ed.c

Branch :

  • Show log

    Commit

  • Author : jca
    Date : 2019-12-02 22:17:32
    Hash : 5fbd5e42
    Message : Use getline(3) to handle lines longer than 8192 bytes in patch files Spotted by jsg@ when working on mesa. Diff tested by sthen@ in a partial i386 bulk. Input from and ok jsg@ millert@

  • usr.bin/patch/ed.c
  • /*	$OpenBSD: ed.c,v 1.4 2019/12/02 22:17:32 jca Exp $ */
    
    /*
     * Copyright (c) 2015 Tobias Stoeckmann <tobias@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/queue.h>
    #include <sys/stat.h>
    
    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include "common.h"
    #include "util.h"
    #include "pch.h"
    #include "inp.h"
    
    /* states of finite state machine */
    #define FSM_CMD		1
    #define FSM_A		2
    #define FSM_C		3
    #define FSM_D		4
    #define FSM_I		5
    #define FSM_S		6
    
    #define SRC_INP		1	/* line's origin is input file */
    #define SRC_PCH		2	/* line's origin is patch file */
    
    #define S_PATTERN	"/.//"
    
    static void		init_lines(void);
    static void		free_lines(void);
    static struct ed_line	*get_line(LINENUM);
    static struct ed_line	*create_line(off_t);
    static int		valid_addr(LINENUM, LINENUM);
    static int		get_command(void);
    static void		write_lines(char *);
    
    LIST_HEAD(ed_head, ed_line) head;
    struct ed_line {
    	LIST_ENTRY(ed_line)	entries;
    	int			src;
    	unsigned long		subst;
    	union {
    		LINENUM		lineno;
    		off_t		seek;
    	} pos;
    };
    
    static LINENUM		first_addr;
    static LINENUM		second_addr;
    static LINENUM		line_count;
    static struct ed_line	*cline;		/* current line */
    
    void
    do_ed_script(void)
    {
    	off_t linepos;
    	struct ed_line *nline;
    	LINENUM i, range;
    	int fsm;
    
    	init_lines();
    	cline = NULL;
    	fsm = FSM_CMD;
    
    	for (;;) {
    		linepos = ftello(pfp);
    		if (pgetline(&buf, &bufsz, pfp) == -1)
    			break;
    		p_input_line++;
    
    		if (fsm == FSM_CMD) {
    			if ((fsm = get_command()) == -1)
    				break;
    
    			switch (fsm) {
    			case FSM_C:
    			case FSM_D:
    				/* delete lines in specified range */
    				if (second_addr == -1)
    					range = 1;
    				else
    					range = second_addr - first_addr + 1;
    				for (i = 0; i < range; i++) {
    					nline = LIST_NEXT(cline, entries);
    					LIST_REMOVE(cline, entries);
    					free(cline);
    					cline = nline;
    					line_count--;
    				}
    				cline = get_line(first_addr - 1);
    				fsm = (fsm == FSM_C) ? FSM_A : FSM_CMD;
    				break;
    			case FSM_S:
    				cline->subst++;
    				fsm = FSM_CMD;
    				break;
    			default:
    				break;
    			}
    
    			continue;
    		}
    
    		if (strcmp(buf, ".\n") == 0) {
    			fsm = FSM_CMD;
    			continue;
    		}
    
    		nline = create_line(linepos);
    		if (cline == NULL)
    			LIST_INSERT_HEAD(&head, nline, entries);
    		else if (fsm == FSM_A)
    			LIST_INSERT_AFTER(cline, nline, entries);
    		else
    			LIST_INSERT_BEFORE(cline, nline, entries);
    		cline = nline;
    		line_count++;
    		fsm = FSM_A;
    	}
    
    	next_intuit_at(linepos, p_input_line);
    
    	if (skip_rest_of_patch) {
    		free_lines();
    		return;
    	}
    
    	write_lines(TMPOUTNAME);
    	free_lines();
    
    	ignore_signals();
    	if (!check_only) {
    		if (move_file(TMPOUTNAME, outname) < 0) {
    			toutkeep = true;
    			chmod(TMPOUTNAME, filemode);
    		} else
    			chmod(outname, filemode);
    	}
    	set_signals(1);
    }
    
    static int
    get_command(void)
    {
    	char *p;
    	LINENUM min_addr;
    	int fsm;
    
    	min_addr = 0;
    	fsm = -1;
    	p = buf;
    
    	/* maybe garbage encountered at end of patch */
    	if (!isdigit((unsigned char)*p))
    		return -1;
    
    	first_addr = strtolinenum(buf, &p);
    	second_addr = (*p == ',') ? strtolinenum(p + 1, &p) : -1;
    
    	switch (*p++) {
    	case 'a':
    		if (second_addr != -1)
    			fatal("invalid address at line %ld: %s",
    			    p_input_line, buf);
    		fsm = FSM_A;
    		break;
    	case 'c':
    		fsm = FSM_C;
    		min_addr = 1;
    		break;
    	case 'd':
    		fsm = FSM_D;
    		min_addr = 1;
    		break;
    	case 'i':
    		if (second_addr != -1)
    			fatal("invalid address at line %ld: %s",
    			    p_input_line, buf);
    		fsm = FSM_I;
    		break;
    	case 's':
    		if (second_addr != -1)
    			fatal("unsupported address range at line %ld: %s",
    			    p_input_line, buf);
    		if (strncmp(p, S_PATTERN, sizeof(S_PATTERN) - 1) != 0)
    			fatal("unsupported substitution at "
    			    "line %ld: %s", p_input_line, buf);
    		p += sizeof(S_PATTERN) - 1;
    		fsm = FSM_S;
    		min_addr = 1;
    		break;
    	default:
    		return -1;
    		/* NOTREACHED */
    	}
    
    	if (*p != '\n')
    		return -1;
    
    	if (!valid_addr(first_addr, min_addr) ||
    	    (second_addr != -1 && !valid_addr(second_addr, first_addr)))
    		fatal("invalid address at line %ld: %s", p_input_line, buf);
    
    	cline = get_line(first_addr);
    
    	return fsm;
    }
    
    static void
    write_lines(char *filename)
    {
    	FILE *ofp;
    	char *p;
    	struct ed_line *line;
    	off_t linepos;
    
    	linepos = ftello(pfp);
    	ofp = fopen(filename, "w");
    	if (ofp == NULL)
    		pfatal("can't create %s", filename);
    
    	LIST_FOREACH(line, &head, entries) {
    		if (line->src == SRC_INP) {
    			p = ifetch(line->pos.lineno, 0);
    			/* Note: string is not NUL terminated. */
    			for (; *p != '\n'; p++)
    				if (line->subst != 0)
    					line->subst--;
    				else
    					putc(*p, ofp);
    			putc('\n', ofp);
    		} else if (line->src == SRC_PCH) {
    			fseeko(pfp, line->pos.seek, SEEK_SET);
    			if (pgetline(&buf, &bufsz, pfp) == -1)
    				fatal("unexpected end of file");
    			p = buf;
    			if (line->subst != 0)
    				for (; *p != '\0' && *p != '\n'; p++)
    					if (line->subst-- == 0)
    						break;
    			fputs(p, ofp);
    			if (strchr(p, '\n') == NULL)
    				putc('\n', ofp);
    		}
    	}
    	fclose(ofp);
    
    	/* restore patch file position to match p_input_line */
    	fseeko(pfp, linepos, SEEK_SET);
    }
    
    /* initialize list with input file */
    static void
    init_lines(void)
    {
    	struct ed_line *line;
    	LINENUM i;
    
    	LIST_INIT(&head);
    	for (i = input_lines; i > 0; i--) {
    		line = malloc(sizeof(*line));
    		if (line == NULL)
    			fatal("cannot allocate memory");
    		line->src = SRC_INP;
    		line->subst = 0;
    		line->pos.lineno = i;
    		LIST_INSERT_HEAD(&head, line, entries);
    	}
    	line_count = input_lines;
    }
    
    static void
    free_lines(void)
    {
    	struct ed_line *line;
    
    	while (!LIST_EMPTY(&head)) {
    		line = LIST_FIRST(&head);
    		LIST_REMOVE(line, entries);
    		free(line);
    	}
    }
    
    static struct ed_line *
    get_line(LINENUM lineno)
    {
    	struct ed_line *line;
    	LINENUM i;
    
    	if (lineno == 0)
    		return NULL;
    
    	i = 0;
    	LIST_FOREACH(line, &head, entries)
    		if (++i == lineno)
    			return line;
    
    	return NULL;
    }
    
    static struct ed_line *
    create_line(off_t seek)
    {
    	struct ed_line *line;
    
    	line = malloc(sizeof(*line));
    	if (line == NULL)
    		fatal("cannot allocate memory");
    	line->src = SRC_PCH;
    	line->subst = 0;
    	line->pos.seek = seek;
    
    	return line;
    }
    
    static int
    valid_addr(LINENUM lineno, LINENUM min)
    {
    	return lineno >= min && lineno <= line_count;
    }