master xplshn/aruu / cmd / posix / sed.c
   1
   2
   3/* FIXME: summary
   4 * decide whether we enforce valid UTF-8, right now it's enforced in certain
   5 *     parts of the script, but not the input...
   6 * nul bytes cause explosions due to use of libc string functions. thoughts?
   7 * lack of newline at end of file, currently we add one. what should we do?
   8 * allow "\\t" for "\t" etc. in regex? in replacement text?
   9 * POSIX says don't flush on N when out of input, but GNU and busybox do.
  10 */
  11
  12#include "config.h"
  13#include "utf.h"
  14#include "util.h"
  15
  16#include <ctype.h>
  17#include <errno.h>
  18#include <libgen.h>
  19#include <regex.h>
  20#include <stdlib.h>
  21#include <string.h>
  22#include <sys/stat.h>
  23#include <unistd.h>
  24
  25/* Types */
  26
  27/* used as queue for writes and stack for {,:,b,t */
  28typedef struct {
  29  void **data;
  30  size_t size;
  31  size_t cap;
  32} Vec;
  33
  34/* used for arbitrary growth, str is a C string
  35 * FIXME: does it make sense to keep track of length? or just rely on libc
  36 *        string functions? If we want to support nul bytes everything changes
  37 */
  38typedef struct {
  39  char  *str;
  40  size_t cap;
  41} String;
  42
  43typedef struct Cmd Cmd;
  44typedef struct {
  45  void (*fn)(Cmd *);
  46  char *(*getarg)(Cmd *, char *);
  47  void (*freearg)(Cmd *);
  48  unsigned char naddr;
  49} Fninfo;
  50
  51typedef struct {
  52  union {
  53    size_t   lineno;
  54    regex_t *re;
  55  } u;
  56  enum {
  57    IGNORE, /* empty address, ignore        */
  58    EVERY,  /* every line                   */
  59    LINE,   /* line number                  */
  60    LAST,   /* last line ($)                */
  61    REGEX,  /* use included regex           */
  62    LASTRE, /* use most recently used regex */
  63  } type;
  64} Addr;
  65
  66/* DISCUSS: naddr is not strictly necessary, but very helpful
  67 * naddr == 0 iff beg.type == EVERY  && end.type == IGNORE
  68 * naddr == 1 iff beg.type != IGNORE && end.type == IGNORE
  69 * naddr == 2 iff beg.type != IGNORE && end.type != IGNORE
  70 */
  71typedef struct {
  72  Addr          beg;
  73  Addr          end;
  74  unsigned char naddr;
  75} Range;
  76
  77typedef struct {
  78  regex_t     *re; /* if NULL use last regex */
  79  String       repl;
  80  FILE        *file;
  81  size_t       occurrence; /* 0 for all (g flag) */
  82  Rune         delim;
  83  unsigned int p : 1;
  84} Sarg;
  85
  86typedef struct {
  87  Rune *set1;
  88  Rune *set2;
  89} Yarg;
  90
  91typedef struct {
  92  String str;                    /* a,c,i text. r file path */
  93  void (*print)(char *, FILE *); /* check_puts for a, write_file for r,
  94            unused for c,i */
  95} ACIRarg;
  96
  97struct Cmd {
  98  Range   range;
  99  Fninfo *fninfo;
 100  union {
 101    Cmd      *jump;   /* used for   b,t when running  */
 102    char     *label;  /* used for :,b,t when building */
 103    ptrdiff_t offset; /* used for { (pointers break during realloc) */
 104    FILE     *file;   /* used for w */
 105
 106    /* FIXME: Should the following be in the union? or pointers and
 107     * malloc? */
 108    Sarg    s;
 109    Yarg    y;
 110    ACIRarg acir;
 111  } u; /* I find your lack of anonymous unions disturbing */
 112  unsigned int in_match : 1;
 113  unsigned int negate : 1;
 114};
 115
 116/* Files for w command (and s' w flag) */
 117typedef struct {
 118  char *path;
 119  FILE *file;
 120} Wfile;
 121
 122/*
 123 * Function Declarations
 124 */
 125
 126/* Dynamically allocated arrays and strings */
 127static void  resize(void **ptr, size_t *nmemb, size_t size, size_t new_nmemb, void **next);
 128static void *pop(Vec *v);
 129static void  push(Vec *v, void *p);
 130static void  stracat(String *dst, char *src);
 131static void  strnacat(String *dst, char *src, size_t n);
 132static void  stracpy(String *dst, char *src);
 133
 134/* Cleanup and errors */
 135static void usage(void);
 136
 137/* Parsing functions and related utilities */
 138static void   compile(char *s, int isfile);
 139static int    read_line(FILE *f, String *s);
 140static char  *make_range(Range *range, char *s);
 141static char  *make_addr(Addr *addr, char *s);
 142static char  *find_delim(char *s, Rune delim, int do_brackets);
 143static char  *chompr(char *s, Rune rune);
 144static char  *chomp(char *s);
 145static Rune  *strtorunes(char *s, size_t nrunes);
 146static long   stol(char *s, char **endp);
 147static size_t escapes(char *beg, char *end, Rune delim, int n_newline);
 148static size_t echarntorune(Rune *r, char *s, size_t n);
 149static void   insert_labels(void);
 150
 151/* Get and Free arg and related utilities */
 152static char *get_aci_arg(Cmd *c, char *s);
 153static void  aci_append(Cmd *c, char *s);
 154static void  free_acir_arg(Cmd *c);
 155static char *get_bt_arg(Cmd *c, char *s);
 156static char *get_r_arg(Cmd *c, char *s);
 157static char *get_s_arg(Cmd *c, char *s);
 158static void  free_s_arg(Cmd *c);
 159static char *get_w_arg(Cmd *c, char *s);
 160static char *get_y_arg(Cmd *c, char *s);
 161static void  free_y_arg(Cmd *c);
 162static char *get_colon_arg(Cmd *c, char *s);
 163static char *get_lbrace_arg(Cmd *c, char *s);
 164static char *get_rbrace_arg(Cmd *c, char *s);
 165static char *semicolon_arg(char *s);
 166
 167/* Running */
 168static void run(void);
 169static int  in_range(Cmd *c);
 170static int  match_addr(Addr *a);
 171static int  next_file(void);
 172static int  is_eof(FILE *f);
 173static void do_writes(void);
 174static void write_file(char *path, FILE *out);
 175static void check_puts(char *s, FILE *f);
 176static void write_patt(char *s, FILE *f);
 177static void update_ranges(Cmd *beg, Cmd *end);
 178
 179/* Sed functions */
 180static void cmd_y(Cmd *c);
 181static void cmd_x(Cmd *c);
 182static void cmd_w(Cmd *c);
 183static void cmd_t(Cmd *c);
 184static void cmd_s(Cmd *c);
 185static void cmd_r(Cmd *c);
 186static void cmd_q(Cmd *c);
 187static void cmd_P(Cmd *c);
 188static void cmd_p(Cmd *c);
 189static void cmd_N(Cmd *c);
 190static void cmd_n(Cmd *c);
 191static void cmd_l(Cmd *c);
 192static void cmd_i(Cmd *c);
 193static void cmd_H(Cmd *c);
 194static void cmd_h(Cmd *c);
 195static void cmd_G(Cmd *c);
 196static void cmd_g(Cmd *c);
 197static void cmd_D(Cmd *c);
 198static void cmd_d(Cmd *c);
 199static void cmd_c(Cmd *c);
 200static void cmd_b(Cmd *c);
 201static void cmd_a(Cmd *c);
 202static void cmd_colon(Cmd *c);
 203static void cmd_equal(Cmd *c);
 204static void cmd_lbrace(Cmd *c);
 205static void cmd_rbrace(Cmd *c);
 206static void cmd_last(Cmd *c);
 207
 208/* Actions */
 209static void new_line(void);
 210static void app_line(void);
 211static void new_next(void);
 212static void old_next(void);
 213
 214/*
 215 * Globals
 216 */
 217static Vec braces, labels, branches; /* holds ptrdiff_t. addrs of {, :, bt */
 218static Vec writes;                   /* holds cmd*. writes scheduled by a and r commands */
 219static Vec wfiles;                   /* holds Wfile*. files for w and s///w commands */
 220
 221static Cmd   *prog, *pc; /* Program, program counter */
 222static size_t pcap;
 223static size_t lineno;
 224#if FEATURE_SED_PRESERVE_NEWLINE
 225static int hadnl = 1;
 226#endif
 227
 228static regex_t *lastre; /* last used regex for empty regex search */
 229static char   **files;  /* list of file names from argv */
 230static FILE    *file;   /* current file we are reading */
 231static int      ret;    /* exit status */
 232
 233static String patt, hold, genbuf;
 234
 235static struct {
 236  unsigned int n : 1;        /* -n (no print) */
 237  unsigned int E : 1;        /* -E (extended re) */
 238  unsigned int s : 1;        /* s/// replacement happened */
 239  unsigned int aci_cont : 1; /* a,c,i text continuation */
 240  unsigned int s_cont : 1;   /* s/// replacement text continuation */
 241  unsigned int halt : 1;     /* halt execution */
 242} gflags;
 243
 244/* FIXME: move character inside Fninfo and only use 26*sizeof(Fninfo) instead of
 245 * 127*sizeof(Fninfo) bytes */
 246static Fninfo fns[] = {
 247    ['a'] = {cmd_a, get_aci_arg, free_acir_arg, 1}, /* schedule write of text for later */
 248    ['b'] = {cmd_b, get_bt_arg, NULL, 2}, /* branch to label char *label when building, Cmd *jump
 249                                       when running                     */
 250    ['c'] = {cmd_c, get_aci_arg, free_acir_arg, 2}, /* delete pattern space. at 0 or 1 addr or end
 251                                                 of 2 addr, write text                     */
 252    ['d'] = {cmd_d, NULL, NULL, 2},                 /* delete pattern space */
 253    ['D'] = {cmd_D, NULL, NULL, 2}, /* delete to first newline and start new cycle without
 254                                 reading (if no newline, d)        */
 255    ['g'] = {cmd_g, NULL, NULL, 2}, /* replace pattern space with hold space */
 256    ['G'] = {cmd_G, NULL, NULL, 2}, /* append newline and hold space to pattern space */
 257    ['h'] = {cmd_h, NULL, NULL, 2}, /* replace hold space with pattern space */
 258    ['H'] = {cmd_H, NULL, NULL, 2}, /* append newline and pattern space to hold space */
 259    ['i'] = {cmd_i, get_aci_arg, free_acir_arg, 1}, /* write text */
 260    ['l'] = {cmd_l, NULL, NULL, 2}, /* write pattern space in 'visually unambiguous form' */
 261    ['n'] = {cmd_n, NULL, NULL, 2}, /* write pattern space (unless -n) read to replace pattern
 262                                 space (if no input, quit)     */
 263    ['N'] = {cmd_N, NULL, NULL, 2}, /* append to pattern space separated by newline, line
 264                                 number changes (if no input, quit) */
 265    ['p'] = {cmd_p, NULL, NULL, 2}, /* write pattern space */
 266    ['P'] = {cmd_P, NULL, NULL, 2}, /* write pattern space up to first newline */
 267    ['q'] = {cmd_q, NULL, NULL, 1}, /* quit */
 268    ['r'] = {cmd_r, get_r_arg, free_acir_arg, 1}, /* write contents of file (unable to open/read
 269                                               treated as empty file)                    */
 270    ['s'] = {cmd_s, get_s_arg, free_s_arg, 2},    /* find/replace/all that crazy s stuff */
 271    ['t'] = {cmd_t, get_bt_arg, NULL, 2}, /* if s/// succeeded (since input or last t) branch to
 272                                       label (branch to end if no label) */
 273    ['w'] = {cmd_w, get_w_arg, NULL, 2},  /* append pattern space to file */
 274    ['x'] = {cmd_x, NULL, NULL, 2},       /* exchange pattern and hold spaces */
 275    ['y'] = {cmd_y, get_y_arg, free_y_arg, 2},     /* replace runes in set1 with runes in set2 */
 276    [':'] = {cmd_colon, get_colon_arg, NULL, 0},   /* defines label for later b and t commands */
 277    ['='] = {cmd_equal, NULL, NULL, 1},            /* printf("%d\n", line_number); */
 278    ['{'] = {cmd_lbrace, get_lbrace_arg, NULL, 2}, /* if we match, run commands, otherwise jump to
 279                                                      close */
 280    ['}'] = {cmd_rbrace, get_rbrace_arg, NULL, 0}, /* noop, hold onto open for ease of building
 281                                                      scripts */
 282
 283    [0x7f] = {NULL, NULL, NULL, 0}, /* index is checked with isascii(3p).
 284               fill out rest of array */
 285};
 286
 287/*
 288 * Function Definitions
 289 */
 290
 291/* given memory pointed to by *ptr that currently holds *nmemb members of size
 292 * size, realloc to hold new_nmemb members, return new_nmemb in *memb and one
 293 * past old end in *next. if realloc fails...explode
 294 */
 295static void
 296resize(void **ptr, size_t *nmemb, size_t size, size_t new_nmemb, void **next)
 297{
 298  void *n, *tmp;
 299
 300  if (new_nmemb) {
 301    tmp = ereallocarray(*ptr, new_nmemb, size);
 302  } else { /* turns out realloc(*ptr, 0) != free(*ptr) */
 303    free(*ptr);
 304    tmp = NULL;
 305  }
 306  n      = (char *)tmp + *nmemb * size;
 307  *nmemb = new_nmemb;
 308  *ptr   = tmp;
 309  if (next)
 310    *next = n;
 311}
 312
 313static void *
 314pop(Vec *v)
 315{
 316  if (!v->size)
 317    return NULL;
 318  return v->data[--v->size];
 319}
 320
 321static void
 322push(Vec *v, void *p)
 323{
 324  if (v->size == v->cap)
 325    resize((void **)&v->data, &v->cap, sizeof(*v->data), v->cap * 2 + 1, NULL);
 326  v->data[v->size++] = p;
 327}
 328
 329static void
 330stracat(String *dst, char *src)
 331{
 332  int new = !dst->cap;
 333  size_t len;
 334
 335  len = (new ? 0 : strlen(dst->str)) + strlen(src) + 1;
 336  if (dst->cap < len)
 337    resize((void **)&dst->str, &dst->cap, 1, len * 2, NULL);
 338  if (new)
 339    *dst->str = '\0';
 340  strcat(dst->str, src);
 341}
 342
 343static void
 344strnacat(String *dst, char *src, size_t n)
 345{
 346  int new = !dst->cap;
 347  size_t len;
 348
 349  len = strlen(src);
 350  len = (new ? 0 : strlen(dst->str)) + MIN(n, len) + 1;
 351  if (dst->cap < len)
 352    resize((void **)&dst->str, &dst->cap, 1, len * 2, NULL);
 353  if (new)
 354    *dst->str = '\0';
 355  strlcat(dst->str, src, len);
 356}
 357
 358static void
 359stracpy(String *dst, char *src)
 360{
 361  size_t len;
 362
 363  len = strlen(src) + 1;
 364  if (dst->cap < len)
 365    resize((void **)&dst->str, &dst->cap, 1, len * 2, NULL);
 366  strcpy(dst->str, src);
 367}
 368
 369static void
 370leprintf(char *s)
 371{
 372  if (errno)
 373    eprintf("%zu: %s: %s\n", lineno, s, strerror(errno));
 374  else
 375    eprintf("%zu: %s\n", lineno, s);
 376}
 377
 378/* FIXME: write usage message */
 379#if FEATURE_SED_INPLACE
 380static int   iflag         = 0;
 381static char *backup_suffix = NULL;
 382
 383static int
 384create_temp_file(const char *orig_path, char **temp_path)
 385{
 386  char *dir, *dircopy, *tmpl;
 387  int   fd;
 388
 389  dircopy = estrdup(orig_path);
 390  dir     = dirname(dircopy);
 391  tmpl    = emalloc(strlen(dir) + 16);
 392  sprintf(tmpl, "%s/sedtmpXXXXXX", dir);
 393  free(dircopy);
 394
 395  fd = mkstemp(tmpl);
 396  if (fd < 0) {
 397    free(tmpl);
 398    return -1;
 399  }
 400  *temp_path = tmpl;
 401  return fd;
 402}
 403#endif
 404
 405static void
 406usage(void)
 407{
 408  eprintf(
 409      "usage: sed [-nrE] script [file ...]\n"
 410      "       sed [-nrE] -e script [-e script] ... [-f scriptfile] "
 411      "... [file ...]\n"
 412      "       sed [-nrE] [-e script] ... -f scriptfile [-f "
 413      "scriptfile] ... [file ...]\n"
 414  );
 415}
 416
 417/* Differences from POSIX
 418 * we allows semicolons and trailing blanks inside {}
 419 * we allow spaces after ! (and in between !s)
 420 * we allow extended regular expressions (-E)
 421 */
 422static void
 423compile(char *s, int isfile)
 424{
 425  FILE *f;
 426
 427  if (isfile) {
 428    f = fopen(s, "r");
 429    if (!f)
 430      eprintf("fopen %s:", s);
 431  } else {
 432    if (!*s) /* empty string script */
 433      return;
 434    f = fmemopen(s, strlen(s), "r");
 435    if (!f)
 436      eprintf("fmemopen:");
 437  }
 438
 439  /* NOTE: get arg functions can't use genbuf */
 440  while (read_line(f, &genbuf) != EOF) {
 441    s = genbuf.str;
 442
 443    /* if the first two characters of the script are "#n" default
 444     * output shall be suppressed */
 445    if (++lineno == 1 && *s == '#' && s[1] == 'n') {
 446      gflags.n = 1;
 447      continue;
 448    }
 449
 450    if (gflags.aci_cont) {
 451      aci_append(pc - 1, s);
 452      continue;
 453    }
 454    if (gflags.s_cont)
 455      s = (pc - 1)->fninfo->getarg(pc - 1, s);
 456
 457    while (*s) {
 458      s = chompr(s, ';');
 459      if (!*s || *s == '#')
 460        break;
 461
 462      if ((size_t)(pc - prog) == pcap)
 463        resize((void **)&prog, &pcap, sizeof(*prog), pcap * 2 + 1, (void **)&pc);
 464
 465      pc->range.beg.type = pc->range.end.type = IGNORE;
 466      pc->fninfo                              = NULL;
 467      pc->in_match                            = 0;
 468
 469      s          = make_range(&pc->range, s);
 470      s          = chomp(s);
 471      pc->negate = *s == '!';
 472      s          = chompr(s, '!');
 473
 474      if (!isascii(*s) || !(pc->fninfo = &fns[(unsigned)*s])->fn)
 475        leprintf("bad sed function");
 476      if (pc->range.naddr > pc->fninfo->naddr)
 477        leprintf("wrong number of addresses");
 478      s++;
 479
 480      if (pc->fninfo->getarg)
 481        s = pc->fninfo->getarg(pc, s);
 482
 483      pc++;
 484    }
 485  }
 486
 487  fshut(f, s);
 488}
 489
 490/* FIXME: if we decide to honor lack of trailing newline, set/clear a global
 491 * flag when reading a line
 492 */
 493static int
 494read_line(FILE *f, String *s)
 495{
 496  ssize_t len;
 497
 498  if (!f)
 499    return EOF;
 500
 501  if ((len = getline(&s->str, &s->cap, f)) < 0) {
 502    if (ferror(f))
 503      eprintf("getline:");
 504    return EOF;
 505  }
 506#if FEATURE_SED_PRESERVE_NEWLINE
 507  if (len > 0)
 508    hadnl = (s->str[len - 1] == '\n');
 509#endif
 510  if (s->str[--len] == '\n')
 511    s->str[len] = '\0';
 512  return 0;
 513}
 514
 515/* read first range from s, return pointer to one past end of range */
 516static char *
 517make_range(Range *range, char *s)
 518{
 519  s = make_addr(&range->beg, s);
 520
 521  if (*s == ',')
 522    s = make_addr(&range->end, s + 1);
 523  else
 524    range->end.type = IGNORE;
 525
 526  if (range->beg.type == EVERY && range->end.type == IGNORE)
 527    range->naddr = 0;
 528  else if (range->beg.type != IGNORE && range->end.type == IGNORE)
 529    range->naddr = 1;
 530  else if (range->beg.type != IGNORE && range->end.type != IGNORE)
 531    range->naddr = 2;
 532  else
 533    leprintf("this is impossible...");
 534
 535  return s;
 536}
 537
 538/* read first addr from s, return pointer to one past end of addr */
 539static char *
 540make_addr(Addr *addr, char *s)
 541{
 542  Rune   r;
 543  char  *p    = s + strlen(s);
 544  size_t rlen = echarntorune(&r, s, p - s);
 545
 546  if (r == '$') {
 547    addr->type = LAST;
 548    s += rlen;
 549  } else if (isdigitrune(r)) {
 550    addr->type     = LINE;
 551    addr->u.lineno = stol(s, &s);
 552  } else if (r == '/' || r == '\\') {
 553    Rune delim;
 554    if (r == '\\') {
 555      s += rlen;
 556      rlen = echarntorune(&r, s, p - s);
 557    }
 558    if (r == '\\')
 559      leprintf("bad delimiter '\\'");
 560    delim = r;
 561    s += rlen;
 562    rlen = echarntorune(&r, s, p - s);
 563    if (r == delim) {
 564      addr->type = LASTRE;
 565      s += rlen;
 566    } else {
 567      addr->type = REGEX;
 568      p          = find_delim(s, delim, 1);
 569      if (!*p)
 570        leprintf("unclosed regex");
 571      p -= escapes(s, p, delim, 0);
 572      *p++       = '\0';
 573      addr->u.re = emalloc(sizeof(*addr->u.re));
 574      eregcomp(addr->u.re, s, gflags.E ? REG_EXTENDED : 0);
 575      s = p;
 576    }
 577  } else {
 578    addr->type = EVERY;
 579  }
 580
 581  return s;
 582}
 583
 584/* return pointer to first delim in s that is not escaped
 585 * and if do_brackets is set, not in [] (note possible [::], [..], [==], inside
 586 * []) return pointer to trailing nul byte if no delim found
 587 *
 588 * any escaped character that is not special is just itself (POSIX undefined)
 589 * FIXME: pull out into some util thing, will be useful for ed as well
 590 */
 591static char *
 592find_delim(char *s, Rune delim, int do_brackets)
 593{
 594  enum {
 595    OUTSIDE,          /* not in brackets */
 596    BRACKETS_OPENING, /* last char was first [ or last two were
 597             first [^ */
 598    BRACKETS_INSIDE,  /* inside [] */
 599    INSIDE_OPENING,   /* inside [] and last char was [ */
 600    CLASS_INSIDE,     /* inside class [::], or colating element [..] or
 601             [==], inside [] */
 602    CLASS_CLOSING,    /* inside class [::], or colating element [..] or
 603             [==], and last character was the respective :
 604             . or = */
 605  } state = OUTSIDE;
 606
 607  Rune   r, c = 0; /* no c won't be used uninitialized, shutup -Wall */
 608  size_t rlen;
 609  int    escape = 0;
 610  char  *end    = s + strlen(s);
 611
 612  for (; *s; s += rlen) {
 613    rlen = echarntorune(&r, s, end - s);
 614
 615    if (state == BRACKETS_OPENING && r == '^') {
 616      continue;
 617    } else if (state == BRACKETS_OPENING && r == ']') {
 618      state = BRACKETS_INSIDE;
 619      continue;
 620    } else if (state == BRACKETS_OPENING) {
 621      state = BRACKETS_INSIDE;
 622    }
 623
 624    if (state == CLASS_CLOSING && r == ']') {
 625      state = BRACKETS_INSIDE;
 626    } else if (state == CLASS_CLOSING) {
 627      state = CLASS_INSIDE;
 628    } else if (state == CLASS_INSIDE && r == c) {
 629      state = CLASS_CLOSING;
 630    } else if (state == INSIDE_OPENING && (r == ':' || r == '.' || r == '=')) {
 631      state = CLASS_INSIDE;
 632      c     = r;
 633    } else if (state == INSIDE_OPENING && r == ']') {
 634      state = OUTSIDE;
 635    } else if (state == INSIDE_OPENING) {
 636      state = BRACKETS_INSIDE;
 637    } else if (state == BRACKETS_INSIDE && r == '[') {
 638      state = INSIDE_OPENING;
 639    } else if (state == BRACKETS_INSIDE && r == ']') {
 640      state = OUTSIDE;
 641    } else if (state == OUTSIDE && escape) {
 642      escape = 0;
 643    } else if (state == OUTSIDE && r == '\\') {
 644      escape = 1;
 645    } else if (state == OUTSIDE && r == delim)
 646      return s;
 647    else if (state == OUTSIDE && do_brackets && r == '[') {
 648      state = BRACKETS_OPENING;
 649    }
 650  }
 651  return s;
 652}
 653
 654static char *
 655chomp(char *s)
 656{
 657  return chompr(s, 0);
 658}
 659
 660/* eat all leading whitespace and occurrences of rune */
 661static char *
 662chompr(char *s, Rune rune)
 663{
 664  Rune   r;
 665  size_t rlen;
 666  char  *end = s + strlen(s);
 667
 668  while (*s && (rlen = echarntorune(&r, s, end - s)) && (isspacerune(r) || r == rune))
 669    s += rlen;
 670  return s;
 671}
 672
 673/* convert first nrunes Runes from UTF-8 string s in allocated Rune*
 674 * NOTE: sequence must be valid UTF-8, check first */
 675static Rune *
 676strtorunes(char *s, size_t nrunes)
 677{
 678  Rune *rs, *rp;
 679
 680  rp = rs = ereallocarray(NULL, nrunes + 1, sizeof(*rs));
 681
 682  while (nrunes--)
 683    s += chartorune(rp++, s);
 684
 685  *rp = '\0';
 686  return rs;
 687}
 688
 689static long
 690stol(char *s, char **endp)
 691{
 692  long n;
 693  errno = 0;
 694  n     = strtol(s, endp, 10);
 695
 696  if (errno)
 697    leprintf("strtol:");
 698  if (*endp == s)
 699    leprintf("strtol: invalid number");
 700
 701  return n;
 702}
 703
 704/* from beg to end replace "\\d" with "d" and "\\n" with "\n" (where d is delim)
 705 * if delim is 'n' and n_newline is 0 then "\\n" is replaced with "n" (normal)
 706 * if delim is 'n' and n_newline is 1 then "\\n" is replaced with "\n" (y
 707 * command) if delim is 0 all escaped characters represent themselves (aci text)
 708 * memmove rest of string (beyond end) into place
 709 * return the number of converted escapes (backslashes removed)
 710 * FIXME: this has had too many corner cases slapped on and is ugly. rewrite
 711 * better
 712 */
 713static size_t
 714escapes(char *beg, char *end, Rune delim, int n_newline)
 715{
 716  size_t num = 0;
 717  char  *src = beg, *dst = beg;
 718
 719  while (src < end) {
 720    /* handle escaped backslash specially so we don't think the
 721     * second backslash is escaping something */
 722    if (*src == '\\' && src[1] == '\\') {
 723      *dst++ = *src++;
 724      if (delim)
 725        *dst++ = *src++;
 726      else
 727        src++;
 728    } else if (*src == '\\' && !delim) {
 729      src++;
 730    } else if (*src == '\\' && src[1]) {
 731      Rune   r;
 732      size_t rlen;
 733      num++;
 734      src++;
 735      rlen = echarntorune(&r, src, end - src);
 736
 737      if (r == 'n' && delim == 'n') {
 738        *src = n_newline ? '\n' : 'n'; /* src so we can still
 739                                    memmove() */
 740      } else if (r == 'n') {
 741        *src = '\n';
 742      } else if (r != delim) {
 743        *dst++ = '\\';
 744        num--;
 745      }
 746
 747      memmove(dst, src, rlen);
 748      dst += rlen;
 749      src += rlen;
 750    } else {
 751      *dst++ = *src++;
 752    }
 753  }
 754  memmove(dst, src, strlen(src) + 1);
 755  return num;
 756}
 757
 758static size_t
 759echarntorune(Rune *r, char *s, size_t n)
 760{
 761  size_t rlen = charntorune(r, s, n);
 762  if (!rlen || *r == Runeerror)
 763    leprintf("invalid UTF-8");
 764  return rlen;
 765}
 766
 767static void
 768insert_labels(void)
 769{
 770  size_t i;
 771  Cmd   *from, *to;
 772
 773  while (branches.size) {
 774    from = prog + (ptrdiff_t)pop(&branches);
 775
 776    if (!from->u.label) { /* no label branch to end of script */
 777      from->u.jump = pc - 1;
 778    } else {
 779      for (i = 0; i < labels.size; i++) {
 780        to = prog + (ptrdiff_t)labels.data[i];
 781        if (!strcmp(from->u.label, to->u.label)) {
 782          from->u.jump = to;
 783          break;
 784        }
 785      }
 786      if (i == labels.size)
 787        leprintf("bad label");
 788    }
 789  }
 790}
 791
 792/*
 793 * Getargs / Freeargs
 794 * Read argument from s, return pointer to one past last character of argument
 795 */
 796
 797/* POSIX compliant
 798 * i\
 799 * foobar
 800 *
 801 * also allow the following non POSIX compliant
 802 * i        # empty line
 803 * ifoobar
 804 * ifoobar\
 805 * baz
 806 *
 807 * FIXME: GNU and busybox discard leading spaces
 808 * i  foobar
 809 * i foobar
 810 * ifoobar
 811 * are equivalent in GNU and busybox. We don't. Should we?
 812 */
 813static char *
 814get_aci_arg(Cmd *c, char *s)
 815{
 816  c->u.acir.print = check_puts;
 817  c->u.acir.str   = (String){NULL, 0};
 818
 819  gflags.aci_cont = !!*s; /* no continue flag if empty string */
 820
 821  /* neither empty string nor POSIX compliant */
 822  if (*s && !(*s == '\\' && !s[1]))
 823    aci_append(c, s);
 824
 825  return s + strlen(s);
 826}
 827
 828static void
 829aci_append(Cmd *c, char *s)
 830{
 831  char *end = s + strlen(s), *p = end;
 832
 833  gflags.aci_cont = 0;
 834  while (--p >= s && *p == '\\')
 835    gflags.aci_cont = !gflags.aci_cont;
 836
 837  if (gflags.aci_cont)
 838    *--end = '\n';
 839
 840  escapes(s, end, 0, 0);
 841  stracat(&c->u.acir.str, s);
 842}
 843
 844static void
 845free_acir_arg(Cmd *c)
 846{
 847  free(c->u.acir.str.str);
 848}
 849
 850/* POSIX dictates that label is rest of line, including semicolons, trailing
 851 * whitespace, closing braces, etc. and can be limited to 8 bytes
 852 *
 853 * I allow a semicolon or closing brace to terminate a label name, it's not
 854 * POSIX compliant, but it's useful and every sed version I've tried to date
 855 * does the same.
 856 *
 857 * FIXME: POSIX dictates that leading whitespace is ignored but trailing
 858 * whitespace is not. This is annoying and we should probably get rid of it.
 859 */
 860static char *
 861get_bt_arg(Cmd *c, char *s)
 862{
 863  char *p = semicolon_arg(s = chomp(s));
 864
 865  if (p != s) {
 866    c->u.label = estrndup(s, p - s);
 867  } else {
 868    c->u.label = NULL;
 869  }
 870
 871  push(&branches, (void *)(c - prog));
 872
 873  return p;
 874}
 875
 876/* POSIX dictates file name is rest of line including semicolons, trailing
 877 * whitespace, closing braces, etc. and file name must be preceded by a space
 878 *
 879 * I allow a semicolon or closing brace to terminate a file name and don't
 880 * enforce leading space.
 881 *
 882 * FIXME: decide whether trailing whitespace should be included and fix
 883 * accordingly
 884 */
 885static char *
 886get_r_arg(Cmd *c, char *s)
 887{
 888  char *p = semicolon_arg(s = chomp(s));
 889
 890  if (p == s)
 891    leprintf("no file name");
 892
 893  c->u.acir.str.str = estrndup(s, p - s);
 894  c->u.acir.print   = write_file;
 895
 896  return p;
 897}
 898
 899/* we allow "\\n" in replacement text to mean "\n" (undefined in POSIX)
 900 *
 901 * FIXME: allow other escapes in regex and replacement? if so change escapes()
 902 */
 903static char *
 904get_s_arg(Cmd *c, char *s)
 905{
 906  Rune  delim, r;
 907  Cmd   buf;
 908  char *p;
 909  int   esc, lastre;
 910
 911  /* s/Find/Replace/Flags */
 912
 913  /* Find */
 914  if (!gflags.s_cont) { /* NOT continuing from literal newline in
 915         replacement text */
 916    lastre            = 0;
 917    c->u.s.repl       = (String){NULL, 0};
 918    c->u.s.occurrence = 1;
 919    c->u.s.file       = NULL;
 920    c->u.s.p          = 0;
 921
 922    if (!*s || *s == '\\')
 923      leprintf("bad delimiter");
 924
 925    p = s + strlen(s);
 926    s += echarntorune(&delim, s, p - s);
 927    c->u.s.delim = delim;
 928
 929    echarntorune(&r, s, p - s);
 930    if (r == delim) /* empty regex */
 931      lastre = 1;
 932
 933    p = find_delim(s, delim, 1);
 934    if (!*p)
 935      leprintf("missing second delimiter");
 936    p -= escapes(s, p, delim, 0);
 937    *p = '\0';
 938
 939    if (lastre) {
 940      c->u.s.re = NULL;
 941    } else {
 942      c->u.s.re = emalloc(sizeof(*c->u.s.re));
 943      /* FIXME: different eregcomp that calls fatal */
 944      eregcomp(c->u.s.re, s, gflags.E ? REG_EXTENDED : 0);
 945    }
 946    s = p + runelen(delim);
 947  }
 948
 949  /* Replace */
 950  delim = c->u.s.delim;
 951
 952  p = find_delim(s, delim, 0);
 953  p -= escapes(s, p, delim, 0);
 954  if (!*p) { /* no third delimiter */
 955    /* FIXME: same backslash counting as aci_append() */
 956    if (p[-1] != '\\')
 957      leprintf(
 958          "missing third delimiter or "
 959          "<backslash><newline>"
 960      );
 961    p[-1]         = '\n';
 962    gflags.s_cont = 1;
 963  } else {
 964    gflags.s_cont = 0;
 965  }
 966
 967  /* check for bad references in replacement text */
 968  *p = '\0';
 969  for (esc = 0, p = s; *p; p++) {
 970    if (esc) {
 971      esc = 0;
 972      if (isdigit(*p) && c->u.s.re && (size_t)(*p - '0') > c->u.s.re->re_nsub)
 973        leprintf(
 974            "back reference number greater than "
 975            "number of groups"
 976        );
 977    } else if (*p == '\\') {
 978      esc = 1;
 979    }
 980  }
 981  stracat(&c->u.s.repl, s);
 982
 983  if (gflags.s_cont)
 984    return p;
 985
 986  s = p + runelen(delim);
 987
 988  /* Flags */
 989  p = semicolon_arg(s = chomp(s));
 990
 991  /* FIXME: currently for simplicity take last of g or occurrence flags
 992   * and ignore multiple p flags. need to fix that */
 993  for (; s < p; s++) {
 994    if (isdigit(*s)) {
 995      c->u.s.occurrence = stol(s, &s);
 996      s--; /* for loop will advance pointer */
 997    } else {
 998      switch (*s) {
 999        case 'g':
1000          c->u.s.occurrence = 0;
1001          break;
1002        case 'p':
1003          c->u.s.p = 1;
1004          break;
1005        case 'w':
1006          /* must be last flag, take everything up to
1007           * newline/semicolon s == p after this */
1008          s           = get_w_arg(&buf, chomp(s + 1));
1009          c->u.s.file = buf.u.file;
1010          break;
1011      }
1012    }
1013  }
1014  return p;
1015}
1016
1017static void
1018free_s_arg(Cmd *c)
1019{
1020  if (c->u.s.re)
1021    regfree(c->u.s.re);
1022  free(c->u.s.re);
1023  free(c->u.s.repl.str);
1024}
1025
1026/* see get_r_arg notes */
1027static char *
1028get_w_arg(Cmd *c, char *s)
1029{
1030  char  *p = semicolon_arg(s = chomp(s));
1031  Wfile *w, **wp;
1032
1033  if (p == s)
1034    leprintf("no file name");
1035
1036  for (wp = (Wfile **)wfiles.data; (size_t)(wp - (Wfile **)wfiles.data) < wfiles.size; wp++) {
1037    if (strlen((*wp)->path) == (size_t)(p - s) && !strncmp(s, (*wp)->path, p - s)) {
1038      c->u.file = (*wp)->file;
1039      return p;
1040    }
1041  }
1042
1043  w       = emalloc(sizeof(*w));
1044  w->path = estrndup(s, p - s);
1045
1046  if (!(w->file = fopen(w->path, "w")))
1047    leprintf("fopen failed");
1048
1049  c->u.file = w->file;
1050
1051  push(&wfiles, w);
1052  return p;
1053}
1054
1055static char *
1056get_y_arg(Cmd *c, char *s)
1057{
1058  Rune   delim;
1059  char  *p    = s + strlen(s);
1060  size_t rlen = echarntorune(&delim, s, p - s);
1061  size_t nrunes1, nrunes2;
1062
1063  c->u.y.set1 = c->u.y.set2 = NULL;
1064
1065  s += rlen;
1066  p = find_delim(s, delim, 0);
1067  p -= escapes(s, p, delim, 1);
1068  nrunes1     = utfnlen(s, p - s);
1069  c->u.y.set1 = strtorunes(s, nrunes1);
1070
1071  s = p + rlen;
1072  p = find_delim(s, delim, 0);
1073  p -= escapes(s, p, delim, 1);
1074  nrunes2 = utfnlen(s, p - s);
1075
1076  if (nrunes1 != nrunes2)
1077    leprintf("different set lengths");
1078
1079  c->u.y.set2 = strtorunes(s, utfnlen(s, p - s));
1080
1081  return p + rlen;
1082}
1083
1084static void
1085free_y_arg(Cmd *c)
1086{
1087  free(c->u.y.set1);
1088  free(c->u.y.set2);
1089}
1090
1091/* see get_bt_arg notes */
1092static char *
1093get_colon_arg(Cmd *c, char *s)
1094{
1095  char *p = semicolon_arg(s = chomp(s));
1096
1097  if (p == s)
1098    leprintf("no label name");
1099
1100  c->u.label = estrndup(s, p - s);
1101  push(&labels, (void *)(c - prog));
1102  return p;
1103}
1104
1105static char *
1106get_lbrace_arg(Cmd *c, char *s)
1107{
1108  push(&braces, (void *)(c - prog));
1109  return s;
1110}
1111
1112static char *
1113get_rbrace_arg(Cmd *c, char *s)
1114{
1115  Cmd *lbrace;
1116
1117  if (!braces.size)
1118    leprintf("extra }");
1119
1120  lbrace           = prog + (ptrdiff_t)pop(&braces);
1121  lbrace->u.offset = c - prog;
1122  return s;
1123}
1124
1125/* s points to beginning of an argument that may be semicolon terminated
1126 * return pointer to semicolon or nul byte after string
1127 * or closing brace as to not force ; before }
1128 * FIXME: decide whether or not to eat trailing whitespace for arguments that
1129 *        we allow semicolon/brace termination that POSIX doesn't
1130 *        b, r, t, w, :
1131 *        POSIX says trailing whitespace is part of label name, file name, etc.
1132 *        we should probably eat it
1133 */
1134static char *
1135semicolon_arg(char *s)
1136{
1137  char *p = strpbrk(s, ";}");
1138  if (!p)
1139    p = s + strlen(s);
1140  return p;
1141}
1142
1143static void
1144run(void)
1145{
1146  lineno = 0;
1147  if (braces.size)
1148    leprintf("extra {");
1149
1150  /* genbuf has already been initialized, patt will be in new_line
1151   * (or we'll halt) */
1152  stracpy(&hold, "");
1153
1154  insert_labels();
1155  next_file();
1156  new_line();
1157
1158  for (pc = prog; !gflags.halt; pc++)
1159    pc->fninfo->fn(pc);
1160}
1161
1162/* return true if we are in range for c, set c->in_match appropriately */
1163static int
1164in_range(Cmd *c)
1165{
1166  if (match_addr(&c->range.beg)) {
1167    if (c->range.naddr == 2) {
1168      if (c->range.end.type == LINE && c->range.end.u.lineno <= lineno)
1169        c->in_match = 0;
1170      else
1171        c->in_match = 1;
1172    }
1173    return !c->negate;
1174  }
1175  if (c->in_match && match_addr(&c->range.end)) {
1176    c->in_match = 0;
1177    return !c->negate;
1178  }
1179  return c->in_match ^ c->negate;
1180}
1181
1182/* return true if addr matches current line */
1183static int
1184match_addr(Addr *a)
1185{
1186  switch (a->type) {
1187    default:
1188    case IGNORE:
1189      return 0;
1190    case EVERY:
1191      return 1;
1192    case LINE:
1193      return lineno == a->u.lineno;
1194    case LAST:
1195      while (is_eof(file) && !next_file())
1196        ;
1197      return !file;
1198    case REGEX:
1199      lastre = a->u.re;
1200      return !regexec(a->u.re, patt.str, 0, NULL, 0);
1201    case LASTRE:
1202      if (!lastre)
1203        leprintf("no previous regex");
1204      return !regexec(lastre, patt.str, 0, NULL, 0);
1205  }
1206}
1207
1208/* move to next input file
1209 * stdin if first call and no files
1210 * return 0 for success and 1 for no more files
1211 */
1212static int
1213next_file(void)
1214{
1215  static unsigned char first = 1;
1216
1217  if (file == stdin)
1218    clearerr(file);
1219  else if (file)
1220    fshut(file, "<file>");
1221  /* given no files, default to stdin */
1222  file  = first && !*files ? stdin : NULL;
1223  first = 0;
1224
1225  while (!file && *files) {
1226    if (!strcmp(*files, "-")) {
1227      file = stdin;
1228    } else if (!(file = fopen(*files, "r"))) {
1229      /* warn this file didn't open, but move on to next */
1230      weprintf("fopen %s:", *files);
1231      ret = 1;
1232    }
1233    files++;
1234  }
1235
1236  return !file;
1237}
1238
1239/* test if stream is at EOF */
1240static int
1241is_eof(FILE *f)
1242{
1243  int c;
1244
1245  if (!f || feof(f))
1246    return 1;
1247
1248  c = fgetc(f);
1249  if (c == EOF && ferror(f))
1250    eprintf("fgetc:");
1251  if (c != EOF && ungetc(c, f) == EOF)
1252    eprintf("ungetc EOF\n");
1253
1254  return c == EOF;
1255}
1256
1257/* perform writes that were scheduled
1258 * for aci this is check_puts(string, stdout)
1259 * for r this is write_file(path, stdout)
1260 */
1261static void
1262do_writes(void)
1263{
1264  Cmd   *c;
1265  size_t i;
1266
1267  for (i = 0; i < writes.size; i++) {
1268    c = writes.data[i];
1269    c->u.acir.print(c->u.acir.str.str, stdout);
1270  }
1271  writes.size = 0;
1272}
1273
1274/* used for r's u.acir.print()
1275 * FIXME: something like util's concat() would be better
1276 */
1277static void
1278write_file(char *path, FILE *out)
1279{
1280  FILE *in = fopen(path, "r");
1281  if (!in) /* no file is treated as empty file */
1282    return;
1283
1284  while (read_line(in, &genbuf) != EOF)
1285    check_puts(genbuf.str, out);
1286
1287  fshut(in, path);
1288}
1289
1290static void
1291check_puts(char *s, FILE *f)
1292{
1293  if (s && fputs(s, f) == EOF)
1294    eprintf("fputs:");
1295  if (fputs("\n", f) == EOF)
1296    eprintf("fputs:");
1297}
1298
1299static void
1300write_patt(char *s, FILE *f)
1301{
1302#if FEATURE_SED_PRESERVE_NEWLINE
1303  if (s && fputs(s, f) == EOF)
1304    eprintf("fputs:");
1305  if (hadnl) {
1306    if (fputs("\n", f) == EOF)
1307      eprintf("fputs:");
1308  }
1309#else
1310  check_puts(s, f);
1311#endif
1312}
1313
1314/* iterate from beg to end updating ranges so we don't miss any commands
1315 * e.g. sed -n '1d;1,3p' should still print lines 2 and 3
1316 */
1317static void
1318update_ranges(Cmd *beg, Cmd *end)
1319{
1320  while (beg < end)
1321    in_range(beg++);
1322}
1323
1324/*
1325 * Sed functions
1326 */
1327static void
1328cmd_a(Cmd *c)
1329{
1330  if (in_range(c))
1331    push(&writes, c);
1332}
1333
1334static void
1335cmd_b(Cmd *c)
1336{
1337  if (!in_range(c))
1338    return;
1339
1340  /* if we jump backwards update to end, otherwise update to destination
1341   */
1342  update_ranges(c + 1, c->u.jump > c ? c->u.jump : prog + pcap);
1343  pc = c->u.jump;
1344}
1345
1346static void
1347cmd_c(Cmd *c)
1348{
1349  if (!in_range(c))
1350    return;
1351
1352  /* write the text on the last line of the match */
1353  if (!c->in_match)
1354    check_puts(c->u.acir.str.str, stdout);
1355  /* otherwise start the next cycle without printing pattern space
1356   * effectively deleting the text */
1357  new_next();
1358}
1359
1360static void
1361cmd_d(Cmd *c)
1362{
1363  if (!in_range(c))
1364    return;
1365
1366  new_next();
1367}
1368
1369static void
1370cmd_D(Cmd *c)
1371{
1372  char *p;
1373
1374  if (!in_range(c))
1375    return;
1376
1377  if ((p = strchr(patt.str, '\n'))) {
1378    p++;
1379    memmove(patt.str, p, strlen(p) + 1);
1380    old_next();
1381  } else {
1382    new_next();
1383  }
1384}
1385
1386static void
1387cmd_g(Cmd *c)
1388{
1389  if (in_range(c))
1390    stracpy(&patt, hold.str);
1391}
1392
1393static void
1394cmd_G(Cmd *c)
1395{
1396  if (!in_range(c))
1397    return;
1398
1399  stracat(&patt, "\n");
1400  stracat(&patt, hold.str);
1401}
1402
1403static void
1404cmd_h(Cmd *c)
1405{
1406  if (in_range(c))
1407    stracpy(&hold, patt.str);
1408}
1409
1410static void
1411cmd_H(Cmd *c)
1412{
1413  if (!in_range(c))
1414    return;
1415
1416  stracat(&hold, "\n");
1417  stracat(&hold, patt.str);
1418}
1419
1420static void
1421cmd_i(Cmd *c)
1422{
1423  if (in_range(c))
1424    check_puts(c->u.acir.str.str, stdout);
1425}
1426
1427/* I think it makes sense to print invalid UTF-8 sequences in octal to satisfy
1428 * the "visually unambiguous form" sed(1p)
1429 */
1430static void
1431cmd_l(Cmd *c)
1432{
1433  Rune   r;
1434  char  *p, *end;
1435  size_t rlen;
1436
1437  char *escapes[] = {
1438      /* FIXME: 7 entries and search instead of 127 */
1439      ['\\'] = "\\\\",
1440      ['\a'] = "\\a",
1441      ['\b'] = "\\b",
1442      ['\f'] = "\\f",
1443      ['\r'] = "\\r",
1444      ['\t'] = "\\t",
1445      ['\v'] = "\\v",
1446      [0x7f] = NULL, /* fill out the table */
1447  };
1448
1449  if (!in_range(c))
1450    return;
1451
1452  /* FIXME: line wrapping. sed(1p) says "length at which folding occurs is
1453   * unspecified, but should be appropraite for the output device"
1454   * just wrap at 80 Runes?
1455   */
1456  for (p = patt.str, end = p + strlen(p); p < end; p += rlen) {
1457    if (isascii(*p) && escapes[(unsigned int)*p]) {
1458      fputs(escapes[(unsigned int)*p], stdout);
1459      rlen = 1;
1460    } else if (!(rlen = charntorune(&r, p, end - p))) {
1461      /* ran out of chars, print the bytes of the short
1462       * sequence */
1463      for (; p < end; p++)
1464        printf("\\%03hho", (unsigned char)*p);
1465      break;
1466    } else if (r == Runeerror) {
1467      for (; rlen; rlen--, p++)
1468        printf("\\%03hho", (unsigned char)*p);
1469    } else {
1470      while (fwrite(p, rlen, 1, stdout) < 1 && errno == EINTR)
1471        ;
1472      if (ferror(stdout))
1473        eprintf("fwrite:");
1474    }
1475  }
1476  check_puts("$", stdout);
1477}
1478
1479static void
1480cmd_n(Cmd *c)
1481{
1482  if (!in_range(c))
1483    return;
1484
1485  if (!gflags.n)
1486    write_patt(patt.str, stdout);
1487  do_writes();
1488  new_line();
1489}
1490
1491static void
1492cmd_N(Cmd *c)
1493{
1494  if (!in_range(c))
1495    return;
1496  do_writes();
1497  app_line();
1498}
1499
1500static void
1501cmd_p(Cmd *c)
1502{
1503  if (in_range(c))
1504    write_patt(patt.str, stdout);
1505}
1506
1507static void
1508cmd_P(Cmd *c)
1509{
1510  char *p;
1511
1512  if (!in_range(c))
1513    return;
1514
1515  if ((p = strchr(patt.str, '\n')))
1516    *p = '\0';
1517
1518  write_patt(patt.str, stdout);
1519
1520  if (p)
1521    *p = '\n';
1522}
1523
1524static void
1525cmd_q(Cmd *c)
1526{
1527  if (!in_range(c))
1528    return;
1529
1530  if (!gflags.n)
1531    check_puts(patt.str, stdout);
1532  do_writes();
1533  gflags.halt = 1;
1534}
1535
1536static void
1537cmd_r(Cmd *c)
1538{
1539  if (in_range(c))
1540    push(&writes, c);
1541}
1542
1543static void
1544cmd_s(Cmd *c)
1545{
1546  String       tmp;
1547  Rune         r;
1548  size_t       plen, rlen, len;
1549  char        *p, *s, *end;
1550  unsigned int matches = 0, last_empty = 1, qflag = 0, cflags = 0;
1551  regex_t     *re;
1552  regmatch_t  *rm, *pmatch = NULL;
1553
1554  if (!in_range(c))
1555    return;
1556
1557  if (!c->u.s.re && !lastre)
1558    leprintf("no previous regex");
1559
1560  re     = c->u.s.re ? c->u.s.re : lastre;
1561  lastre = re;
1562
1563  plen   = re->re_nsub + 1;
1564  pmatch = ereallocarray(NULL, plen, sizeof(regmatch_t));
1565
1566  *genbuf.str = '\0';
1567  s           = patt.str;
1568
1569  while (!qflag && !regexec(re, s, plen, pmatch, cflags)) {
1570    cflags = REG_NOTBOL; /* match against beginning of line first
1571          time, but not again */
1572    if (!*s)             /* match against empty string first time, but not again
1573                          */
1574      qflag = 1;
1575
1576    /* don't substitute if last match was not empty but this one is.
1577     * s_a*_._g
1578     * foobar -> .f.o.o.b.r.
1579     */
1580    if ((last_empty || pmatch[0].rm_eo) && (++matches == c->u.s.occurrence || !c->u.s.occurrence)) {
1581      /* copy over everything before the match */
1582      strnacat(&genbuf, s, pmatch[0].rm_so);
1583
1584      /* copy over replacement text, taking into account &,
1585       * backreferences, and \ escapes */
1586      for (p = c->u.s.repl.str, len = strcspn(p, "\\&"); *p; len = strcspn(++p, "\\&")) {
1587        strnacat(&genbuf, p, len);
1588        p += len;
1589        switch (*p) {
1590          default:
1591            leprintf("this shouldn't be possible");
1592            break;
1593          case '\0':
1594            /* we're at the end, back up one so the
1595             * ++p will put us on the null byte to
1596             * break out of the loop */
1597            --p;
1598            break;
1599          case '&':
1600            strnacat(&genbuf, s + pmatch[0].rm_so, pmatch[0].rm_eo - pmatch[0].rm_so);
1601            break;
1602          case '\\':
1603            if (isdigit(*++p)) { /* backreference */
1604              /* only need to check here if
1605               * using lastre, otherwise we
1606               * checked when building */
1607              if (!c->u.s.re && (size_t)(*p - '0') > re->re_nsub)
1608                leprintf(
1609                    "back "
1610                    "reference "
1611                    "number "
1612                    "greater than "
1613                    "number of "
1614                    "groups"
1615                );
1616              rm = &pmatch[*p - '0'];
1617              strnacat(&genbuf, s + rm->rm_so, rm->rm_eo - rm->rm_so);
1618            } else { /* character after backslash
1619                  taken literally (well one
1620                  byte, but it works) */
1621              strnacat(&genbuf, p, 1);
1622            }
1623            break;
1624        }
1625      }
1626    } else {
1627      /* not replacing, copy over everything up to and
1628       * including the match */
1629      strnacat(&genbuf, s, pmatch[0].rm_eo);
1630    }
1631
1632    if (!pmatch[0].rm_eo) { /* empty match, advance one rune and add
1633             it to output */
1634      end  = s + strlen(s);
1635      rlen = charntorune(&r, s, end - s);
1636
1637      if (!rlen) { /* ran out of bytes, copy short sequence */
1638        stracat(&genbuf, s);
1639        s = end;
1640      } else { /* copy whether or not it's a good rune */
1641        strnacat(&genbuf, s, rlen);
1642        s += rlen;
1643      }
1644    }
1645    last_empty = !pmatch[0].rm_eo;
1646    s += pmatch[0].rm_eo;
1647  }
1648  free(pmatch);
1649
1650  if (!(matches && matches >= c->u.s.occurrence)) /* no replacement */
1651    return;
1652
1653  gflags.s = 1;
1654
1655  stracat(&genbuf, s);
1656
1657  tmp    = patt;
1658  patt   = genbuf;
1659  genbuf = tmp;
1660
1661  if (c->u.s.p)
1662    write_patt(patt.str, stdout);
1663  if (c->u.s.file)
1664    write_patt(patt.str, c->u.s.file);
1665}
1666
1667static void
1668cmd_t(Cmd *c)
1669{
1670  if (!in_range(c) || !gflags.s)
1671    return;
1672
1673  /* if we jump backwards update to end, otherwise update to destination
1674   */
1675  update_ranges(c + 1, c->u.jump > c ? c->u.jump : prog + pcap);
1676  pc       = c->u.jump;
1677  gflags.s = 0;
1678}
1679
1680static void
1681cmd_w(Cmd *c)
1682{
1683  if (in_range(c))
1684    write_patt(patt.str, c->u.file);
1685}
1686
1687static void
1688cmd_x(Cmd *c)
1689{
1690  String tmp;
1691
1692  if (!in_range(c))
1693    return;
1694
1695  tmp  = patt;
1696  patt = hold;
1697  hold = tmp;
1698}
1699
1700static void
1701cmd_y(Cmd *c)
1702{
1703  String tmp;
1704  Rune   r, *rp;
1705  size_t n, rlen;
1706  char  *s, *end, buf[UTFmax];
1707
1708  if (!in_range(c))
1709    return;
1710
1711  *genbuf.str = '\0';
1712  for (s = patt.str, end = s + strlen(s); *s; s += rlen) {
1713    if (!(rlen = charntorune(&r, s, end - s))) { /* ran out of chars, copy rest */
1714      stracat(&genbuf, s);
1715      break;
1716    } else if (r == Runeerror) { /* bad UTF-8 sequence, copy bytes */
1717      strnacat(&genbuf, s, rlen);
1718    } else {
1719      for (rp = c->u.y.set1; *rp; rp++)
1720        if (*rp == r)
1721          break;
1722      if (*rp) { /* found r in set1, replace with Rune from
1723              set2 */
1724        n = runetochar(buf, c->u.y.set2 + (rp - c->u.y.set1));
1725        strnacat(&genbuf, buf, n);
1726      } else {
1727        strnacat(&genbuf, s, rlen);
1728      }
1729    }
1730  }
1731  tmp    = patt;
1732  patt   = genbuf;
1733  genbuf = tmp;
1734}
1735
1736static void
1737cmd_colon(Cmd *c)
1738{
1739  (void)c;
1740}
1741
1742static void
1743cmd_equal(Cmd *c)
1744{
1745  if (in_range(c))
1746    printf("%zu\n", lineno);
1747}
1748
1749static void
1750cmd_lbrace(Cmd *c)
1751{
1752  Cmd *jump;
1753
1754  if (in_range(c))
1755    return;
1756
1757  /* update ranges on all commands we skip */
1758  jump = prog + c->u.offset;
1759  update_ranges(c + 1, jump);
1760  pc = jump;
1761}
1762
1763static void
1764cmd_rbrace(Cmd *c)
1765{
1766  (void)c;
1767}
1768
1769/* not actually a sed function, but acts like one, put in last spot of script */
1770static void
1771cmd_last(Cmd *c)
1772{
1773  (void)c;
1774  if (!gflags.n)
1775    write_patt(patt.str, stdout);
1776  do_writes();
1777  new_next();
1778}
1779
1780/*
1781 * Actions
1782 */
1783
1784/* read new line, continue current cycle */
1785static void
1786new_line(void)
1787{
1788  while (read_line(file, &patt) == EOF) {
1789    if (next_file()) {
1790      gflags.halt = 1;
1791      return;
1792    }
1793  }
1794  gflags.s = 0;
1795  lineno++;
1796}
1797
1798/* append new line, continue current cycle
1799 * FIXME: used for N, POSIX specifies do not print pattern space when out of
1800 *        input, but GNU does so busybox does as well. Currently we don't.
1801 *        Should we?
1802 */
1803static void
1804app_line(void)
1805{
1806  while (read_line(file, &genbuf) == EOF) {
1807    if (next_file()) {
1808      gflags.halt = 1;
1809      return;
1810    }
1811  }
1812
1813  stracat(&patt, "\n");
1814  stracat(&patt, genbuf.str);
1815  gflags.s = 0;
1816  lineno++;
1817}
1818
1819/* read new line, start new cycle */
1820static void
1821new_next(void)
1822{
1823  *patt.str = '\0';
1824  update_ranges(pc + 1, prog + pcap);
1825  new_line();
1826  pc = prog - 1;
1827}
1828
1829/* keep old pattern space, start new cycle */
1830static void
1831old_next(void)
1832{
1833  update_ranges(pc + 1, prog + pcap);
1834  pc = prog - 1;
1835}
1836
1837// ?man sed: stream editor
1838// ?man arguments: script [file ...]
1839// ?man stream editor for filtering and transforming text
1840int
1841main(int argc, char *argv[])
1842{
1843  char *arg;
1844  int   script = 0;
1845
1846  ARGBEGIN
1847  {
1848    // ?man -n: print line numbers or counts
1849    case 'n':
1850      gflags.n = 1;
1851      break;
1852    // ?man -r: operate recursively
1853    case 'r':
1854    // ?man -E: specify option flag
1855    case 'E':
1856      gflags.E = 1;
1857      break;
1858    // ?man -e:str: specify expression or pattern
1859    case 'e':
1860      arg = EARGF(usage());
1861      compile(arg, 0);
1862      script = 1;
1863      break;
1864    // ?man -f:str: force the operation
1865    case 'f':
1866      arg = EARGF(usage());
1867      compile(arg, 1);
1868      script = 1;
1869      break;
1870#if FEATURE_SED_INPLACE
1871    // ?man -i: interactive mode or prompt for confirmation
1872    case 'i':
1873      iflag = 1;
1874      if (argv[0][1] != '\0') {
1875        backup_suffix = &argv[0][1];
1876        brk_          = 1;
1877      } else {
1878        backup_suffix = "";
1879      }
1880      break;
1881#endif
1882    default:
1883      usage();
1884  }
1885  ARGEND
1886
1887  /* no script to run */
1888  if (!script && !argc)
1889    usage();
1890
1891  /* no script yet, next argument is script */
1892  if (!script)
1893    compile(*argv++, 0);
1894
1895  /* shrink/grow memory to fit and add our last instruction */
1896  resize((void **)&prog, &pcap, sizeof(*prog), pc - prog + 1, NULL);
1897  pc         = prog + pcap - 1;
1898  pc->fninfo = &(Fninfo){cmd_last, NULL, NULL, 0};
1899
1900#if FEATURE_SED_INPLACE
1901  if (iflag) {
1902    char  *single_file[2] = {NULL, NULL};
1903    char **orig_files     = argv;
1904    int    i;
1905
1906    if (!*orig_files)
1907      eprintf("no input files\n");
1908
1909    for (i = 0; orig_files[i]; i++) {
1910      char       *temp_path = NULL;
1911      int         temp_fd;
1912      int         real_stdout;
1913      struct stat st;
1914      Cmd        *c;
1915
1916      if (strcmp(orig_files[i], "-") == 0) {
1917        weprintf("cannot edit stdin in-place\n");
1918        ret = 1;
1919        continue;
1920      }
1921
1922      if (stat(orig_files[i], &st) < 0) {
1923        weprintf("stat %s:", orig_files[i]);
1924        ret = 1;
1925        continue;
1926      }
1927
1928      temp_fd = create_temp_file(orig_files[i], &temp_path);
1929      if (temp_fd < 0) {
1930        weprintf("create_temp_file:");
1931        ret = 1;
1932        continue;
1933      }
1934
1935      real_stdout = dup(1);
1936      if (real_stdout < 0) {
1937        weprintf("dup stdout:");
1938        close(temp_fd);
1939        free(temp_path);
1940        ret = 1;
1941        continue;
1942      }
1943      if (dup2(temp_fd, 1) < 0) {
1944        weprintf("dup2 stdout:");
1945        close(temp_fd);
1946        close(real_stdout);
1947        free(temp_path);
1948        ret = 1;
1949        continue;
1950      }
1951      close(temp_fd);
1952
1953      single_file[0] = orig_files[i];
1954      files          = single_file;
1955
1956      /* reset state for next file */
1957      lineno      = 0;
1958      gflags.halt = 0;
1959      stracpy(&hold, "");
1960      stracpy(&patt, "");
1961      writes.size = 0;
1962      for (c = prog; c->fninfo->fn != cmd_last; c++) {
1963        c->in_match = 0;
1964      }
1965
1966      run();
1967
1968      fflush(stdout);
1969      dup2(real_stdout, 1);
1970      close(real_stdout);
1971
1972      if (backup_suffix && *backup_suffix) {
1973        char *backup_path = emalloc(strlen(orig_files[i]) + strlen(backup_suffix) + 1);
1974        sprintf(backup_path, "%s%s", orig_files[i], backup_suffix);
1975        if (rename(orig_files[i], backup_path) < 0) {
1976          weprintf("rename %s to %s:", orig_files[i], backup_path);
1977          unlink(temp_path);
1978          free(backup_path);
1979          free(temp_path);
1980          ret = 1;
1981          continue;
1982        }
1983        free(backup_path);
1984      } else {
1985        unlink(orig_files[i]);
1986      }
1987
1988      if (rename(temp_path, orig_files[i]) < 0) {
1989        weprintf("rename %s to %s:", temp_path, orig_files[i]);
1990        unlink(temp_path);
1991        free(temp_path);
1992        ret = 1;
1993        continue;
1994      }
1995
1996      chmod(orig_files[i], st.st_mode);
1997      chown(orig_files[i], st.st_uid, st.st_gid);
1998
1999      free(temp_path);
2000    }
2001  } else
2002#endif
2003  {
2004    files = argv;
2005    run();
2006  }
2007
2008  ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
2009
2010  return ret;
2011}