root/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;
}