[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit

Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/busybox/src/editors/Config.src b/ap/app/busybox/src/editors/Config.src
new file mode 100644
index 0000000..af1e1de
--- /dev/null
+++ b/ap/app/busybox/src/editors/Config.src
@@ -0,0 +1,78 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Editors"
+
+INSERT
+
+config AWK
+	bool "awk"
+	default y
+	help
+	  Awk is used as a pattern scanning and processing language. This is
+	  the BusyBox implementation of that programming language.
+
+config FEATURE_AWK_LIBM
+	bool "Enable math functions (requires libm)"
+	default y
+	depends on AWK
+	help
+	  Enable math functions of the Awk programming language.
+	  NOTE: This will require libm to be present for linking.
+
+config CMP
+	bool "cmp"
+	default y
+	help
+	  cmp is used to compare two files and returns the result
+	  to standard output.
+
+config DIFF
+	bool "diff"
+	default y
+	help
+	  diff compares two files or directories and outputs the
+	  differences between them in a form that can be given to
+	  the patch command.
+
+config FEATURE_DIFF_LONG_OPTIONS
+	bool "Enable long options"
+	default y
+	depends on DIFF && LONG_OPTS
+	help
+	  Enable use of long options.
+
+config FEATURE_DIFF_DIR
+	bool "Enable directory support"
+	default y
+	depends on DIFF
+	help
+	  This option enables support for directory and subdirectory
+	  comparison.
+
+config ED
+	bool "ed"
+	default y
+	help
+	  The original 1970's Unix text editor, from the days of teletypes.
+	  Small, simple, evil. Part of SUSv3. If you're not already using
+	  this, you don't need it.
+
+config SED
+	bool "sed"
+	default y
+	help
+	  sed is used to perform text transformations on a file
+	  or input from a pipeline.
+
+config FEATURE_ALLOW_EXEC
+	bool "Allow vi and awk to execute shell commands"
+	default y
+	depends on VI || AWK
+	help
+	  Enables vi and awk features which allows user to execute
+	  shell commands (using system() C call).
+
+endmenu
diff --git a/ap/app/busybox/src/editors/Kbuild.src b/ap/app/busybox/src/editors/Kbuild.src
new file mode 100644
index 0000000..8888cba
--- /dev/null
+++ b/ap/app/busybox/src/editors/Kbuild.src
@@ -0,0 +1,14 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under GPLv2, see file LICENSE in this source tree.
+
+lib-y:=
+
+INSERT
+lib-$(CONFIG_AWK)       += awk.o
+lib-$(CONFIG_CMP)       += cmp.o
+lib-$(CONFIG_DIFF)      += diff.o
+lib-$(CONFIG_ED)        += ed.o
+lib-$(CONFIG_SED)       += sed.o
diff --git a/ap/app/busybox/src/editors/awk.c b/ap/app/busybox/src/editors/awk.c
new file mode 100644
index 0000000..3224788
--- /dev/null
+++ b/ap/app/busybox/src/editors/awk.c
@@ -0,0 +1,3216 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * awk implementation for busybox
+ *
+ * Copyright (C) 2002 by Dmitry Zakharov <dmit@crp.bank.gov.ua>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//usage:#define awk_trivial_usage
+//usage:       "[OPTIONS] [AWK_PROGRAM] [FILE]..."
+//usage:#define awk_full_usage "\n\n"
+//usage:       "	-v VAR=VAL	Set variable"
+//usage:     "\n	-F SEP		Use SEP as field separator"
+//usage:     "\n	-f FILE		Read program from FILE"
+
+#include "libbb.h"
+#include "xregex.h"
+#include <math.h>
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+/* If you comment out one of these below, it will be #defined later
+ * to perform debug printfs to stderr: */
+#define debug_printf_walker(...)  do {} while (0)
+#define debug_printf_eval(...)  do {} while (0)
+#define debug_printf_parse(...)  do {} while (0)
+
+#ifndef debug_printf_walker
+# define debug_printf_walker(...) (fprintf(stderr, __VA_ARGS__))
+#endif
+#ifndef debug_printf_eval
+# define debug_printf_eval(...) (fprintf(stderr, __VA_ARGS__))
+#endif
+#ifndef debug_printf_parse
+# define debug_printf_parse(...) (fprintf(stderr, __VA_ARGS__))
+#endif
+
+
+
+#define	MAXVARFMT       240
+#define	MINNVBLOCK      64
+
+/* variable flags */
+#define	VF_NUMBER       0x0001	/* 1 = primary type is number */
+#define	VF_ARRAY        0x0002	/* 1 = it's an array */
+
+#define	VF_CACHED       0x0100	/* 1 = num/str value has cached str/num eq */
+#define	VF_USER         0x0200	/* 1 = user input (may be numeric string) */
+#define	VF_SPECIAL      0x0400	/* 1 = requires extra handling when changed */
+#define	VF_WALK         0x0800	/* 1 = variable has alloc'd x.walker list */
+#define	VF_FSTR         0x1000	/* 1 = var::string points to fstring buffer */
+#define	VF_CHILD        0x2000	/* 1 = function arg; x.parent points to source */
+#define	VF_DIRTY        0x4000	/* 1 = variable was set explicitly */
+
+/* these flags are static, don't change them when value is changed */
+#define	VF_DONTTOUCH    (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY)
+
+typedef struct walker_list {
+	char *end;
+	char *cur;
+	struct walker_list *prev;
+	char wbuf[1];
+} walker_list;
+
+/* Variable */
+typedef struct var_s {
+	unsigned type;            /* flags */
+	double number;
+	char *string;
+	union {
+		int aidx;               /* func arg idx (for compilation stage) */
+		struct xhash_s *array;  /* array ptr */
+		struct var_s *parent;   /* for func args, ptr to actual parameter */
+		walker_list *walker;    /* list of array elements (for..in) */
+	} x;
+} var;
+
+/* Node chain (pattern-action chain, BEGIN, END, function bodies) */
+typedef struct chain_s {
+	struct node_s *first;
+	struct node_s *last;
+	const char *programname;
+} chain;
+
+/* Function */
+typedef struct func_s {
+	unsigned nargs;
+	struct chain_s body;
+} func;
+
+/* I/O stream */
+typedef struct rstream_s {
+	FILE *F;
+	char *buffer;
+	int adv;
+	int size;
+	int pos;
+	smallint is_pipe;
+} rstream;
+
+typedef struct hash_item_s {
+	union {
+		struct var_s v;         /* variable/array hash */
+		struct rstream_s rs;    /* redirect streams hash */
+		struct func_s f;        /* functions hash */
+	} data;
+	struct hash_item_s *next;       /* next in chain */
+	char name[1];                   /* really it's longer */
+} hash_item;
+
+typedef struct xhash_s {
+	unsigned nel;           /* num of elements */
+	unsigned csize;         /* current hash size */
+	unsigned nprime;        /* next hash size in PRIMES[] */
+	unsigned glen;          /* summary length of item names */
+	struct hash_item_s **items;
+} xhash;
+
+/* Tree node */
+typedef struct node_s {
+	uint32_t info;
+	unsigned lineno;
+	union {
+		struct node_s *n;
+		var *v;
+		int aidx;
+		char *new_progname;
+		regex_t *re;
+	} l;
+	union {
+		struct node_s *n;
+		regex_t *ire;
+		func *f;
+	} r;
+	union {
+		struct node_s *n;
+	} a;
+} node;
+
+/* Block of temporary variables */
+typedef struct nvblock_s {
+	int size;
+	var *pos;
+	struct nvblock_s *prev;
+	struct nvblock_s *next;
+	var nv[];
+} nvblock;
+
+typedef struct tsplitter_s {
+	node n;
+	regex_t re[2];
+} tsplitter;
+
+/* simple token classes */
+/* Order and hex values are very important!!!  See next_token() */
+#define	TC_SEQSTART	1			/* ( */
+#define	TC_SEQTERM	(1 << 1)		/* ) */
+#define	TC_REGEXP	(1 << 2)		/* /.../ */
+#define	TC_OUTRDR	(1 << 3)		/* | > >> */
+#define	TC_UOPPOST	(1 << 4)		/* unary postfix operator */
+#define	TC_UOPPRE1	(1 << 5)		/* unary prefix operator */
+#define	TC_BINOPX	(1 << 6)		/* two-opnd operator */
+#define	TC_IN		(1 << 7)
+#define	TC_COMMA	(1 << 8)
+#define	TC_PIPE		(1 << 9)		/* input redirection pipe */
+#define	TC_UOPPRE2	(1 << 10)		/* unary prefix operator */
+#define	TC_ARRTERM	(1 << 11)		/* ] */
+#define	TC_GRPSTART	(1 << 12)		/* { */
+#define	TC_GRPTERM	(1 << 13)		/* } */
+#define	TC_SEMICOL	(1 << 14)
+#define	TC_NEWLINE	(1 << 15)
+#define	TC_STATX	(1 << 16)		/* ctl statement (for, next...) */
+#define	TC_WHILE	(1 << 17)
+#define	TC_ELSE		(1 << 18)
+#define	TC_BUILTIN	(1 << 19)
+#define	TC_GETLINE	(1 << 20)
+#define	TC_FUNCDECL	(1 << 21)		/* `function' `func' */
+#define	TC_BEGIN	(1 << 22)
+#define	TC_END		(1 << 23)
+#define	TC_EOF		(1 << 24)
+#define	TC_VARIABLE	(1 << 25)
+#define	TC_ARRAY	(1 << 26)
+#define	TC_FUNCTION	(1 << 27)
+#define	TC_STRING	(1 << 28)
+#define	TC_NUMBER	(1 << 29)
+
+#define	TC_UOPPRE  (TC_UOPPRE1 | TC_UOPPRE2)
+
+/* combined token classes */
+#define	TC_BINOP   (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
+#define	TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
+#define	TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION \
+                   | TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER)
+
+#define	TC_STATEMNT (TC_STATX | TC_WHILE)
+#define	TC_OPTERM  (TC_SEMICOL | TC_NEWLINE)
+
+/* word tokens, cannot mean something else if not expected */
+#define	TC_WORD    (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN \
+                   | TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END)
+
+/* discard newlines after these */
+#define	TC_NOTERM  (TC_COMMA | TC_GRPSTART | TC_GRPTERM \
+                   | TC_BINOP | TC_OPTERM)
+
+/* what can expression begin with */
+#define	TC_OPSEQ   (TC_OPERAND | TC_UOPPRE | TC_REGEXP)
+/* what can group begin with */
+#define	TC_GRPSEQ  (TC_OPSEQ | TC_OPTERM | TC_STATEMNT | TC_GRPSTART)
+
+/* if previous token class is CONCAT1 and next is CONCAT2, concatenation */
+/* operator is inserted between them */
+#define	TC_CONCAT1 (TC_VARIABLE | TC_ARRTERM | TC_SEQTERM \
+                   | TC_STRING | TC_NUMBER | TC_UOPPOST)
+#define	TC_CONCAT2 (TC_OPERAND | TC_UOPPRE)
+
+#define	OF_RES1    0x010000
+#define	OF_RES2    0x020000
+#define	OF_STR1    0x040000
+#define	OF_STR2    0x080000
+#define	OF_NUM1    0x100000
+#define	OF_CHECKED 0x200000
+
+/* combined operator flags */
+#define	xx	0
+#define	xV	OF_RES2
+#define	xS	(OF_RES2 | OF_STR2)
+#define	Vx	OF_RES1
+#define	VV	(OF_RES1 | OF_RES2)
+#define	Nx	(OF_RES1 | OF_NUM1)
+#define	NV	(OF_RES1 | OF_NUM1 | OF_RES2)
+#define	Sx	(OF_RES1 | OF_STR1)
+#define	SV	(OF_RES1 | OF_STR1 | OF_RES2)
+#define	SS	(OF_RES1 | OF_STR1 | OF_RES2 | OF_STR2)
+
+#define	OPCLSMASK 0xFF00
+#define	OPNMASK   0x007F
+
+/* operator priority is a highest byte (even: r->l, odd: l->r grouping)
+ * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1,
+ * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string
+ */
+#undef P
+#undef PRIMASK
+#undef PRIMASK2
+#define P(x)      (x << 24)
+#define PRIMASK   0x7F000000
+#define PRIMASK2  0x7E000000
+
+/* Operation classes */
+
+#define	SHIFT_TIL_THIS	0x0600
+#define	RECUR_FROM_THIS	0x1000
+
+enum {
+	OC_DELETE = 0x0100,     OC_EXEC = 0x0200,       OC_NEWSOURCE = 0x0300,
+	OC_PRINT = 0x0400,      OC_PRINTF = 0x0500,     OC_WALKINIT = 0x0600,
+
+	OC_BR = 0x0700,         OC_BREAK = 0x0800,      OC_CONTINUE = 0x0900,
+	OC_EXIT = 0x0a00,       OC_NEXT = 0x0b00,       OC_NEXTFILE = 0x0c00,
+	OC_TEST = 0x0d00,       OC_WALKNEXT = 0x0e00,
+
+	OC_BINARY = 0x1000,     OC_BUILTIN = 0x1100,    OC_COLON = 0x1200,
+	OC_COMMA = 0x1300,      OC_COMPARE = 0x1400,    OC_CONCAT = 0x1500,
+	OC_FBLTIN = 0x1600,     OC_FIELD = 0x1700,      OC_FNARG = 0x1800,
+	OC_FUNC = 0x1900,       OC_GETLINE = 0x1a00,    OC_IN = 0x1b00,
+	OC_LAND = 0x1c00,       OC_LOR = 0x1d00,        OC_MATCH = 0x1e00,
+	OC_MOVE = 0x1f00,       OC_PGETLINE = 0x2000,   OC_REGEXP = 0x2100,
+	OC_REPLACE = 0x2200,    OC_RETURN = 0x2300,     OC_SPRINTF = 0x2400,
+	OC_TERNARY = 0x2500,    OC_UNARY = 0x2600,      OC_VAR = 0x2700,
+	OC_DONE = 0x2800,
+
+	ST_IF = 0x3000,         ST_DO = 0x3100,         ST_FOR = 0x3200,
+	ST_WHILE = 0x3300
+};
+
+/* simple builtins */
+enum {
+	F_in,	F_rn,	F_co,	F_ex,	F_lg,	F_si,	F_sq,	F_sr,
+	F_ti,	F_le,	F_sy,	F_ff,	F_cl
+};
+
+/* builtins */
+enum {
+	B_a2,	B_ix,	B_ma,	B_sp,	B_ss,	B_ti,   B_mt,	B_lo,	B_up,
+	B_ge,	B_gs,	B_su,
+	B_an,	B_co,	B_ls,	B_or,	B_rs,	B_xo,
+};
+
+/* tokens and their corresponding info values */
+
+#define NTC     "\377"  /* switch to next token class (tc<<1) */
+#define NTCC    '\377'
+
+#define OC_B  OC_BUILTIN
+
+static const char tokenlist[] ALIGN1 =
+	"\1("         NTC
+	"\1)"         NTC
+	"\1/"         NTC                                   /* REGEXP */
+	"\2>>"        "\1>"         "\1|"       NTC         /* OUTRDR */
+	"\2++"        "\2--"        NTC                     /* UOPPOST */
+	"\2++"        "\2--"        "\1$"       NTC         /* UOPPRE1 */
+	"\2=="        "\1="         "\2+="      "\2-="      /* BINOPX */
+	"\2*="        "\2/="        "\2%="      "\2^="
+	"\1+"         "\1-"         "\3**="     "\2**"
+	"\1/"         "\1%"         "\1^"       "\1*"
+	"\2!="        "\2>="        "\2<="      "\1>"
+	"\1<"         "\2!~"        "\1~"       "\2&&"
+	"\2||"        "\1?"         "\1:"       NTC
+	"\2in"        NTC
+	"\1,"         NTC
+	"\1|"         NTC
+	"\1+"         "\1-"         "\1!"       NTC         /* UOPPRE2 */
+	"\1]"         NTC
+	"\1{"         NTC
+	"\1}"         NTC
+	"\1;"         NTC
+	"\1\n"        NTC
+	"\2if"        "\2do"        "\3for"     "\5break"   /* STATX */
+	"\10continue" "\6delete"    "\5print"
+	"\6printf"    "\4next"      "\10nextfile"
+	"\6return"    "\4exit"      NTC
+	"\5while"     NTC
+	"\4else"      NTC
+
+	"\3and"       "\5compl"     "\6lshift"  "\2or"
+	"\6rshift"    "\3xor"
+	"\5close"     "\6system"    "\6fflush"  "\5atan2"   /* BUILTIN */
+	"\3cos"       "\3exp"       "\3int"     "\3log"
+	"\4rand"      "\3sin"       "\4sqrt"    "\5srand"
+	"\6gensub"    "\4gsub"      "\5index"   "\6length"
+	"\5match"     "\5split"     "\7sprintf" "\3sub"
+	"\6substr"    "\7systime"   "\10strftime" "\6mktime"
+	"\7tolower"   "\7toupper"   NTC
+	"\7getline"   NTC
+	"\4func"      "\10function" NTC
+	"\5BEGIN"     NTC
+	"\3END"
+	/* compiler adds trailing "\0" */
+	;
+
+static const uint32_t tokeninfo[] = {
+	0,
+	0,
+	OC_REGEXP,
+	xS|'a',                  xS|'w',                  xS|'|',
+	OC_UNARY|xV|P(9)|'p',    OC_UNARY|xV|P(9)|'m',
+	OC_UNARY|xV|P(9)|'P',    OC_UNARY|xV|P(9)|'M',    OC_FIELD|xV|P(5),
+	OC_COMPARE|VV|P(39)|5,   OC_MOVE|VV|P(74),        OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-',
+	OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/', OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&',
+	OC_BINARY|NV|P(29)|'+',  OC_BINARY|NV|P(29)|'-',  OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&',
+	OC_BINARY|NV|P(25)|'/',  OC_BINARY|NV|P(25)|'%',  OC_BINARY|NV|P(15)|'&',  OC_BINARY|NV|P(25)|'*',
+	OC_COMPARE|VV|P(39)|4,   OC_COMPARE|VV|P(39)|3,   OC_COMPARE|VV|P(39)|0,   OC_COMPARE|VV|P(39)|1,
+	OC_COMPARE|VV|P(39)|2,   OC_MATCH|Sx|P(45)|'!',   OC_MATCH|Sx|P(45)|'~',   OC_LAND|Vx|P(55),
+	OC_LOR|Vx|P(59),         OC_TERNARY|Vx|P(64)|'?', OC_COLON|xx|P(67)|':',
+	OC_IN|SV|P(49), /* in */
+	OC_COMMA|SS|P(80),
+	OC_PGETLINE|SV|P(37),
+	OC_UNARY|xV|P(19)|'+',   OC_UNARY|xV|P(19)|'-',   OC_UNARY|xV|P(19)|'!',
+	0, /* ] */
+	0,
+	0,
+	0,
+	0, /* \n */
+	ST_IF,        ST_DO,        ST_FOR,      OC_BREAK,
+	OC_CONTINUE,  OC_DELETE|Vx, OC_PRINT,
+	OC_PRINTF,    OC_NEXT,      OC_NEXTFILE,
+	OC_RETURN|Vx, OC_EXIT|Nx,
+	ST_WHILE,
+	0, /* else */
+
+	OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83),
+	OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83),
+	OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83),
+	OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg,
+	OC_FBLTIN|F_rn,    OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr,
+	OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le,
+	OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF,        OC_B|B_su|P(0xb6),
+	OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti,    OC_B|B_ti|P(0x0b), OC_B|B_mt|P(0x0b),
+	OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49),
+	OC_GETLINE|SV|P(0),
+	0,                 0,
+	0,
+	0 /* END */
+};
+
+/* internal variable names and their initial values       */
+/* asterisk marks SPECIAL vars; $ is just no-named Field0 */
+enum {
+	CONVFMT,    OFMT,       FS,         OFS,
+	ORS,        RS,         RT,         FILENAME,
+	SUBSEP,     F0,         ARGIND,     ARGC,
+	ARGV,       ERRNO,      FNR,        NR,
+	NF,         IGNORECASE, ENVIRON,    NUM_INTERNAL_VARS
+};
+
+static const char vNames[] ALIGN1 =
+	"CONVFMT\0" "OFMT\0"    "FS\0*"     "OFS\0"
+	"ORS\0"     "RS\0*"     "RT\0"      "FILENAME\0"
+	"SUBSEP\0"  "$\0*"      "ARGIND\0"  "ARGC\0"
+	"ARGV\0"    "ERRNO\0"   "FNR\0"     "NR\0"
+	"NF\0*"     "IGNORECASE\0*" "ENVIRON\0" "\0";
+
+static const char vValues[] ALIGN1 =
+	"%.6g\0"    "%.6g\0"    " \0"       " \0"
+	"\n\0"      "\n\0"      "\0"        "\0"
+	"\034\0"    "\0"        "\377";
+
+/* hash size may grow to these values */
+#define FIRST_PRIME 61
+static const uint16_t PRIMES[] ALIGN2 = { 251, 1021, 4093, 16381, 65521 };
+
+
+/* Globals. Split in two parts so that first one is addressed
+ * with (mostly short) negative offsets.
+ * NB: it's unsafe to put members of type "double"
+ * into globals2 (gcc may fail to align them).
+ */
+struct globals {
+	double t_double;
+	chain beginseq, mainseq, endseq;
+	chain *seq;
+	node *break_ptr, *continue_ptr;
+	rstream *iF;
+	xhash *vhash, *ahash, *fdhash, *fnhash;
+	const char *g_progname;
+	int g_lineno;
+	int nfields;
+	int maxfields; /* used in fsrealloc() only */
+	var *Fields;
+	nvblock *g_cb;
+	char *g_pos;
+	char *g_buf;
+	smallint icase;
+	smallint exiting;
+	smallint nextrec;
+	smallint nextfile;
+	smallint is_f0_split;
+	smallint t_rollback;
+};
+struct globals2 {
+	uint32_t t_info; /* often used */
+	uint32_t t_tclass;
+	char *t_string;
+	int t_lineno;
+
+	var *intvar[NUM_INTERNAL_VARS]; /* often used */
+
+	/* former statics from various functions */
+	char *split_f0__fstrings;
+
+	uint32_t next_token__save_tclass;
+	uint32_t next_token__save_info;
+	uint32_t next_token__ltclass;
+	smallint next_token__concat_inserted;
+
+	smallint next_input_file__files_happen;
+	rstream next_input_file__rsm;
+
+	var *evaluate__fnargs;
+	unsigned evaluate__seed;
+	regex_t evaluate__sreg;
+
+	var ptest__v;
+
+	tsplitter exec_builtin__tspl;
+
+	/* biggest and least used members go last */
+	tsplitter fsplitter, rsplitter;
+};
+#define G1 (ptr_to_globals[-1])
+#define G (*(struct globals2 *)ptr_to_globals)
+/* For debug. nm --size-sort awk.o | grep -vi ' [tr] ' */
+/*char G1size[sizeof(G1)]; - 0x74 */
+/*char Gsize[sizeof(G)]; - 0x1c4 */
+/* Trying to keep most of members accessible with short offsets: */
+/*char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; - 0x90 */
+#define t_double     (G1.t_double    )
+#define beginseq     (G1.beginseq    )
+#define mainseq      (G1.mainseq     )
+#define endseq       (G1.endseq      )
+#define seq          (G1.seq         )
+#define break_ptr    (G1.break_ptr   )
+#define continue_ptr (G1.continue_ptr)
+#define iF           (G1.iF          )
+#define vhash        (G1.vhash       )
+#define ahash        (G1.ahash       )
+#define fdhash       (G1.fdhash      )
+#define fnhash       (G1.fnhash      )
+#define g_progname   (G1.g_progname  )
+#define g_lineno     (G1.g_lineno    )
+#define nfields      (G1.nfields     )
+#define maxfields    (G1.maxfields   )
+#define Fields       (G1.Fields      )
+#define g_cb         (G1.g_cb        )
+#define g_pos        (G1.g_pos       )
+#define g_buf        (G1.g_buf       )
+#define icase        (G1.icase       )
+#define exiting      (G1.exiting     )
+#define nextrec      (G1.nextrec     )
+#define nextfile     (G1.nextfile    )
+#define is_f0_split  (G1.is_f0_split )
+#define t_rollback   (G1.t_rollback  )
+#define t_info       (G.t_info      )
+#define t_tclass     (G.t_tclass    )
+#define t_string     (G.t_string    )
+#define t_lineno     (G.t_lineno    )
+#define intvar       (G.intvar      )
+#define fsplitter    (G.fsplitter   )
+#define rsplitter    (G.rsplitter   )
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS((char*)xzalloc(sizeof(G1)+sizeof(G)) + sizeof(G1)); \
+	G.next_token__ltclass = TC_OPTERM; \
+	G.evaluate__seed = 1; \
+} while (0)
+
+
+/* function prototypes */
+static void handle_special(var *);
+static node *parse_expr(uint32_t);
+static void chain_group(void);
+static var *evaluate(node *, var *);
+static rstream *next_input_file(void);
+static int fmt_num(char *, int, const char *, double, int);
+static int awk_exit(int) NORETURN;
+
+/* ---- error handling ---- */
+
+static const char EMSG_INTERNAL_ERROR[] ALIGN1 = "Internal error";
+static const char EMSG_UNEXP_EOS[] ALIGN1 = "Unexpected end of string";
+static const char EMSG_UNEXP_TOKEN[] ALIGN1 = "Unexpected token";
+static const char EMSG_DIV_BY_ZERO[] ALIGN1 = "Division by zero";
+static const char EMSG_INV_FMT[] ALIGN1 = "Invalid format specifier";
+static const char EMSG_TOO_FEW_ARGS[] ALIGN1 = "Too few arguments for builtin";
+static const char EMSG_NOT_ARRAY[] ALIGN1 = "Not an array";
+static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error";
+static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function";
+static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in";
+
+static void zero_out_var(var *vp)
+{
+	memset(vp, 0, sizeof(*vp));
+}
+
+static void syntax_error(const char *message) NORETURN;
+static void syntax_error(const char *message)
+{
+	bb_error_msg_and_die("%s:%i: %s", g_progname, g_lineno, message);
+}
+
+/* ---- hash stuff ---- */
+
+static unsigned hashidx(const char *name)
+{
+	unsigned idx = 0;
+
+	while (*name)
+		idx = *name++ + (idx << 6) - idx;
+	return idx;
+}
+
+/* create new hash */
+static xhash *hash_init(void)
+{
+	xhash *newhash;
+
+	newhash = xzalloc(sizeof(*newhash));
+	newhash->csize = FIRST_PRIME;
+	newhash->items = xzalloc(FIRST_PRIME * sizeof(newhash->items[0]));
+
+	return newhash;
+}
+
+/* find item in hash, return ptr to data, NULL if not found */
+static void *hash_search(xhash *hash, const char *name)
+{
+	hash_item *hi;
+
+	hi = hash->items[hashidx(name) % hash->csize];
+	while (hi) {
+		if (strcmp(hi->name, name) == 0)
+			return &hi->data;
+		hi = hi->next;
+	}
+	return NULL;
+}
+
+/* grow hash if it becomes too big */
+static void hash_rebuild(xhash *hash)
+{
+	unsigned newsize, i, idx;
+	hash_item **newitems, *hi, *thi;
+
+	if (hash->nprime == ARRAY_SIZE(PRIMES))
+		return;
+
+	newsize = PRIMES[hash->nprime++];
+	newitems = xzalloc(newsize * sizeof(newitems[0]));
+
+	for (i = 0; i < hash->csize; i++) {
+		hi = hash->items[i];
+		while (hi) {
+			thi = hi;
+			hi = thi->next;
+			idx = hashidx(thi->name) % newsize;
+			thi->next = newitems[idx];
+			newitems[idx] = thi;
+		}
+	}
+
+	free(hash->items);
+	hash->csize = newsize;
+	hash->items = newitems;
+}
+
+/* find item in hash, add it if necessary. Return ptr to data */
+static void *hash_find(xhash *hash, const char *name)
+{
+	hash_item *hi;
+	unsigned idx;
+	int l;
+
+	hi = hash_search(hash, name);
+	if (!hi) {
+		if (++hash->nel / hash->csize > 10)
+			hash_rebuild(hash);
+
+		l = strlen(name) + 1;
+		hi = xzalloc(sizeof(*hi) + l);
+		strcpy(hi->name, name);
+
+		idx = hashidx(name) % hash->csize;
+		hi->next = hash->items[idx];
+		hash->items[idx] = hi;
+		hash->glen += l;
+	}
+	return &hi->data;
+}
+
+#define findvar(hash, name) ((var*)    hash_find((hash), (name)))
+#define newvar(name)        ((var*)    hash_find(vhash, (name)))
+#define newfile(name)       ((rstream*)hash_find(fdhash, (name)))
+#define newfunc(name)       ((func*)   hash_find(fnhash, (name)))
+
+static void hash_remove(xhash *hash, const char *name)
+{
+	hash_item *hi, **phi;
+
+	phi = &hash->items[hashidx(name) % hash->csize];
+	while (*phi) {
+		hi = *phi;
+		if (strcmp(hi->name, name) == 0) {
+			hash->glen -= (strlen(name) + 1);
+			hash->nel--;
+			*phi = hi->next;
+			free(hi);
+			break;
+		}
+		phi = &hi->next;
+	}
+}
+
+/* ------ some useful functions ------ */
+
+static char *skip_spaces(char *p)
+{
+	while (1) {
+		if (*p == '\\' && p[1] == '\n') {
+			p++;
+			t_lineno++;
+		} else if (*p != ' ' && *p != '\t') {
+			break;
+		}
+		p++;
+	}
+	return p;
+}
+
+/* returns old *s, advances *s past word and terminating NUL */
+static char *nextword(char **s)
+{
+	char *p = *s;
+	while (*(*s)++ != '\0')
+		continue;
+	return p;
+}
+
+static char nextchar(char **s)
+{
+	char c, *pps;
+
+	c = *(*s)++;
+	pps = *s;
+	if (c == '\\')
+		c = bb_process_escape_sequence((const char**)s);
+	/* Example awk statement:
+	 * s = "abc\"def"
+	 * we must treat \" as "
+	 */
+	if (c == '\\' && *s == pps) { /* unrecognized \z? */
+		c = *(*s); /* yes, fetch z */
+		if (c)
+			(*s)++; /* advance unless z = NUL */
+	}
+	return c;
+}
+
+/* TODO: merge with strcpy_and_process_escape_sequences()?
+ */
+static void unescape_string_in_place(char *s1)
+{
+	char *s = s1;
+	while ((*s1 = nextchar(&s)) != '\0')
+		s1++;
+}
+
+static ALWAYS_INLINE int isalnum_(int c)
+{
+	return (isalnum(c) || c == '_');
+}
+
+static double my_strtod(char **pp)
+{
+	char *cp = *pp;
+	if (ENABLE_DESKTOP && cp[0] == '0') {
+		/* Might be hex or octal integer: 0x123abc or 07777 */
+		char c = (cp[1] | 0x20);
+		if (c == 'x' || isdigit(cp[1])) {
+			unsigned long long ull = strtoull(cp, pp, 0);
+			if (c == 'x')
+				return ull;
+			c = **pp;
+			if (!isdigit(c) && c != '.')
+				return ull;
+			/* else: it may be a floating number. Examples:
+			 * 009.123 (*pp points to '9')
+			 * 000.123 (*pp points to '.')
+			 * fall through to strtod.
+			 */
+		}
+	}
+	return strtod(cp, pp);
+}
+
+/* -------- working with variables (set/get/copy/etc) -------- */
+
+static xhash *iamarray(var *v)
+{
+	var *a = v;
+
+	while (a->type & VF_CHILD)
+		a = a->x.parent;
+
+	if (!(a->type & VF_ARRAY)) {
+		a->type |= VF_ARRAY;
+		a->x.array = hash_init();
+	}
+	return a->x.array;
+}
+
+static void clear_array(xhash *array)
+{
+	unsigned i;
+	hash_item *hi, *thi;
+
+	for (i = 0; i < array->csize; i++) {
+		hi = array->items[i];
+		while (hi) {
+			thi = hi;
+			hi = hi->next;
+			free(thi->data.v.string);
+			free(thi);
+		}
+		array->items[i] = NULL;
+	}
+	array->glen = array->nel = 0;
+}
+
+/* clear a variable */
+static var *clrvar(var *v)
+{
+	if (!(v->type & VF_FSTR))
+		free(v->string);
+
+	v->type &= VF_DONTTOUCH;
+	v->type |= VF_DIRTY;
+	v->string = NULL;
+	return v;
+}
+
+/* assign string value to variable */
+static var *setvar_p(var *v, char *value)
+{
+	clrvar(v);
+	v->string = value;
+	handle_special(v);
+	return v;
+}
+
+/* same as setvar_p but make a copy of string */
+static var *setvar_s(var *v, const char *value)
+{
+	return setvar_p(v, (value && *value) ? xstrdup(value) : NULL);
+}
+
+/* same as setvar_s but sets USER flag */
+static var *setvar_u(var *v, const char *value)
+{
+	v = setvar_s(v, value);
+	v->type |= VF_USER;
+	return v;
+}
+
+/* set array element to user string */
+static void setari_u(var *a, int idx, const char *s)
+{
+	var *v;
+
+	v = findvar(iamarray(a), itoa(idx));
+	setvar_u(v, s);
+}
+
+/* assign numeric value to variable */
+static var *setvar_i(var *v, double value)
+{
+	clrvar(v);
+	v->type |= VF_NUMBER;
+	v->number = value;
+	handle_special(v);
+	return v;
+}
+
+static const char *getvar_s(var *v)
+{
+	/* if v is numeric and has no cached string, convert it to string */
+	if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) {
+		fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[CONVFMT]), v->number, TRUE);
+		v->string = xstrdup(g_buf);
+		v->type |= VF_CACHED;
+	}
+	return (v->string == NULL) ? "" : v->string;
+}
+
+static double getvar_i(var *v)
+{
+	char *s;
+
+	if ((v->type & (VF_NUMBER | VF_CACHED)) == 0) {
+		v->number = 0;
+		s = v->string;
+		if (s && *s) {
+			debug_printf_eval("getvar_i: '%s'->", s);
+			v->number = my_strtod(&s);
+			debug_printf_eval("%f (s:'%s')\n", v->number, s);
+			if (v->type & VF_USER) {
+				s = skip_spaces(s);
+				if (*s != '\0')
+					v->type &= ~VF_USER;
+			}
+		} else {
+			debug_printf_eval("getvar_i: '%s'->zero\n", s);
+			v->type &= ~VF_USER;
+		}
+		v->type |= VF_CACHED;
+	}
+	debug_printf_eval("getvar_i: %f\n", v->number);
+	return v->number;
+}
+
+/* Used for operands of bitwise ops */
+static unsigned long getvar_i_int(var *v)
+{
+	double d = getvar_i(v);
+
+	/* Casting doubles to longs is undefined for values outside
+	 * of target type range. Try to widen it as much as possible */
+	if (d >= 0)
+		return (unsigned long)d;
+	/* Why? Think about d == -4294967295.0 (assuming 32bit longs) */
+	return - (long) (unsigned long) (-d);
+}
+
+static var *copyvar(var *dest, const var *src)
+{
+	if (dest != src) {
+		clrvar(dest);
+		dest->type |= (src->type & ~(VF_DONTTOUCH | VF_FSTR));
+		debug_printf_eval("copyvar: number:%f string:'%s'\n", src->number, src->string);
+		dest->number = src->number;
+		if (src->string)
+			dest->string = xstrdup(src->string);
+	}
+	handle_special(dest);
+	return dest;
+}
+
+static var *incvar(var *v)
+{
+	return setvar_i(v, getvar_i(v) + 1.0);
+}
+
+/* return true if v is number or numeric string */
+static int is_numeric(var *v)
+{
+	getvar_i(v);
+	return ((v->type ^ VF_DIRTY) & (VF_NUMBER | VF_USER | VF_DIRTY));
+}
+
+/* return 1 when value of v corresponds to true, 0 otherwise */
+static int istrue(var *v)
+{
+	if (is_numeric(v))
+		return (v->number != 0);
+	return (v->string && v->string[0]);
+}
+
+/* temporary variables allocator. Last allocated should be first freed */
+static var *nvalloc(int n)
+{
+	nvblock *pb = NULL;
+	var *v, *r;
+	int size;
+
+	while (g_cb) {
+		pb = g_cb;
+		if ((g_cb->pos - g_cb->nv) + n <= g_cb->size)
+			break;
+		g_cb = g_cb->next;
+	}
+
+	if (!g_cb) {
+		size = (n <= MINNVBLOCK) ? MINNVBLOCK : n;
+		g_cb = xzalloc(sizeof(nvblock) + size * sizeof(var));
+		g_cb->size = size;
+		g_cb->pos = g_cb->nv;
+		g_cb->prev = pb;
+		/*g_cb->next = NULL; - xzalloc did it */
+		if (pb)
+			pb->next = g_cb;
+	}
+
+	v = r = g_cb->pos;
+	g_cb->pos += n;
+
+	while (v < g_cb->pos) {
+		v->type = 0;
+		v->string = NULL;
+		v++;
+	}
+
+	return r;
+}
+
+static void nvfree(var *v)
+{
+	var *p;
+
+	if (v < g_cb->nv || v >= g_cb->pos)
+		syntax_error(EMSG_INTERNAL_ERROR);
+
+	for (p = v; p < g_cb->pos; p++) {
+		if ((p->type & (VF_ARRAY | VF_CHILD)) == VF_ARRAY) {
+			clear_array(iamarray(p));
+			free(p->x.array->items);
+			free(p->x.array);
+		}
+		if (p->type & VF_WALK) {
+			walker_list *n;
+			walker_list *w = p->x.walker;
+			debug_printf_walker("nvfree: freeing walker @%p\n", &p->x.walker);
+			p->x.walker = NULL;
+			while (w) {
+				n = w->prev;
+				debug_printf_walker(" free(%p)\n", w);
+				free(w);
+				w = n;
+			}
+		}
+		clrvar(p);
+	}
+
+	g_cb->pos = v;
+	while (g_cb->prev && g_cb->pos == g_cb->nv) {
+		g_cb = g_cb->prev;
+	}
+}
+
+/* ------- awk program text parsing ------- */
+
+/* Parse next token pointed by global pos, place results into global ttt.
+ * If token isn't expected, give away. Return token class
+ */
+static uint32_t next_token(uint32_t expected)
+{
+#define concat_inserted (G.next_token__concat_inserted)
+#define save_tclass     (G.next_token__save_tclass)
+#define save_info       (G.next_token__save_info)
+/* Initialized to TC_OPTERM: */
+#define ltclass         (G.next_token__ltclass)
+
+	char *p, *s;
+	const char *tl;
+	uint32_t tc;
+	const uint32_t *ti;
+
+	if (t_rollback) {
+		t_rollback = FALSE;
+
+	} else if (concat_inserted) {
+		concat_inserted = FALSE;
+		t_tclass = save_tclass;
+		t_info = save_info;
+
+	} else {
+		p = g_pos;
+ readnext:
+		p = skip_spaces(p);
+		g_lineno = t_lineno;
+		if (*p == '#')
+			while (*p != '\n' && *p != '\0')
+				p++;
+
+		if (*p == '\n')
+			t_lineno++;
+
+		if (*p == '\0') {
+			tc = TC_EOF;
+			debug_printf_parse("%s: token found: TC_EOF\n", __func__);
+
+		} else if (*p == '\"') {
+			/* it's a string */
+			t_string = s = ++p;
+			while (*p != '\"') {
+				char *pp;
+				if (*p == '\0' || *p == '\n')
+					syntax_error(EMSG_UNEXP_EOS);
+				pp = p;
+				*s++ = nextchar(&pp);
+				p = pp;
+			}
+			p++;
+			*s = '\0';
+			tc = TC_STRING;
+			debug_printf_parse("%s: token found:'%s' TC_STRING\n", __func__, t_string);
+
+		} else if ((expected & TC_REGEXP) && *p == '/') {
+			/* it's regexp */
+			t_string = s = ++p;
+			while (*p != '/') {
+				if (*p == '\0' || *p == '\n')
+					syntax_error(EMSG_UNEXP_EOS);
+				*s = *p++;
+				if (*s++ == '\\') {
+					char *pp = p;
+					s[-1] = bb_process_escape_sequence((const char **)&pp);
+					if (*p == '\\')
+						*s++ = '\\';
+					if (pp == p)
+						*s++ = *p++;
+					else
+						p = pp;
+				}
+			}
+			p++;
+			*s = '\0';
+			tc = TC_REGEXP;
+			debug_printf_parse("%s: token found:'%s' TC_REGEXP\n", __func__, t_string);
+
+		} else if (*p == '.' || isdigit(*p)) {
+			/* it's a number */
+			char *pp = p;
+			t_double = my_strtod(&pp);
+			p = pp;
+			if (*p == '.')
+				syntax_error(EMSG_UNEXP_TOKEN);
+			tc = TC_NUMBER;
+			debug_printf_parse("%s: token found:%f TC_NUMBER\n", __func__, t_double);
+
+		} else {
+			/* search for something known */
+			tl = tokenlist;
+			tc = 0x00000001;
+			ti = tokeninfo;
+			while (*tl) {
+				int l = (unsigned char) *tl++;
+				if (l == (unsigned char) NTCC) {
+					tc <<= 1;
+					continue;
+				}
+				/* if token class is expected,
+				 * token matches,
+				 * and it's not a longer word,
+				 */
+				if ((tc & (expected | TC_WORD | TC_NEWLINE))
+				 && strncmp(p, tl, l) == 0
+				 && !((tc & TC_WORD) && isalnum_(p[l]))
+				) {
+					/* then this is what we are looking for */
+					t_info = *ti;
+					debug_printf_parse("%s: token found:'%.*s' t_info:%x\n", __func__, l, p, t_info);
+					p += l;
+					goto token_found;
+				}
+				ti++;
+				tl += l;
+			}
+			/* not a known token */
+
+			/* is it a name? (var/array/function) */
+			if (!isalnum_(*p))
+				syntax_error(EMSG_UNEXP_TOKEN); /* no */
+			/* yes */
+			t_string = --p;
+			while (isalnum_(*++p)) {
+				p[-1] = *p;
+			}
+			p[-1] = '\0';
+			tc = TC_VARIABLE;
+			/* also consume whitespace between functionname and bracket */
+			if (!(expected & TC_VARIABLE) || (expected & TC_ARRAY))
+				p = skip_spaces(p);
+			if (*p == '(') {
+				tc = TC_FUNCTION;
+				debug_printf_parse("%s: token found:'%s' TC_FUNCTION\n", __func__, t_string);
+			} else {
+				if (*p == '[') {
+					p++;
+					tc = TC_ARRAY;
+					debug_printf_parse("%s: token found:'%s' TC_ARRAY\n", __func__, t_string);
+				} else
+					debug_printf_parse("%s: token found:'%s' TC_VARIABLE\n", __func__, t_string);
+			}
+		}
+ token_found:
+		g_pos = p;
+
+		/* skipping newlines in some cases */
+		if ((ltclass & TC_NOTERM) && (tc & TC_NEWLINE))
+			goto readnext;
+
+		/* insert concatenation operator when needed */
+		if ((ltclass & TC_CONCAT1) && (tc & TC_CONCAT2) && (expected & TC_BINOP)) {
+			concat_inserted = TRUE;
+			save_tclass = tc;
+			save_info = t_info;
+			tc = TC_BINOP;
+			t_info = OC_CONCAT | SS | P(35);
+		}
+
+		t_tclass = tc;
+	}
+	ltclass = t_tclass;
+
+	/* Are we ready for this? */
+	if (!(ltclass & expected))
+		syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ?
+				EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN);
+
+	return ltclass;
+#undef concat_inserted
+#undef save_tclass
+#undef save_info
+#undef ltclass
+}
+
+static void rollback_token(void)
+{
+	t_rollback = TRUE;
+}
+
+static node *new_node(uint32_t info)
+{
+	node *n;
+
+	n = xzalloc(sizeof(node));
+	n->info = info;
+	n->lineno = g_lineno;
+	return n;
+}
+
+static void mk_re_node(const char *s, node *n, regex_t *re)
+{
+	n->info = OC_REGEXP;
+	n->l.re = re;
+	n->r.ire = re + 1;
+	xregcomp(re, s, REG_EXTENDED);
+	xregcomp(re + 1, s, REG_EXTENDED | REG_ICASE);
+}
+
+static node *condition(void)
+{
+	next_token(TC_SEQSTART);
+	return parse_expr(TC_SEQTERM);
+}
+
+/* parse expression terminated by given argument, return ptr
+ * to built subtree. Terminator is eaten by parse_expr */
+static node *parse_expr(uint32_t iexp)
+{
+	node sn;
+	node *cn = &sn;
+	node *vn, *glptr;
+	uint32_t tc, xtc;
+	var *v;
+
+	debug_printf_parse("%s(%x)\n", __func__, iexp);
+
+	sn.info = PRIMASK;
+	sn.r.n = glptr = NULL;
+	xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp;
+
+	while (!((tc = next_token(xtc)) & iexp)) {
+
+		if (glptr && (t_info == (OC_COMPARE | VV | P(39) | 2))) {
+			/* input redirection (<) attached to glptr node */
+			debug_printf_parse("%s: input redir\n", __func__);
+			cn = glptr->l.n = new_node(OC_CONCAT | SS | P(37));
+			cn->a.n = glptr;
+			xtc = TC_OPERAND | TC_UOPPRE;
+			glptr = NULL;
+
+		} else if (tc & (TC_BINOP | TC_UOPPOST)) {
+			debug_printf_parse("%s: TC_BINOP | TC_UOPPOST\n", __func__);
+			/* for binary and postfix-unary operators, jump back over
+			 * previous operators with higher priority */
+			vn = cn;
+			while (((t_info & PRIMASK) > (vn->a.n->info & PRIMASK2))
+			    || ((t_info == vn->info) && ((t_info & OPCLSMASK) == OC_COLON))
+			) {
+				vn = vn->a.n;
+			}
+			if ((t_info & OPCLSMASK) == OC_TERNARY)
+				t_info += P(6);
+			cn = vn->a.n->r.n = new_node(t_info);
+			cn->a.n = vn->a.n;
+			if (tc & TC_BINOP) {
+				cn->l.n = vn;
+				xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
+				if ((t_info & OPCLSMASK) == OC_PGETLINE) {
+					/* it's a pipe */
+					next_token(TC_GETLINE);
+					/* give maximum priority to this pipe */
+					cn->info &= ~PRIMASK;
+					xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
+				}
+			} else {
+				cn->r.n = vn;
+				xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
+			}
+			vn->a.n = cn;
+
+		} else {
+			debug_printf_parse("%s: other\n", __func__);
+			/* for operands and prefix-unary operators, attach them
+			 * to last node */
+			vn = cn;
+			cn = vn->r.n = new_node(t_info);
+			cn->a.n = vn;
+			xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
+			if (tc & (TC_OPERAND | TC_REGEXP)) {
+				debug_printf_parse("%s: TC_OPERAND | TC_REGEXP\n", __func__);
+				xtc = TC_UOPPRE | TC_UOPPOST | TC_BINOP | TC_OPERAND | iexp;
+				/* one should be very careful with switch on tclass -
+				 * only simple tclasses should be used! */
+				switch (tc) {
+				case TC_VARIABLE:
+				case TC_ARRAY:
+					debug_printf_parse("%s: TC_VARIABLE | TC_ARRAY\n", __func__);
+					cn->info = OC_VAR;
+					v = hash_search(ahash, t_string);
+					if (v != NULL) {
+						cn->info = OC_FNARG;
+						cn->l.aidx = v->x.aidx;
+					} else {
+						cn->l.v = newvar(t_string);
+					}
+					if (tc & TC_ARRAY) {
+						cn->info |= xS;
+						cn->r.n = parse_expr(TC_ARRTERM);
+					}
+					break;
+
+				case TC_NUMBER:
+				case TC_STRING:
+					debug_printf_parse("%s: TC_NUMBER | TC_STRING\n", __func__);
+					cn->info = OC_VAR;
+					v = cn->l.v = xzalloc(sizeof(var));
+					if (tc & TC_NUMBER)
+						setvar_i(v, t_double);
+					else
+						setvar_s(v, t_string);
+					break;
+
+				case TC_REGEXP:
+					debug_printf_parse("%s: TC_REGEXP\n", __func__);
+					mk_re_node(t_string, cn, xzalloc(sizeof(regex_t)*2));
+					break;
+
+				case TC_FUNCTION:
+					debug_printf_parse("%s: TC_FUNCTION\n", __func__);
+					cn->info = OC_FUNC;
+					cn->r.f = newfunc(t_string);
+					cn->l.n = condition();
+					break;
+
+				case TC_SEQSTART:
+					debug_printf_parse("%s: TC_SEQSTART\n", __func__);
+					cn = vn->r.n = parse_expr(TC_SEQTERM);
+					if (!cn)
+						syntax_error("Empty sequence");
+					cn->a.n = vn;
+					break;
+
+				case TC_GETLINE:
+					debug_printf_parse("%s: TC_GETLINE\n", __func__);
+					glptr = cn;
+					xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
+					break;
+
+				case TC_BUILTIN:
+					debug_printf_parse("%s: TC_BUILTIN\n", __func__);
+					cn->l.n = condition();
+					break;
+				}
+			}
+		}
+	}
+
+	debug_printf_parse("%s() returns %p\n", __func__, sn.r.n);
+	return sn.r.n;
+}
+
+/* add node to chain. Return ptr to alloc'd node */
+static node *chain_node(uint32_t info)
+{
+	node *n;
+
+	if (!seq->first)
+		seq->first = seq->last = new_node(0);
+
+	if (seq->programname != g_progname) {
+		seq->programname = g_progname;
+		n = chain_node(OC_NEWSOURCE);
+		n->l.new_progname = xstrdup(g_progname);
+	}
+
+	n = seq->last;
+	n->info = info;
+	seq->last = n->a.n = new_node(OC_DONE);
+
+	return n;
+}
+
+static void chain_expr(uint32_t info)
+{
+	node *n;
+
+	n = chain_node(info);
+	n->l.n = parse_expr(TC_OPTERM | TC_GRPTERM);
+	if (t_tclass & TC_GRPTERM)
+		rollback_token();
+}
+
+static node *chain_loop(node *nn)
+{
+	node *n, *n2, *save_brk, *save_cont;
+
+	save_brk = break_ptr;
+	save_cont = continue_ptr;
+
+	n = chain_node(OC_BR | Vx);
+	continue_ptr = new_node(OC_EXEC);
+	break_ptr = new_node(OC_EXEC);
+	chain_group();
+	n2 = chain_node(OC_EXEC | Vx);
+	n2->l.n = nn;
+	n2->a.n = n;
+	continue_ptr->a.n = n2;
+	break_ptr->a.n = n->r.n = seq->last;
+
+	continue_ptr = save_cont;
+	break_ptr = save_brk;
+
+	return n;
+}
+
+/* parse group and attach it to chain */
+static void chain_group(void)
+{
+	uint32_t c;
+	node *n, *n2, *n3;
+
+	do {
+		c = next_token(TC_GRPSEQ);
+	} while (c & TC_NEWLINE);
+
+	if (c & TC_GRPSTART) {
+		debug_printf_parse("%s: TC_GRPSTART\n", __func__);
+		while (next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) {
+			debug_printf_parse("%s: !TC_GRPTERM\n", __func__);
+			if (t_tclass & TC_NEWLINE)
+				continue;
+			rollback_token();
+			chain_group();
+		}
+		debug_printf_parse("%s: TC_GRPTERM\n", __func__);
+	} else if (c & (TC_OPSEQ | TC_OPTERM)) {
+		debug_printf_parse("%s: TC_OPSEQ | TC_OPTERM\n", __func__);
+		rollback_token();
+		chain_expr(OC_EXEC | Vx);
+	} else {
+		/* TC_STATEMNT */
+		debug_printf_parse("%s: TC_STATEMNT(?)\n", __func__);
+		switch (t_info & OPCLSMASK) {
+		case ST_IF:
+			debug_printf_parse("%s: ST_IF\n", __func__);
+			n = chain_node(OC_BR | Vx);
+			n->l.n = condition();
+			chain_group();
+			n2 = chain_node(OC_EXEC);
+			n->r.n = seq->last;
+			if (next_token(TC_GRPSEQ | TC_GRPTERM | TC_ELSE) == TC_ELSE) {
+				chain_group();
+				n2->a.n = seq->last;
+			} else {
+				rollback_token();
+			}
+			break;
+
+		case ST_WHILE:
+			debug_printf_parse("%s: ST_WHILE\n", __func__);
+			n2 = condition();
+			n = chain_loop(NULL);
+			n->l.n = n2;
+			break;
+
+		case ST_DO:
+			debug_printf_parse("%s: ST_DO\n", __func__);
+			n2 = chain_node(OC_EXEC);
+			n = chain_loop(NULL);
+			n2->a.n = n->a.n;
+			next_token(TC_WHILE);
+			n->l.n = condition();
+			break;
+
+		case ST_FOR:
+			debug_printf_parse("%s: ST_FOR\n", __func__);
+			next_token(TC_SEQSTART);
+			n2 = parse_expr(TC_SEMICOL | TC_SEQTERM);
+			if (t_tclass & TC_SEQTERM) {	/* for-in */
+				if ((n2->info & OPCLSMASK) != OC_IN)
+					syntax_error(EMSG_UNEXP_TOKEN);
+				n = chain_node(OC_WALKINIT | VV);
+				n->l.n = n2->l.n;
+				n->r.n = n2->r.n;
+				n = chain_loop(NULL);
+				n->info = OC_WALKNEXT | Vx;
+				n->l.n = n2->l.n;
+			} else {			/* for (;;) */
+				n = chain_node(OC_EXEC | Vx);
+				n->l.n = n2;
+				n2 = parse_expr(TC_SEMICOL);
+				n3 = parse_expr(TC_SEQTERM);
+				n = chain_loop(n3);
+				n->l.n = n2;
+				if (!n2)
+					n->info = OC_EXEC;
+			}
+			break;
+
+		case OC_PRINT:
+		case OC_PRINTF:
+			debug_printf_parse("%s: OC_PRINT[F]\n", __func__);
+			n = chain_node(t_info);
+			n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM);
+			if (t_tclass & TC_OUTRDR) {
+				n->info |= t_info;
+				n->r.n = parse_expr(TC_OPTERM | TC_GRPTERM);
+			}
+			if (t_tclass & TC_GRPTERM)
+				rollback_token();
+			break;
+
+		case OC_BREAK:
+			debug_printf_parse("%s: OC_BREAK\n", __func__);
+			n = chain_node(OC_EXEC);
+			n->a.n = break_ptr;
+			break;
+
+		case OC_CONTINUE:
+			debug_printf_parse("%s: OC_CONTINUE\n", __func__);
+			n = chain_node(OC_EXEC);
+			n->a.n = continue_ptr;
+			break;
+
+		/* delete, next, nextfile, return, exit */
+		default:
+			debug_printf_parse("%s: default\n", __func__);
+			chain_expr(t_info);
+		}
+	}
+}
+
+static void parse_program(char *p)
+{
+	uint32_t tclass;
+	node *cn;
+	func *f;
+	var *v;
+
+	g_pos = p;
+	t_lineno = 1;
+	while ((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART |
+			TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) {
+
+		if (tclass & TC_OPTERM) {
+			debug_printf_parse("%s: TC_OPTERM\n", __func__);
+			continue;
+		}
+
+		seq = &mainseq;
+		if (tclass & TC_BEGIN) {
+			debug_printf_parse("%s: TC_BEGIN\n", __func__);
+			seq = &beginseq;
+			chain_group();
+
+		} else if (tclass & TC_END) {
+			debug_printf_parse("%s: TC_END\n", __func__);
+			seq = &endseq;
+			chain_group();
+
+		} else if (tclass & TC_FUNCDECL) {
+			debug_printf_parse("%s: TC_FUNCDECL\n", __func__);
+			next_token(TC_FUNCTION);
+			g_pos++;
+			f = newfunc(t_string);
+			f->body.first = NULL;
+			f->nargs = 0;
+			while (next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) {
+				v = findvar(ahash, t_string);
+				v->x.aidx = f->nargs++;
+
+				if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
+					break;
+			}
+			seq = &f->body;
+			chain_group();
+			clear_array(ahash);
+
+		} else if (tclass & TC_OPSEQ) {
+			debug_printf_parse("%s: TC_OPSEQ\n", __func__);
+			rollback_token();
+			cn = chain_node(OC_TEST);
+			cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART);
+			if (t_tclass & TC_GRPSTART) {
+				debug_printf_parse("%s: TC_GRPSTART\n", __func__);
+				rollback_token();
+				chain_group();
+			} else {
+				debug_printf_parse("%s: !TC_GRPSTART\n", __func__);
+				chain_node(OC_PRINT);
+			}
+			cn->r.n = mainseq.last;
+
+		} else /* if (tclass & TC_GRPSTART) */ {
+			debug_printf_parse("%s: TC_GRPSTART(?)\n", __func__);
+			rollback_token();
+			chain_group();
+		}
+	}
+	debug_printf_parse("%s: TC_EOF\n", __func__);
+}
+
+
+/* -------- program execution part -------- */
+
+static node *mk_splitter(const char *s, tsplitter *spl)
+{
+	regex_t *re, *ire;
+	node *n;
+
+	re = &spl->re[0];
+	ire = &spl->re[1];
+	n = &spl->n;
+	if ((n->info & OPCLSMASK) == OC_REGEXP) {
+		regfree(re);
+		regfree(ire); // TODO: nuke ire, use re+1?
+	}
+	if (s[0] && s[1]) { /* strlen(s) > 1 */
+		mk_re_node(s, n, re);
+	} else {
+		n->info = (uint32_t) s[0];
+	}
+
+	return n;
+}
+
+/* use node as a regular expression. Supplied with node ptr and regex_t
+ * storage space. Return ptr to regex (if result points to preg, it should
+ * be later regfree'd manually
+ */
+static regex_t *as_regex(node *op, regex_t *preg)
+{
+	int cflags;
+	var *v;
+	const char *s;
+
+	if ((op->info & OPCLSMASK) == OC_REGEXP) {
+		return icase ? op->r.ire : op->l.re;
+	}
+	v = nvalloc(1);
+	s = getvar_s(evaluate(op, v));
+
+	cflags = icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED;
+	/* Testcase where REG_EXTENDED fails (unpaired '{'):
+	 * echo Hi | awk 'gsub("@(samp|code|file)\{","");'
+	 * gawk 3.1.5 eats this. We revert to ~REG_EXTENDED
+	 * (maybe gsub is not supposed to use REG_EXTENDED?).
+	 */
+	if (regcomp(preg, s, cflags)) {
+		cflags &= ~REG_EXTENDED;
+		xregcomp(preg, s, cflags);
+	}
+	nvfree(v);
+	return preg;
+}
+
+/* gradually increasing buffer.
+ * note that we reallocate even if n == old_size,
+ * and thus there is at least one extra allocated byte.
+ */
+static char* qrealloc(char *b, int n, int *size)
+{
+	if (!b || n >= *size) {
+		*size = n + (n>>1) + 80;
+		b = xrealloc(b, *size);
+	}
+	return b;
+}
+
+/* resize field storage space */
+static void fsrealloc(int size)
+{
+	int i;
+
+	if (size >= maxfields) {
+		i = maxfields;
+		maxfields = size + 16;
+		Fields = xrealloc(Fields, maxfields * sizeof(Fields[0]));
+		for (; i < maxfields; i++) {
+			Fields[i].type = VF_SPECIAL;
+			Fields[i].string = NULL;
+		}
+	}
+	/* if size < nfields, clear extra field variables */
+	for (i = size; i < nfields; i++) {
+		clrvar(Fields + i);
+	}
+	nfields = size;
+}
+
+static int awk_split(const char *s, node *spl, char **slist)
+{
+	int l, n;
+	char c[4];
+	char *s1;
+	regmatch_t pmatch[2]; // TODO: why [2]? [1] is enough...
+
+	/* in worst case, each char would be a separate field */
+	*slist = s1 = xzalloc(strlen(s) * 2 + 3);
+	strcpy(s1, s);
+
+	c[0] = c[1] = (char)spl->info;
+	c[2] = c[3] = '\0';
+	if (*getvar_s(intvar[RS]) == '\0')
+		c[2] = '\n';
+
+	n = 0;
+	if ((spl->info & OPCLSMASK) == OC_REGEXP) {  /* regex split */
+		if (!*s)
+			return n; /* "": zero fields */
+		n++; /* at least one field will be there */
+		do {
+			l = strcspn(s, c+2); /* len till next NUL or \n */
+			if (regexec(icase ? spl->r.ire : spl->l.re, s, 1, pmatch, 0) == 0
+			 && pmatch[0].rm_so <= l
+			) {
+				l = pmatch[0].rm_so;
+				if (pmatch[0].rm_eo == 0) {
+					l++;
+					pmatch[0].rm_eo++;
+				}
+				n++; /* we saw yet another delimiter */
+			} else {
+				pmatch[0].rm_eo = l;
+				if (s[l])
+					pmatch[0].rm_eo++;
+			}
+			memcpy(s1, s, l);
+			/* make sure we remove *all* of the separator chars */
+			do {
+				s1[l] = '\0';
+			} while (++l < pmatch[0].rm_eo);
+			nextword(&s1);
+			s += pmatch[0].rm_eo;
+		} while (*s);
+		return n;
+	}
+	if (c[0] == '\0') {  /* null split */
+		while (*s) {
+			*s1++ = *s++;
+			*s1++ = '\0';
+			n++;
+		}
+		return n;
+	}
+	if (c[0] != ' ') {  /* single-character split */
+		if (icase) {
+			c[0] = toupper(c[0]);
+			c[1] = tolower(c[1]);
+		}
+		if (*s1)
+			n++;
+		while ((s1 = strpbrk(s1, c)) != NULL) {
+			*s1++ = '\0';
+			n++;
+		}
+		return n;
+	}
+	/* space split */
+	while (*s) {
+		s = skip_whitespace(s);
+		if (!*s)
+			break;
+		n++;
+		while (*s && !isspace(*s))
+			*s1++ = *s++;
+		*s1++ = '\0';
+	}
+	return n;
+}
+
+static void split_f0(void)
+{
+/* static char *fstrings; */
+#define fstrings (G.split_f0__fstrings)
+
+	int i, n;
+	char *s;
+
+	if (is_f0_split)
+		return;
+
+	is_f0_split = TRUE;
+	free(fstrings);
+	fsrealloc(0);
+	n = awk_split(getvar_s(intvar[F0]), &fsplitter.n, &fstrings);
+	fsrealloc(n);
+	s = fstrings;
+	for (i = 0; i < n; i++) {
+		Fields[i].string = nextword(&s);
+		Fields[i].type |= (VF_FSTR | VF_USER | VF_DIRTY);
+	}
+
+	/* set NF manually to avoid side effects */
+	clrvar(intvar[NF]);
+	intvar[NF]->type = VF_NUMBER | VF_SPECIAL;
+	intvar[NF]->number = nfields;
+#undef fstrings
+}
+
+/* perform additional actions when some internal variables changed */
+static void handle_special(var *v)
+{
+	int n;
+	char *b;
+	const char *sep, *s;
+	int sl, l, len, i, bsize;
+
+	if (!(v->type & VF_SPECIAL))
+		return;
+
+	if (v == intvar[NF]) {
+		n = (int)getvar_i(v);
+		fsrealloc(n);
+
+		/* recalculate $0 */
+		sep = getvar_s(intvar[OFS]);
+		sl = strlen(sep);
+		b = NULL;
+		len = 0;
+		for (i = 0; i < n; i++) {
+			s = getvar_s(&Fields[i]);
+			l = strlen(s);
+			if (b) {
+				memcpy(b+len, sep, sl);
+				len += sl;
+			}
+			b = qrealloc(b, len+l+sl, &bsize);
+			memcpy(b+len, s, l);
+			len += l;
+		}
+		if (b)
+			b[len] = '\0';
+		setvar_p(intvar[F0], b);
+		is_f0_split = TRUE;
+
+	} else if (v == intvar[F0]) {
+		is_f0_split = FALSE;
+
+	} else if (v == intvar[FS]) {
+		/*
+		 * The POSIX-2008 standard says that changing FS should have no effect on the
+		 * current input line, but only on the next one. The language is:
+		 *
+		 * > Before the first reference to a field in the record is evaluated, the record
+		 * > shall be split into fields, according to the rules in Regular Expressions,
+		 * > using the value of FS that was current at the time the record was read.
+		 *
+		 * So, split up current line before assignment to FS:
+		 */
+		split_f0();
+
+		mk_splitter(getvar_s(v), &fsplitter);
+
+	} else if (v == intvar[RS]) {
+		mk_splitter(getvar_s(v), &rsplitter);
+
+	} else if (v == intvar[IGNORECASE]) {
+		icase = istrue(v);
+
+	} else {				/* $n */
+		n = getvar_i(intvar[NF]);
+		setvar_i(intvar[NF], n > v-Fields ? n : v-Fields+1);
+		/* right here v is invalid. Just to note... */
+	}
+}
+
+/* step through func/builtin/etc arguments */
+static node *nextarg(node **pn)
+{
+	node *n;
+
+	n = *pn;
+	if (n && (n->info & OPCLSMASK) == OC_COMMA) {
+		*pn = n->r.n;
+		n = n->l.n;
+	} else {
+		*pn = NULL;
+	}
+	return n;
+}
+
+static void hashwalk_init(var *v, xhash *array)
+{
+	hash_item *hi;
+	unsigned i;
+	walker_list *w;
+	walker_list *prev_walker;
+
+	if (v->type & VF_WALK) {
+		prev_walker = v->x.walker;
+	} else {
+		v->type |= VF_WALK;
+		prev_walker = NULL;
+	}
+	debug_printf_walker("hashwalk_init: prev_walker:%p\n", prev_walker);
+
+	w = v->x.walker = xzalloc(sizeof(*w) + array->glen + 1); /* why + 1? */
+	debug_printf_walker(" walker@%p=%p\n", &v->x.walker, w);
+	w->cur = w->end = w->wbuf;
+	w->prev = prev_walker;
+	for (i = 0; i < array->csize; i++) {
+		hi = array->items[i];
+		while (hi) {
+			strcpy(w->end, hi->name);
+			nextword(&w->end);
+			hi = hi->next;
+		}
+	}
+}
+
+static int hashwalk_next(var *v)
+{
+	walker_list *w = v->x.walker;
+
+	if (w->cur >= w->end) {
+		walker_list *prev_walker = w->prev;
+
+		debug_printf_walker("end of iteration, free(walker@%p:%p), prev_walker:%p\n", &v->x.walker, w, prev_walker);
+		free(w);
+		v->x.walker = prev_walker;
+		return FALSE;
+	}
+
+	setvar_s(v, nextword(&w->cur));
+	return TRUE;
+}
+
+/* evaluate node, return 1 when result is true, 0 otherwise */
+static int ptest(node *pattern)
+{
+	/* ptest__v is "static": to save stack space? */
+	return istrue(evaluate(pattern, &G.ptest__v));
+}
+
+/* read next record from stream rsm into a variable v */
+static int awk_getline(rstream *rsm, var *v)
+{
+	char *b;
+	regmatch_t pmatch[2];
+	int size, a, p, pp = 0;
+	int fd, so, eo, r, rp;
+	char c, *m, *s;
+
+	debug_printf_eval("entered %s()\n", __func__);
+
+	/* we're using our own buffer since we need access to accumulating
+	 * characters
+	 */
+	fd = fileno(rsm->F);
+	m = rsm->buffer;
+	a = rsm->adv;
+	p = rsm->pos;
+	size = rsm->size;
+	c = (char) rsplitter.n.info;
+	rp = 0;
+
+	if (!m)
+		m = qrealloc(m, 256, &size);
+
+	do {
+		b = m + a;
+		so = eo = p;
+		r = 1;
+		if (p > 0) {
+			if ((rsplitter.n.info & OPCLSMASK) == OC_REGEXP) {
+				if (regexec(icase ? rsplitter.n.r.ire : rsplitter.n.l.re,
+							b, 1, pmatch, 0) == 0) {
+					so = pmatch[0].rm_so;
+					eo = pmatch[0].rm_eo;
+					if (b[eo] != '\0')
+						break;
+				}
+			} else if (c != '\0') {
+				s = strchr(b+pp, c);
+				if (!s)
+					s = memchr(b+pp, '\0', p - pp);
+				if (s) {
+					so = eo = s-b;
+					eo++;
+					break;
+				}
+			} else {
+				while (b[rp] == '\n')
+					rp++;
+				s = strstr(b+rp, "\n\n");
+				if (s) {
+					so = eo = s-b;
+					while (b[eo] == '\n')
+						eo++;
+					if (b[eo] != '\0')
+						break;
+				}
+			}
+		}
+
+		if (a > 0) {
+			memmove(m, m+a, p+1);
+			b = m;
+			a = 0;
+		}
+
+		m = qrealloc(m, a+p+128, &size);
+		b = m + a;
+		pp = p;
+		p += safe_read(fd, b+p, size-p-1);
+		if (p < pp) {
+			p = 0;
+			r = 0;
+			setvar_i(intvar[ERRNO], errno);
+		}
+		b[p] = '\0';
+
+	} while (p > pp);
+
+	if (p == 0) {
+		r--;
+	} else {
+		c = b[so]; b[so] = '\0';
+		setvar_s(v, b+rp);
+		v->type |= VF_USER;
+		b[so] = c;
+		c = b[eo]; b[eo] = '\0';
+		setvar_s(intvar[RT], b+so);
+		b[eo] = c;
+	}
+
+	rsm->buffer = m;
+	rsm->adv = a + eo;
+	rsm->pos = p - eo;
+	rsm->size = size;
+
+	debug_printf_eval("returning from %s(): %d\n", __func__, r);
+
+	return r;
+}
+
+static int fmt_num(char *b, int size, const char *format, double n, int int_as_int)
+{
+	int r = 0;
+	char c;
+	const char *s = format;
+
+	if (int_as_int && n == (int)n) {
+		r = snprintf(b, size, "%d", (int)n);
+	} else {
+		do { c = *s; } while (c && *++s);
+		if (strchr("diouxX", c)) {
+			r = snprintf(b, size, format, (int)n);
+		} else if (strchr("eEfgG", c)) {
+			r = snprintf(b, size, format, n);
+		} else {
+			syntax_error(EMSG_INV_FMT);
+		}
+	}
+	return r;
+}
+
+/* formatted output into an allocated buffer, return ptr to buffer */
+static char *awk_printf(node *n)
+{
+	char *b = NULL;
+	char *fmt, *s, *f;
+	const char *s1;
+	int i, j, incr, bsize;
+	char c, c1;
+	var *v, *arg;
+
+	v = nvalloc(1);
+	fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), v)));
+
+	i = 0;
+	while (*f) {
+		s = f;
+		while (*f && (*f != '%' || *++f == '%'))
+			f++;
+		while (*f && !isalpha(*f)) {
+			if (*f == '*')
+				syntax_error("%*x formats are not supported");
+			f++;
+		}
+
+		incr = (f - s) + MAXVARFMT;
+		b = qrealloc(b, incr + i, &bsize);
+		c = *f;
+		if (c != '\0')
+			f++;
+		c1 = *f;
+		*f = '\0';
+		arg = evaluate(nextarg(&n), v);
+
+		j = i;
+		if (c == 'c' || !c) {
+			i += sprintf(b+i, s, is_numeric(arg) ?
+					(char)getvar_i(arg) : *getvar_s(arg));
+		} else if (c == 's') {
+			s1 = getvar_s(arg);
+			b = qrealloc(b, incr+i+strlen(s1), &bsize);
+			i += sprintf(b+i, s, s1);
+		} else {
+			i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE);
+		}
+		*f = c1;
+
+		/* if there was an error while sprintf, return value is negative */
+		if (i < j)
+			i = j;
+	}
+
+	free(fmt);
+	nvfree(v);
+	b = xrealloc(b, i + 1);
+	b[i] = '\0';
+	return b;
+}
+
+/* Common substitution routine.
+ * Replace (nm)'th substring of (src) that matches (rn) with (repl),
+ * store result into (dest), return number of substitutions.
+ * If nm = 0, replace all matches.
+ * If src or dst is NULL, use $0.
+ * If subexp != 0, enable subexpression matching (\1-\9).
+ */
+static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int subexp)
+{
+	char *resbuf;
+	const char *sp;
+	int match_no, residx, replen, resbufsize;
+	int regexec_flags;
+	regmatch_t pmatch[10];
+	regex_t sreg, *regex;
+
+	resbuf = NULL;
+	residx = 0;
+	match_no = 0;
+	regexec_flags = 0;
+	regex = as_regex(rn, &sreg);
+	sp = getvar_s(src ? src : intvar[F0]);
+	replen = strlen(repl);
+	while (regexec(regex, sp, 10, pmatch, regexec_flags) == 0) {
+		int so = pmatch[0].rm_so;
+		int eo = pmatch[0].rm_eo;
+
+		//bb_error_msg("match %u: [%u,%u] '%s'%p", match_no+1, so, eo, sp,sp);
+		resbuf = qrealloc(resbuf, residx + eo + replen, &resbufsize);
+		memcpy(resbuf + residx, sp, eo);
+		residx += eo;
+		if (++match_no >= nm) {
+			const char *s;
+			int nbs;
+
+			/* replace */
+			residx -= (eo - so);
+			nbs = 0;
+			for (s = repl; *s; s++) {
+				char c = resbuf[residx++] = *s;
+				if (c == '\\') {
+					nbs++;
+					continue;
+				}
+				if (c == '&' || (subexp && c >= '0' && c <= '9')) {
+					int j;
+					residx -= ((nbs + 3) >> 1);
+					j = 0;
+					if (c != '&') {
+						j = c - '0';
+						nbs++;
+					}
+					if (nbs % 2) {
+						resbuf[residx++] = c;
+					} else {
+						int n = pmatch[j].rm_eo - pmatch[j].rm_so;
+						resbuf = qrealloc(resbuf, residx + replen + n, &resbufsize);
+						memcpy(resbuf + residx, sp + pmatch[j].rm_so, n);
+						residx += n;
+					}
+				}
+				nbs = 0;
+			}
+		}
+
+		regexec_flags = REG_NOTBOL;
+		sp += eo;
+		if (match_no == nm)
+			break;
+		if (eo == so) {
+			/* Empty match (e.g. "b*" will match anywhere).
+			 * Advance by one char. */
+//BUG (bug 1333):
+//gsub(/\<b*/,"") on "abc" will reach this point, advance to "bc"
+//... and will erroneously match "b" even though it is NOT at the word start.
+//we need REG_NOTBOW but it does not exist...
+//TODO: if EXTRA_COMPAT=y, use GNU matching and re_search,
+//it should be able to do it correctly.
+			/* Subtle: this is safe only because
+			 * qrealloc allocated at least one extra byte */
+			resbuf[residx] = *sp;
+			if (*sp == '\0')
+				goto ret;
+			sp++;
+			residx++;
+		}
+	}
+
+	resbuf = qrealloc(resbuf, residx + strlen(sp), &resbufsize);
+	strcpy(resbuf + residx, sp);
+ ret:
+	//bb_error_msg("end sp:'%s'%p", sp,sp);
+	setvar_p(dest ? dest : intvar[F0], resbuf);
+	if (regex == &sreg)
+		regfree(regex);
+	return match_no;
+}
+
+static NOINLINE int do_mktime(const char *ds)
+{
+	struct tm then;
+	int count;
+
+	/*memset(&then, 0, sizeof(then)); - not needed */
+	then.tm_isdst = -1; /* default is unknown */
+
+	/* manpage of mktime says these fields are ints,
+	 * so we can sscanf stuff directly into them */
+	count = sscanf(ds, "%u %u %u %u %u %u %d",
+		&then.tm_year, &then.tm_mon, &then.tm_mday,
+		&then.tm_hour, &then.tm_min, &then.tm_sec,
+		&then.tm_isdst);
+
+	if (count < 6
+	 || (unsigned)then.tm_mon < 1
+	 || (unsigned)then.tm_year < 1900
+	) {
+		return -1;
+	}
+
+	then.tm_mon -= 1;
+	then.tm_year -= 1900;
+
+	return mktime(&then);
+}
+
+static NOINLINE var *exec_builtin(node *op, var *res)
+{
+#define tspl (G.exec_builtin__tspl)
+
+	var *tv;
+	node *an[4];
+	var *av[4];
+	const char *as[4];
+	regmatch_t pmatch[2];
+	regex_t sreg, *re;
+	node *spl;
+	uint32_t isr, info;
+	int nargs;
+	time_t tt;
+	int i, l, ll, n;
+
+	tv = nvalloc(4);
+	isr = info = op->info;
+	op = op->l.n;
+
+	av[2] = av[3] = NULL;
+	for (i = 0; i < 4 && op; i++) {
+		an[i] = nextarg(&op);
+		if (isr & 0x09000000)
+			av[i] = evaluate(an[i], &tv[i]);
+		if (isr & 0x08000000)
+			as[i] = getvar_s(av[i]);
+		isr >>= 1;
+	}
+
+	nargs = i;
+	if ((uint32_t)nargs < (info >> 30))
+		syntax_error(EMSG_TOO_FEW_ARGS);
+
+	info &= OPNMASK;
+	switch (info) {
+
+	case B_a2:
+		if (ENABLE_FEATURE_AWK_LIBM)
+			setvar_i(res, atan2(getvar_i(av[0]), getvar_i(av[1])));
+		else
+			syntax_error(EMSG_NO_MATH);
+		break;
+
+	case B_sp: {
+		char *s, *s1;
+
+		if (nargs > 2) {
+			spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ?
+				an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl);
+		} else {
+			spl = &fsplitter.n;
+		}
+
+		n = awk_split(as[0], spl, &s);
+		s1 = s;
+		clear_array(iamarray(av[1]));
+		for (i = 1; i <= n; i++)
+			setari_u(av[1], i, nextword(&s));
+		free(s1);
+		setvar_i(res, n);
+		break;
+	}
+
+	case B_ss: {
+		char *s;
+
+		l = strlen(as[0]);
+		i = getvar_i(av[1]) - 1;
+		if (i > l)
+			i = l;
+		if (i < 0)
+			i = 0;
+		n = (nargs > 2) ? getvar_i(av[2]) : l-i;
+		if (n < 0)
+			n = 0;
+		s = xstrndup(as[0]+i, n);
+		setvar_p(res, s);
+		break;
+	}
+
+	/* Bitwise ops must assume that operands are unsigned. GNU Awk 3.1.5:
+	 * awk '{ print or(-1,1) }' gives "4.29497e+09", not "-2.xxxe+09" */
+	case B_an:
+		setvar_i(res, getvar_i_int(av[0]) & getvar_i_int(av[1]));
+		break;
+
+	case B_co:
+		setvar_i(res, ~getvar_i_int(av[0]));
+		break;
+
+	case B_ls:
+		setvar_i(res, getvar_i_int(av[0]) << getvar_i_int(av[1]));
+		break;
+
+	case B_or:
+		setvar_i(res, getvar_i_int(av[0]) | getvar_i_int(av[1]));
+		break;
+
+	case B_rs:
+		setvar_i(res, getvar_i_int(av[0]) >> getvar_i_int(av[1]));
+		break;
+
+	case B_xo:
+		setvar_i(res, getvar_i_int(av[0]) ^ getvar_i_int(av[1]));
+		break;
+
+	case B_lo:
+	case B_up: {
+		char *s, *s1;
+		s1 = s = xstrdup(as[0]);
+		while (*s1) {
+			//*s1 = (info == B_up) ? toupper(*s1) : tolower(*s1);
+			if ((unsigned char)((*s1 | 0x20) - 'a') <= ('z' - 'a'))
+				*s1 = (info == B_up) ? (*s1 & 0xdf) : (*s1 | 0x20);
+			s1++;
+		}
+		setvar_p(res, s);
+		break;
+	}
+
+	case B_ix:
+		n = 0;
+		ll = strlen(as[1]);
+		l = strlen(as[0]) - ll;
+		if (ll > 0 && l >= 0) {
+			if (!icase) {
+				char *s = strstr(as[0], as[1]);
+				if (s)
+					n = (s - as[0]) + 1;
+			} else {
+				/* this piece of code is terribly slow and
+				 * really should be rewritten
+				 */
+				for (i = 0; i <= l; i++) {
+					if (strncasecmp(as[0]+i, as[1], ll) == 0) {
+						n = i+1;
+						break;
+					}
+				}
+			}
+		}
+		setvar_i(res, n);
+		break;
+
+	case B_ti:
+		if (nargs > 1)
+			tt = getvar_i(av[1]);
+		else
+			time(&tt);
+		//s = (nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y";
+		i = strftime(g_buf, MAXVARFMT,
+			((nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y"),
+			localtime(&tt));
+		g_buf[i] = '\0';
+		setvar_s(res, g_buf);
+		break;
+
+	case B_mt:
+		setvar_i(res, do_mktime(as[0]));
+		break;
+
+	case B_ma:
+		re = as_regex(an[1], &sreg);
+		n = regexec(re, as[0], 1, pmatch, 0);
+		if (n == 0) {
+			pmatch[0].rm_so++;
+			pmatch[0].rm_eo++;
+		} else {
+			pmatch[0].rm_so = 0;
+			pmatch[0].rm_eo = -1;
+		}
+		setvar_i(newvar("RSTART"), pmatch[0].rm_so);
+		setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so);
+		setvar_i(res, pmatch[0].rm_so);
+		if (re == &sreg)
+			regfree(re);
+		break;
+
+	case B_ge:
+		awk_sub(an[0], as[1], getvar_i(av[2]), av[3], res, TRUE);
+		break;
+
+	case B_gs:
+		setvar_i(res, awk_sub(an[0], as[1], 0, av[2], av[2], FALSE));
+		break;
+
+	case B_su:
+		setvar_i(res, awk_sub(an[0], as[1], 1, av[2], av[2], FALSE));
+		break;
+	}
+
+	nvfree(tv);
+	return res;
+#undef tspl
+}
+
+/*
+ * Evaluate node - the heart of the program. Supplied with subtree
+ * and place where to store result. returns ptr to result.
+ */
+#define XC(n) ((n) >> 8)
+
+static var *evaluate(node *op, var *res)
+{
+/* This procedure is recursive so we should count every byte */
+#define fnargs (G.evaluate__fnargs)
+/* seed is initialized to 1 */
+#define seed   (G.evaluate__seed)
+#define sreg   (G.evaluate__sreg)
+
+	var *v1;
+
+	if (!op)
+		return setvar_s(res, NULL);
+
+	debug_printf_eval("entered %s()\n", __func__);
+
+	v1 = nvalloc(2);
+
+	while (op) {
+		struct {
+			var *v;
+			const char *s;
+		} L = L; /* for compiler */
+		struct {
+			var *v;
+			const char *s;
+		} R = R;
+		double L_d = L_d;
+		uint32_t opinfo;
+		int opn;
+		node *op1;
+
+		opinfo = op->info;
+		opn = (opinfo & OPNMASK);
+		g_lineno = op->lineno;
+		op1 = op->l.n;
+		debug_printf_eval("opinfo:%08x opn:%08x\n", opinfo, opn);
+
+		/* execute inevitable things */
+		if (opinfo & OF_RES1)
+			L.v = evaluate(op1, v1);
+		if (opinfo & OF_RES2)
+			R.v = evaluate(op->r.n, v1+1);
+		if (opinfo & OF_STR1) {
+			L.s = getvar_s(L.v);
+			debug_printf_eval("L.s:'%s'\n", L.s);
+		}
+		if (opinfo & OF_STR2) {
+			R.s = getvar_s(R.v);
+			debug_printf_eval("R.s:'%s'\n", R.s);
+		}
+		if (opinfo & OF_NUM1) {
+			L_d = getvar_i(L.v);
+			debug_printf_eval("L_d:%f\n", L_d);
+		}
+
+		debug_printf_eval("switch(0x%x)\n", XC(opinfo & OPCLSMASK));
+		switch (XC(opinfo & OPCLSMASK)) {
+
+		/* -- iterative node type -- */
+
+		/* test pattern */
+		case XC( OC_TEST ):
+			if ((op1->info & OPCLSMASK) == OC_COMMA) {
+				/* it's range pattern */
+				if ((opinfo & OF_CHECKED) || ptest(op1->l.n)) {
+					op->info |= OF_CHECKED;
+					if (ptest(op1->r.n))
+						op->info &= ~OF_CHECKED;
+					op = op->a.n;
+				} else {
+					op = op->r.n;
+				}
+			} else {
+				op = ptest(op1) ? op->a.n : op->r.n;
+			}
+			break;
+
+		/* just evaluate an expression, also used as unconditional jump */
+		case XC( OC_EXEC ):
+			break;
+
+		/* branch, used in if-else and various loops */
+		case XC( OC_BR ):
+			op = istrue(L.v) ? op->a.n : op->r.n;
+			break;
+
+		/* initialize for-in loop */
+		case XC( OC_WALKINIT ):
+			hashwalk_init(L.v, iamarray(R.v));
+			break;
+
+		/* get next array item */
+		case XC( OC_WALKNEXT ):
+			op = hashwalk_next(L.v) ? op->a.n : op->r.n;
+			break;
+
+		case XC( OC_PRINT ):
+		case XC( OC_PRINTF ): {
+			FILE *F = stdout;
+
+			if (op->r.n) {
+				rstream *rsm = newfile(R.s);
+				if (!rsm->F) {
+					if (opn == '|') {
+						rsm->F = popen(R.s, "w");
+						if (rsm->F == NULL)
+							bb_perror_msg_and_die("popen");
+						rsm->is_pipe = 1;
+					} else {
+						rsm->F = xfopen(R.s, opn=='w' ? "w" : "a");
+					}
+				}
+				F = rsm->F;
+			}
+
+			if ((opinfo & OPCLSMASK) == OC_PRINT) {
+				if (!op1) {
+					fputs(getvar_s(intvar[F0]), F);
+				} else {
+					while (op1) {
+						var *v = evaluate(nextarg(&op1), v1);
+						if (v->type & VF_NUMBER) {
+							fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[OFMT]),
+									getvar_i(v), TRUE);
+							fputs(g_buf, F);
+						} else {
+							fputs(getvar_s(v), F);
+						}
+
+						if (op1)
+							fputs(getvar_s(intvar[OFS]), F);
+					}
+				}
+				fputs(getvar_s(intvar[ORS]), F);
+
+			} else {	/* OC_PRINTF */
+				char *s = awk_printf(op1);
+				fputs(s, F);
+				free(s);
+			}
+			fflush(F);
+			break;
+		}
+
+		case XC( OC_DELETE ): {
+			uint32_t info = op1->info & OPCLSMASK;
+			var *v;
+
+			if (info == OC_VAR) {
+				v = op1->l.v;
+			} else if (info == OC_FNARG) {
+				v = &fnargs[op1->l.aidx];
+			} else {
+				syntax_error(EMSG_NOT_ARRAY);
+			}
+
+			if (op1->r.n) {
+				const char *s;
+				clrvar(L.v);
+				s = getvar_s(evaluate(op1->r.n, v1));
+				hash_remove(iamarray(v), s);
+			} else {
+				clear_array(iamarray(v));
+			}
+			break;
+		}
+
+		case XC( OC_NEWSOURCE ):
+			g_progname = op->l.new_progname;
+			break;
+
+		case XC( OC_RETURN ):
+			copyvar(res, L.v);
+			break;
+
+		case XC( OC_NEXTFILE ):
+			nextfile = TRUE;
+		case XC( OC_NEXT ):
+			nextrec = TRUE;
+		case XC( OC_DONE ):
+			clrvar(res);
+			break;
+
+		case XC( OC_EXIT ):
+			awk_exit(L_d);
+
+		/* -- recursive node type -- */
+
+		case XC( OC_VAR ):
+			L.v = op->l.v;
+			if (L.v == intvar[NF])
+				split_f0();
+			goto v_cont;
+
+		case XC( OC_FNARG ):
+			L.v = &fnargs[op->l.aidx];
+ v_cont:
+			res = op->r.n ? findvar(iamarray(L.v), R.s) : L.v;
+			break;
+
+		case XC( OC_IN ):
+			setvar_i(res, hash_search(iamarray(R.v), L.s) ? 1 : 0);
+			break;
+
+		case XC( OC_REGEXP ):
+			op1 = op;
+			L.s = getvar_s(intvar[F0]);
+			goto re_cont;
+
+		case XC( OC_MATCH ):
+			op1 = op->r.n;
+ re_cont:
+			{
+				regex_t *re = as_regex(op1, &sreg);
+				int i = regexec(re, L.s, 0, NULL, 0);
+				if (re == &sreg)
+					regfree(re);
+				setvar_i(res, (i == 0) ^ (opn == '!'));
+			}
+			break;
+
+		case XC( OC_MOVE ):
+			debug_printf_eval("MOVE\n");
+			/* if source is a temporary string, jusk relink it to dest */
+//Disabled: if R.v is numeric but happens to have cached R.v->string,
+//then L.v ends up being a string, which is wrong
+//			if (R.v == v1+1 && R.v->string) {
+//				res = setvar_p(L.v, R.v->string);
+//				R.v->string = NULL;
+//			} else {
+				res = copyvar(L.v, R.v);
+//			}
+			break;
+
+		case XC( OC_TERNARY ):
+			if ((op->r.n->info & OPCLSMASK) != OC_COLON)
+				syntax_error(EMSG_POSSIBLE_ERROR);
+			res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res);
+			break;
+
+		case XC( OC_FUNC ): {
+			var *vbeg, *v;
+			const char *sv_progname;
+
+			if (!op->r.f->body.first)
+				syntax_error(EMSG_UNDEF_FUNC);
+
+			vbeg = v = nvalloc(op->r.f->nargs + 1);
+			while (op1) {
+				var *arg = evaluate(nextarg(&op1), v1);
+				copyvar(v, arg);
+				v->type |= VF_CHILD;
+				v->x.parent = arg;
+				if (++v - vbeg >= op->r.f->nargs)
+					break;
+			}
+
+			v = fnargs;
+			fnargs = vbeg;
+			sv_progname = g_progname;
+
+			res = evaluate(op->r.f->body.first, res);
+
+			g_progname = sv_progname;
+			nvfree(fnargs);
+			fnargs = v;
+
+			break;
+		}
+
+		case XC( OC_GETLINE ):
+		case XC( OC_PGETLINE ): {
+			rstream *rsm;
+			int i;
+
+			if (op1) {
+				rsm = newfile(L.s);
+				if (!rsm->F) {
+					if ((opinfo & OPCLSMASK) == OC_PGETLINE) {
+						rsm->F = popen(L.s, "r");
+						rsm->is_pipe = TRUE;
+					} else {
+						rsm->F = fopen_for_read(L.s);  /* not xfopen! */
+					}
+				}
+			} else {
+				if (!iF)
+					iF = next_input_file();
+				rsm = iF;
+			}
+
+			if (!rsm || !rsm->F) {
+				setvar_i(intvar[ERRNO], errno);
+				setvar_i(res, -1);
+				break;
+			}
+
+			if (!op->r.n)
+				R.v = intvar[F0];
+
+			i = awk_getline(rsm, R.v);
+			if (i > 0 && !op1) {
+				incvar(intvar[FNR]);
+				incvar(intvar[NR]);
+			}
+			setvar_i(res, i);
+			break;
+		}
+
+		/* simple builtins */
+		case XC( OC_FBLTIN ): {
+			double R_d = R_d; /* for compiler */
+
+			switch (opn) {
+			case F_in:
+				R_d = (int)L_d;
+				break;
+
+			case F_rn:
+				R_d = (double)rand() / (double)RAND_MAX;
+				break;
+
+			case F_co:
+				if (ENABLE_FEATURE_AWK_LIBM) {
+					R_d = cos(L_d);
+					break;
+				}
+
+			case F_ex:
+				if (ENABLE_FEATURE_AWK_LIBM) {
+					R_d = exp(L_d);
+					break;
+				}
+
+			case F_lg:
+				if (ENABLE_FEATURE_AWK_LIBM) {
+					R_d = log(L_d);
+					break;
+				}
+
+			case F_si:
+				if (ENABLE_FEATURE_AWK_LIBM) {
+					R_d = sin(L_d);
+					break;
+				}
+
+			case F_sq:
+				if (ENABLE_FEATURE_AWK_LIBM) {
+					R_d = sqrt(L_d);
+					break;
+				}
+
+				syntax_error(EMSG_NO_MATH);
+				break;
+
+			case F_sr:
+				R_d = (double)seed;
+				seed = op1 ? (unsigned)L_d : (unsigned)time(NULL);
+				srand(seed);
+				break;
+
+			case F_ti:
+				R_d = time(NULL);
+				break;
+
+			case F_le:
+				if (!op1)
+					L.s = getvar_s(intvar[F0]);
+				R_d = strlen(L.s);
+				break;
+
+			case F_sy:
+				fflush_all();
+				R_d = (ENABLE_FEATURE_ALLOW_EXEC && L.s && *L.s)
+						? (system(L.s) >> 8) : 0;
+				break;
+
+			case F_ff:
+				if (!op1) {
+					fflush(stdout);
+				} else if (L.s && *L.s) {
+					rstream *rsm = newfile(L.s);
+					fflush(rsm->F);
+				} else {
+					fflush_all();
+				}
+				break;
+
+			case F_cl: {
+				rstream *rsm;
+				int err = 0;
+				rsm = (rstream *)hash_search(fdhash, L.s);
+				debug_printf_eval("OC_FBLTIN F_cl rsm:%p\n", rsm);
+				if (rsm) {
+					debug_printf_eval("OC_FBLTIN F_cl "
+						"rsm->is_pipe:%d, ->F:%p\n",
+						rsm->is_pipe, rsm->F);
+					/* Can be NULL if open failed. Example:
+					 * getline line <"doesnt_exist";
+					 * close("doesnt_exist"); <--- here rsm->F is NULL
+					 */
+					if (rsm->F)
+						err = rsm->is_pipe ? pclose(rsm->F) : fclose(rsm->F);
+					free(rsm->buffer);
+					hash_remove(fdhash, L.s);
+				}
+				if (err)
+					setvar_i(intvar[ERRNO], errno);
+				R_d = (double)err;
+				break;
+			}
+			} /* switch */
+			setvar_i(res, R_d);
+			break;
+		}
+
+		case XC( OC_BUILTIN ):
+			res = exec_builtin(op, res);
+			break;
+
+		case XC( OC_SPRINTF ):
+			setvar_p(res, awk_printf(op1));
+			break;
+
+		case XC( OC_UNARY ): {
+			double Ld, R_d;
+
+			Ld = R_d = getvar_i(R.v);
+			switch (opn) {
+			case 'P':
+				Ld = ++R_d;
+				goto r_op_change;
+			case 'p':
+				R_d++;
+				goto r_op_change;
+			case 'M':
+				Ld = --R_d;
+				goto r_op_change;
+			case 'm':
+				R_d--;
+ r_op_change:
+				setvar_i(R.v, R_d);
+				break;
+			case '!':
+				Ld = !istrue(R.v);
+				break;
+			case '-':
+				Ld = -R_d;
+				break;
+			}
+			setvar_i(res, Ld);
+			break;
+		}
+
+		case XC( OC_FIELD ): {
+			int i = (int)getvar_i(R.v);
+			if (i == 0) {
+				res = intvar[F0];
+			} else {
+				split_f0();
+				if (i > nfields)
+					fsrealloc(i);
+				res = &Fields[i - 1];
+			}
+			break;
+		}
+
+		/* concatenation (" ") and index joining (",") */
+		case XC( OC_CONCAT ):
+		case XC( OC_COMMA ): {
+			const char *sep = "";
+			if ((opinfo & OPCLSMASK) == OC_COMMA)
+				sep = getvar_s(intvar[SUBSEP]);
+			setvar_p(res, xasprintf("%s%s%s", L.s, sep, R.s));
+			break;
+		}
+
+		case XC( OC_LAND ):
+			setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0);
+			break;
+
+		case XC( OC_LOR ):
+			setvar_i(res, istrue(L.v) ? 1 : ptest(op->r.n));
+			break;
+
+		case XC( OC_BINARY ):
+		case XC( OC_REPLACE ): {
+			double R_d = getvar_i(R.v);
+			debug_printf_eval("BINARY/REPLACE: R_d:%f opn:%c\n", R_d, opn);
+			switch (opn) {
+			case '+':
+				L_d += R_d;
+				break;
+			case '-':
+				L_d -= R_d;
+				break;
+			case '*':
+				L_d *= R_d;
+				break;
+			case '/':
+				if (R_d == 0)
+					syntax_error(EMSG_DIV_BY_ZERO);
+				L_d /= R_d;
+				break;
+			case '&':
+				if (ENABLE_FEATURE_AWK_LIBM)
+					L_d = pow(L_d, R_d);
+				else
+					syntax_error(EMSG_NO_MATH);
+				break;
+			case '%':
+				if (R_d == 0)
+					syntax_error(EMSG_DIV_BY_ZERO);
+				L_d -= (int)(L_d / R_d) * R_d;
+				break;
+			}
+			debug_printf_eval("BINARY/REPLACE result:%f\n", L_d);
+			res = setvar_i(((opinfo & OPCLSMASK) == OC_BINARY) ? res : L.v, L_d);
+			break;
+		}
+
+		case XC( OC_COMPARE ): {
+			int i = i; /* for compiler */
+			double Ld;
+
+			if (is_numeric(L.v) && is_numeric(R.v)) {
+				Ld = getvar_i(L.v) - getvar_i(R.v);
+			} else {
+				const char *l = getvar_s(L.v);
+				const char *r = getvar_s(R.v);
+				Ld = icase ? strcasecmp(l, r) : strcmp(l, r);
+			}
+			switch (opn & 0xfe) {
+			case 0:
+				i = (Ld > 0);
+				break;
+			case 2:
+				i = (Ld >= 0);
+				break;
+			case 4:
+				i = (Ld == 0);
+				break;
+			}
+			setvar_i(res, (i == 0) ^ (opn & 1));
+			break;
+		}
+
+		default:
+			syntax_error(EMSG_POSSIBLE_ERROR);
+		}
+		if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS)
+			op = op->a.n;
+		if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS)
+			break;
+		if (nextrec)
+			break;
+	} /* while (op) */
+
+	nvfree(v1);
+	debug_printf_eval("returning from %s(): %p\n", __func__, res);
+	return res;
+#undef fnargs
+#undef seed
+#undef sreg
+}
+
+
+/* -------- main & co. -------- */
+
+static int awk_exit(int r)
+{
+	var tv;
+	unsigned i;
+	hash_item *hi;
+
+	zero_out_var(&tv);
+
+	if (!exiting) {
+		exiting = TRUE;
+		nextrec = FALSE;
+		evaluate(endseq.first, &tv);
+	}
+
+	/* waiting for children */
+	for (i = 0; i < fdhash->csize; i++) {
+		hi = fdhash->items[i];
+		while (hi) {
+			if (hi->data.rs.F && hi->data.rs.is_pipe)
+				pclose(hi->data.rs.F);
+			hi = hi->next;
+		}
+	}
+
+	exit(r);
+}
+
+/* if expr looks like "var=value", perform assignment and return 1,
+ * otherwise return 0 */
+static int is_assignment(const char *expr)
+{
+	char *exprc, *val;
+
+	if (!isalnum_(*expr) || (val = strchr(expr, '=')) == NULL) {
+		return FALSE;
+	}
+
+	exprc = xstrdup(expr);
+	val = exprc + (val - expr);
+	*val++ = '\0';
+
+	unescape_string_in_place(val);
+	setvar_u(newvar(exprc), val);
+	free(exprc);
+	return TRUE;
+}
+
+/* switch to next input file */
+static rstream *next_input_file(void)
+{
+#define rsm          (G.next_input_file__rsm)
+#define files_happen (G.next_input_file__files_happen)
+
+	FILE *F;
+	const char *fname, *ind;
+
+	if (rsm.F)
+		fclose(rsm.F);
+	rsm.F = NULL;
+	rsm.pos = rsm.adv = 0;
+
+	for (;;) {
+		if (getvar_i(intvar[ARGIND])+1 >= getvar_i(intvar[ARGC])) {
+			if (files_happen)
+				return NULL;
+			fname = "-";
+			F = stdin;
+			break;
+		}
+		ind = getvar_s(incvar(intvar[ARGIND]));
+		fname = getvar_s(findvar(iamarray(intvar[ARGV]), ind));
+		if (fname && *fname && !is_assignment(fname)) {
+			F = xfopen_stdin(fname);
+			break;
+		}
+	}
+
+	files_happen = TRUE;
+	setvar_s(intvar[FILENAME], fname);
+	rsm.F = F;
+	return &rsm;
+#undef rsm
+#undef files_happen
+}
+
+int awk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int awk_main(int argc, char **argv)
+{
+	unsigned opt;
+	char *opt_F;
+	llist_t *list_v = NULL;
+	llist_t *list_f = NULL;
+	int i, j;
+	var *v;
+	var tv;
+	char **envp;
+	char *vnames = (char *)vNames; /* cheat */
+	char *vvalues = (char *)vValues;
+
+	INIT_G();
+
+	/* Undo busybox.c, or else strtod may eat ','! This breaks parsing:
+	 * $1,$2 == '$1,' '$2', NOT '$1' ',' '$2' */
+	if (ENABLE_LOCALE_SUPPORT)
+		setlocale(LC_NUMERIC, "C");
+
+	zero_out_var(&tv);
+
+	/* allocate global buffer */
+	g_buf = xmalloc(MAXVARFMT + 1);
+
+	vhash = hash_init();
+	ahash = hash_init();
+	fdhash = hash_init();
+	fnhash = hash_init();
+
+	/* initialize variables */
+	for (i = 0; *vnames; i++) {
+		intvar[i] = v = newvar(nextword(&vnames));
+		if (*vvalues != '\377')
+			setvar_s(v, nextword(&vvalues));
+		else
+			setvar_i(v, 0);
+
+		if (*vnames == '*') {
+			v->type |= VF_SPECIAL;
+			vnames++;
+		}
+	}
+
+	handle_special(intvar[FS]);
+	handle_special(intvar[RS]);
+
+	newfile("/dev/stdin")->F = stdin;
+	newfile("/dev/stdout")->F = stdout;
+	newfile("/dev/stderr")->F = stderr;
+
+	/* Huh, people report that sometimes environ is NULL. Oh well. */
+	if (environ) for (envp = environ; *envp; envp++) {
+		/* environ is writable, thus we don't strdup it needlessly */
+		char *s = *envp;
+		char *s1 = strchr(s, '=');
+		if (s1) {
+			*s1 = '\0';
+			/* Both findvar and setvar_u take const char*
+			 * as 2nd arg -> environment is not trashed */
+			setvar_u(findvar(iamarray(intvar[ENVIRON]), s), s1 + 1);
+			*s1 = '=';
+		}
+	}
+	opt_complementary = "v::f::"; /* -v and -f can occur multiple times */
+	opt = getopt32(argv, "F:v:f:W:", &opt_F, &list_v, &list_f, NULL);
+	argv += optind;
+	argc -= optind;
+	if (opt & 0x1) { /* -F */
+		unescape_string_in_place(opt_F);
+		setvar_s(intvar[FS], opt_F);
+	}
+	while (list_v) { /* -v */
+		if (!is_assignment(llist_pop(&list_v)))
+			bb_show_usage();
+	}
+	if (list_f) { /* -f */
+		do {
+			char *s = NULL;
+			FILE *from_file;
+
+			g_progname = llist_pop(&list_f);
+			from_file = xfopen_stdin(g_progname);
+			/* one byte is reserved for some trick in next_token */
+			for (i = j = 1; j > 0; i += j) {
+				s = xrealloc(s, i + 4096);
+				j = fread(s + i, 1, 4094, from_file);
+			}
+			s[i] = '\0';
+			fclose(from_file);
+			parse_program(s + 1);
+			free(s);
+		} while (list_f);
+		argc++;
+	} else { // no -f: take program from 1st parameter
+		if (!argc)
+			bb_show_usage();
+		g_progname = "cmd. line";
+		parse_program(*argv++);
+	}
+	if (opt & 0x8) // -W
+		bb_error_msg("warning: option -W is ignored");
+
+	/* fill in ARGV array */
+	setvar_i(intvar[ARGC], argc);
+	setari_u(intvar[ARGV], 0, "awk");
+	i = 0;
+	while (*argv)
+		setari_u(intvar[ARGV], ++i, *argv++);
+
+	evaluate(beginseq.first, &tv);
+	if (!mainseq.first && !endseq.first)
+		awk_exit(EXIT_SUCCESS);
+
+	/* input file could already be opened in BEGIN block */
+	if (!iF)
+		iF = next_input_file();
+
+	/* passing through input files */
+	while (iF) {
+		nextfile = FALSE;
+		setvar_i(intvar[FNR], 0);
+
+		while ((i = awk_getline(iF, intvar[F0])) > 0) {
+			nextrec = FALSE;
+			incvar(intvar[NR]);
+			incvar(intvar[FNR]);
+			evaluate(mainseq.first, &tv);
+
+			if (nextfile)
+				break;
+		}
+
+		if (i < 0)
+			syntax_error(strerror(errno));
+
+		iF = next_input_file();
+	}
+
+	awk_exit(EXIT_SUCCESS);
+	/*return 0;*/
+}
diff --git a/ap/app/busybox/src/editors/cmp.c b/ap/app/busybox/src/editors/cmp.c
new file mode 100644
index 0000000..fbe6b97
--- /dev/null
+++ b/ap/app/busybox/src/editors/cmp.c
@@ -0,0 +1,130 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini cmp implementation for busybox
+ *
+ * Copyright (C) 2000,2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+/* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */
+
+//usage:#define cmp_trivial_usage
+//usage:       "[-l] [-s] FILE1 [FILE2" IF_DESKTOP(" [SKIP1 [SKIP2]]") "]"
+//usage:#define cmp_full_usage "\n\n"
+//usage:       "Compare FILE1 with FILE2 (or stdin)\n"
+//usage:     "\n	-l	Write the byte numbers (decimal) and values (octal)"
+//usage:     "\n		for all differing bytes"
+//usage:     "\n	-s	Quiet"
+
+#include "libbb.h"
+
+static const char fmt_eof[] ALIGN1 = "cmp: EOF on %s\n";
+static const char fmt_differ[] ALIGN1 = "%s %s differ: char %"OFF_FMT"u, line %u\n";
+// This fmt_l_opt uses gnu-isms.  SUSv3 would be "%.0s%.0s%"OFF_FMT"u %o %o\n"
+static const char fmt_l_opt[] ALIGN1 = "%.0s%.0s%"OFF_FMT"u %3o %3o\n";
+
+static const char opt_chars[] ALIGN1 = "sl";
+#define CMP_OPT_s (1<<0)
+#define CMP_OPT_l (1<<1)
+
+int cmp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cmp_main(int argc UNUSED_PARAM, char **argv)
+{
+	FILE *fp1, *fp2, *outfile = stdout;
+	const char *filename1, *filename2 = "-";
+	off_t skip1 = 0, skip2 = 0, char_pos = 0;
+	int line_pos = 1; /* Hopefully won't overflow... */
+	const char *fmt;
+	int c1, c2;
+	unsigned opt;
+	int retval = 0;
+
+	opt_complementary = "-1"
+			IF_DESKTOP(":?4")
+			IF_NOT_DESKTOP(":?2")
+			":l--s:s--l";
+	opt = getopt32(argv, opt_chars);
+	argv += optind;
+
+	filename1 = *argv;
+	if (*++argv) {
+		filename2 = *argv;
+		if (ENABLE_DESKTOP && *++argv) {
+			skip1 = XATOOFF(*argv);
+			if (*++argv) {
+				skip2 = XATOOFF(*argv);
+			}
+		}
+	}
+
+	xfunc_error_retval = 2;  /* missing file results in exitcode 2 */
+	if (opt & CMP_OPT_s)
+		logmode = 0;  /* -s suppresses open error messages */
+	fp1 = xfopen_stdin(filename1);
+	fp2 = xfopen_stdin(filename2);
+	if (fp1 == fp2) {		/* Paranoia check... stdin == stdin? */
+		/* Note that we don't bother reading stdin.  Neither does gnu wc.
+		 * But perhaps we should, so that other apps down the chain don't
+		 * get the input.  Consider 'echo hello | (cmp - - && cat -)'.
+		 */
+		return 0;
+	}
+	logmode = LOGMODE_STDIO;
+
+	if (opt & CMP_OPT_l)
+		fmt = fmt_l_opt;
+	else
+		fmt = fmt_differ;
+
+	if (ENABLE_DESKTOP) {
+		while (skip1) { getc(fp1); skip1--; }
+		while (skip2) { getc(fp2); skip2--; }
+	}
+	do {
+		c1 = getc(fp1);
+		c2 = getc(fp2);
+		++char_pos;
+		if (c1 != c2) {			/* Remember: a read error may have occurred. */
+			retval = 1;		/* But assume the files are different for now. */
+			if (c2 == EOF) {
+				/* We know that fp1 isn't at EOF or in an error state.  But to
+				 * save space below, things are setup to expect an EOF in fp1
+				 * if an EOF occurred.  So, swap things around.
+				 */
+				fp1 = fp2;
+				filename1 = filename2;
+				c1 = c2;
+			}
+			if (c1 == EOF) {
+				die_if_ferror(fp1, filename1);
+				fmt = fmt_eof;	/* Well, no error, so it must really be EOF. */
+				outfile = stderr;
+				/* There may have been output to stdout (option -l), so
+				 * make sure we fflush before writing to stderr. */
+				fflush_all();
+			}
+			if (!(opt & CMP_OPT_s)) {
+				if (opt & CMP_OPT_l) {
+					line_pos = c1;	/* line_pos is unused in the -l case. */
+				}
+				fprintf(outfile, fmt, filename1, filename2, char_pos, line_pos, c2);
+				if (opt) {	/* This must be -l since not -s. */
+					/* If we encountered an EOF,
+					 * the while check will catch it. */
+					continue;
+				}
+			}
+			break;
+		}
+		if (c1 == '\n') {
+			++line_pos;
+		}
+	} while (c1 != EOF);
+
+	die_if_ferror(fp1, filename1);
+	die_if_ferror(fp2, filename2);
+
+	fflush_stdout_and_exit(retval);
+}
diff --git a/ap/app/busybox/src/editors/diff.c b/ap/app/busybox/src/editors/diff.c
new file mode 100644
index 0000000..b08ded3
--- /dev/null
+++ b/ap/app/busybox/src/editors/diff.c
@@ -0,0 +1,1025 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini diff implementation for busybox, adapted from OpenBSD diff.
+ *
+ * Copyright (C) 2010 by Matheus Izvekov <mizvekov@gmail.com>
+ * Copyright (C) 2006 by Robert Sullivan <cogito.ergo.cogito@hotmail.com>
+ * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+/*
+ * The following code uses an algorithm due to Harold Stone,
+ * which finds a pair of longest identical subsequences in
+ * the two files.
+ *
+ * The major goal is to generate the match vector J.
+ * J[i] is the index of the line in file1 corresponding
+ * to line i in file0. J[i] = 0 if there is no
+ * such line in file1.
+ *
+ * Lines are hashed so as to work in core. All potential
+ * matches are located by sorting the lines of each file
+ * on the hash (called "value"). In particular, this
+ * collects the equivalence classes in file1 together.
+ * Subroutine equiv replaces the value of each line in
+ * file0 by the index of the first element of its
+ * matching equivalence in (the reordered) file1.
+ * To save space equiv squeezes file1 into a single
+ * array member in which the equivalence classes
+ * are simply concatenated, except that their first
+ * members are flagged by changing sign.
+ *
+ * Next the indices that point into member are unsorted into
+ * array class according to the original order of file0.
+ *
+ * The cleverness lies in routine stone. This marches
+ * through the lines of file0, developing a vector klist
+ * of "k-candidates". At step i a k-candidate is a matched
+ * pair of lines x,y (x in file0, y in file1) such that
+ * there is a common subsequence of length k
+ * between the first i lines of file0 and the first y
+ * lines of file1, but there is no such subsequence for
+ * any smaller y. x is the earliest possible mate to y
+ * that occurs in such a subsequence.
+ *
+ * Whenever any of the members of the equivalence class of
+ * lines in file1 matable to a line in file0 has serial number
+ * less than the y of some k-candidate, that k-candidate
+ * with the smallest such y is replaced. The new
+ * k-candidate is chained (via pred) to the current
+ * k-1 candidate so that the actual subsequence can
+ * be recovered. When a member has serial number greater
+ * that the y of all k-candidates, the klist is extended.
+ * At the end, the longest subsequence is pulled out
+ * and placed in the array J by unravel
+ *
+ * With J in hand, the matches there recorded are
+ * checked against reality to assure that no spurious
+ * matches have crept in due to hashing. If they have,
+ * they are broken, and "jackpot" is recorded--a harmless
+ * matter except that a true match for a spuriously
+ * mated line may now be unnecessarily reported as a change.
+ *
+ * Much of the complexity of the program comes simply
+ * from trying to minimize core utilization and
+ * maximize the range of doable problems by dynamically
+ * allocating what is needed and reusing what is not.
+ * The core requirements for problems larger than somewhat
+ * are (in words) 2*length(file0) + length(file1) +
+ * 3*(number of k-candidates installed), typically about
+ * 6n words for files of length n.
+ */
+
+//usage:#define diff_trivial_usage
+//usage:       "[-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
+//usage:#define diff_full_usage "\n\n"
+//usage:       "Compare files line by line and output the differences between them.\n"
+//usage:       "This implementation supports unified diffs only.\n"
+//usage:     "\n	-a	Treat all files as text"
+//usage:     "\n	-b	Ignore changes in the amount of whitespace"
+//usage:     "\n	-B	Ignore changes whose lines are all blank"
+//usage:     "\n	-d	Try hard to find a smaller set of changes"
+//usage:     "\n	-i	Ignore case differences"
+//usage:     "\n	-L	Use LABEL instead of the filename in the unified header"
+//usage:     "\n	-N	Treat absent files as empty"
+//usage:     "\n	-q	Output only whether files differ"
+//usage:     "\n	-r	Recurse"
+//usage:     "\n	-S	Start with FILE when comparing directories"
+//usage:     "\n	-T	Make tabs line up by prefixing a tab when necessary"
+//usage:     "\n	-s	Report when two files are the same"
+//usage:     "\n	-t	Expand tabs to spaces in output"
+//usage:     "\n	-U	Output LINES lines of context"
+//usage:     "\n	-w	Ignore all whitespace"
+
+#include "libbb.h"
+
+#if 0
+# define dbg_error_msg(...) bb_error_msg(__VA_ARGS__)
+#else
+# define dbg_error_msg(...) ((void)0)
+#endif
+
+enum {                  /* print_status() and diffreg() return values */
+	STATUS_SAME,    /* files are the same */
+	STATUS_DIFFER,  /* files differ */
+	STATUS_BINARY,  /* binary files differ */
+};
+
+enum {                  /* Commandline flags */
+	FLAG_a,
+	FLAG_b,
+	FLAG_d,
+	FLAG_i,
+	FLAG_L,         /* never used, handled by getopt32 */
+	FLAG_N,
+	FLAG_q,
+	FLAG_r,
+	FLAG_s,
+	FLAG_S,         /* never used, handled by getopt32 */
+	FLAG_t,
+	FLAG_T,
+	FLAG_U,         /* never used, handled by getopt32 */
+	FLAG_w,
+	FLAG_u,         /* ignored, this is the default */
+	FLAG_p,         /* not implemented */
+	FLAG_B,
+	FLAG_E,         /* not implemented */
+};
+#define FLAG(x) (1 << FLAG_##x)
+
+/* We cache file position to avoid excessive seeking */
+typedef struct FILE_and_pos_t {
+	FILE *ft_fp;
+	off_t ft_pos;
+} FILE_and_pos_t;
+
+struct globals {
+	smallint exit_status;
+	int opt_U_context;
+	const char *other_dir;
+	char *label[2];
+	struct stat stb[2];
+};
+#define G (*ptr_to_globals)
+#define exit_status        (G.exit_status       )
+#define opt_U_context      (G.opt_U_context     )
+#define label              (G.label             )
+#define stb                (G.stb               )
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+	opt_U_context = 3; \
+} while (0)
+
+typedef int token_t;
+
+enum {
+	/* Public */
+	TOK_EMPTY = 1 << 9,  /* Line fully processed, you can proceed to the next */
+	TOK_EOF   = 1 << 10, /* File ended */
+	/* Private (Only to be used by read_token() */
+	TOK_EOL   = 1 << 11, /* we saw EOL (sticky) */
+	TOK_SPACE = 1 << 12, /* used -b code, means we are skipping spaces */
+	SHIFT_EOF = (sizeof(token_t)*8 - 8) - 1,
+	CHAR_MASK = 0x1ff,   /* 8th bit is used to distinguish EOF from 0xff */
+};
+
+/* Restores full EOF from one 8th bit: */
+//#define TOK2CHAR(t) (((t) << SHIFT_EOF) >> SHIFT_EOF)
+/* We don't really need the above, we only need to have EOF != any_real_char: */
+#define TOK2CHAR(t) ((t) & CHAR_MASK)
+
+static void seek_ft(FILE_and_pos_t *ft, off_t pos)
+{
+	if (ft->ft_pos != pos) {
+		ft->ft_pos = pos;
+		fseeko(ft->ft_fp, pos, SEEK_SET);
+	}
+}
+
+/* Reads tokens from given fp, handling -b and -w flags
+ * The user must reset tok every line start
+ */
+static int read_token(FILE_and_pos_t *ft, token_t tok)
+{
+	tok |= TOK_EMPTY;
+	while (!(tok & TOK_EOL)) {
+		bool is_space;
+		int t;
+
+		t = fgetc(ft->ft_fp);
+		if (t != EOF)
+			ft->ft_pos++;
+		is_space = (t == EOF || isspace(t));
+
+		/* If t == EOF (-1), set both TOK_EOF and TOK_EOL */
+		tok |= (t & (TOK_EOF + TOK_EOL));
+		/* Only EOL? */
+		if (t == '\n')
+			tok |= TOK_EOL;
+
+		if (option_mask32 & FLAG(i)) /* Handcoded tolower() */
+			t = (t >= 'A' && t <= 'Z') ? t - ('A' - 'a') : t;
+
+		if ((option_mask32 & FLAG(w)) && is_space)
+			continue;
+
+		/* Trim char value to low 9 bits */
+		t &= CHAR_MASK;
+
+		if (option_mask32 & FLAG(b)) {
+			/* Was prev char whitespace? */
+			if (tok & TOK_SPACE) { /* yes */
+				if (is_space) /* this one too, ignore it */
+					continue;
+				tok &= ~TOK_SPACE;
+			} else if (is_space) {
+				/* 1st whitespace char.
+				 * Set TOK_SPACE and replace char by ' ' */
+				t = TOK_SPACE + ' ';
+			}
+		}
+		/* Clear EMPTY */
+		tok &= ~(TOK_EMPTY + CHAR_MASK);
+		/* Assign char value (low 9 bits) and maybe set TOK_SPACE */
+		tok |= t;
+		break;
+	}
+#if 0
+	bb_error_msg("fp:%p tok:%x '%c'%s%s%s%s", fp, tok, tok & 0xff
+		, tok & TOK_EOF ? " EOF" : ""
+		, tok & TOK_EOL ? " EOL" : ""
+		, tok & TOK_EMPTY ? " EMPTY" : ""
+		, tok & TOK_SPACE ? " SPACE" : ""
+	);
+#endif
+	return tok;
+}
+
+struct cand {
+	int x;
+	int y;
+	int pred;
+};
+
+static int search(const int *c, int k, int y, const struct cand *list)
+{
+	int i, j;
+
+	if (list[c[k]].y < y)  /* quick look for typical case */
+		return k + 1;
+
+	for (i = 0, j = k + 1;;) {
+		const int l = (i + j) >> 1;
+		if (l > i) {
+			const int t = list[c[l]].y;
+			if (t > y)
+				j = l;
+			else if (t < y)
+				i = l;
+			else
+				return l;
+		} else
+			return l + 1;
+	}
+}
+
+static unsigned isqrt(unsigned n)
+{
+	unsigned x = 1;
+	while (1) {
+		const unsigned y = x;
+		x = ((n / x) + x) >> 1;
+		if (x <= (y + 1) && x >= (y - 1))
+			return x;
+	}
+}
+
+static void stone(const int *a, int n, const int *b, int *J, int pref)
+{
+	const unsigned isq = isqrt(n);
+	const unsigned bound =
+		(option_mask32 & FLAG(d)) ? UINT_MAX : MAX(256, isq);
+	int clen = 1;
+	int clistlen = 100;
+	int k = 0;
+	struct cand *clist = xzalloc(clistlen * sizeof(clist[0]));
+	struct cand cand;
+	struct cand *q;
+	int *klist = xzalloc((n + 2) * sizeof(klist[0]));
+	/*clist[0] = (struct cand){0}; - xzalloc did it */
+	/*klist[0] = 0; */
+
+	for (cand.x = 1; cand.x <= n; cand.x++) {
+		int j = a[cand.x], oldl = 0;
+		unsigned numtries = 0;
+		if (j == 0)
+			continue;
+		cand.y = -b[j];
+		cand.pred = klist[0];
+		do {
+			int l, tc;
+			if (cand.y <= clist[cand.pred].y)
+				continue;
+			l = search(klist, k, cand.y, clist);
+			if (l != oldl + 1)
+				cand.pred = klist[l - 1];
+			if (l <= k && clist[klist[l]].y <= cand.y)
+				continue;
+			if (clen == clistlen) {
+				clistlen = clistlen * 11 / 10;
+				clist = xrealloc(clist, clistlen * sizeof(clist[0]));
+			}
+			clist[clen] = cand;
+			tc = klist[l];
+			klist[l] = clen++;
+			if (l <= k) {
+				cand.pred = tc;
+				oldl = l;
+				numtries++;
+			} else {
+				k++;
+				break;
+			}
+		} while ((cand.y = b[++j]) > 0 && numtries < bound);
+	}
+	/* Unravel */
+	for (q = clist + klist[k]; q->y; q = clist + q->pred)
+		J[q->x + pref] = q->y + pref;
+	free(klist);
+	free(clist);
+}
+
+struct line {
+	/* 'serial' is not used in the begining, so we reuse it
+	 * to store line offsets, thus reducing memory pressure
+	 */
+	union {
+		unsigned serial;
+		off_t offset;
+	};
+	unsigned value;
+};
+
+static void equiv(struct line *a, int n, struct line *b, int m, int *c)
+{
+	int i = 1, j = 1;
+
+	while (i <= n && j <= m) {
+		if (a[i].value < b[j].value)
+			a[i++].value = 0;
+		else if (a[i].value == b[j].value)
+			a[i++].value = j;
+		else
+			j++;
+	}
+	while (i <= n)
+		a[i++].value = 0;
+	b[m + 1].value = 0;
+	j = 0;
+	while (++j <= m) {
+		c[j] = -b[j].serial;
+		while (b[j + 1].value == b[j].value) {
+			j++;
+			c[j] = b[j].serial;
+		}
+	}
+	c[j] = -1;
+}
+
+static void unsort(const struct line *f, int l, int *b)
+{
+	int i;
+	int *a = xmalloc((l + 1) * sizeof(a[0]));
+	for (i = 1; i <= l; i++)
+		a[f[i].serial] = f[i].value;
+	for (i = 1; i <= l; i++)
+		b[i] = a[i];
+	free(a);
+}
+
+static int line_compar(const void *a, const void *b)
+{
+#define l0 ((const struct line*)a)
+#define l1 ((const struct line*)b)
+	int r = l0->value - l1->value;
+	if (r)
+		return r;
+	return l0->serial - l1->serial;
+#undef l0
+#undef l1
+}
+
+static void fetch(FILE_and_pos_t *ft, const off_t *ix, int a, int b, int ch)
+{
+	int i, j, col;
+	for (i = a; i <= b; i++) {
+		seek_ft(ft, ix[i - 1]);
+		putchar(ch);
+		if (option_mask32 & FLAG(T))
+			putchar('\t');
+		for (j = 0, col = 0; j < ix[i] - ix[i - 1]; j++) {
+			int c = fgetc(ft->ft_fp);
+			if (c == EOF) {
+				printf("\n\\ No newline at end of file\n");
+				return;
+			}
+			ft->ft_pos++;
+			if (c == '\t' && (option_mask32 & FLAG(t)))
+				do putchar(' '); while (++col & 7);
+			else {
+				putchar(c);
+				col++;
+			}
+		}
+	}
+}
+
+/* Creates the match vector J, where J[i] is the index
+ * of the line in the new file corresponding to the line i
+ * in the old file. Lines start at 1 instead of 0, that value
+ * being used instead to denote no corresponding line.
+ * This vector is dynamically allocated and must be freed by the caller.
+ *
+ * * fp is an input parameter, where fp[0] and fp[1] are the open
+ *   old file and new file respectively.
+ * * nlen is an output variable, where nlen[0] and nlen[1]
+ *   gets the number of lines in the old and new file respectively.
+ * * ix is an output variable, where ix[0] and ix[1] gets
+ *   assigned dynamically allocated vectors of the offsets of the lines
+ *   of the old and new file respectively. These must be freed by the caller.
+ */
+static NOINLINE int *create_J(FILE_and_pos_t ft[2], int nlen[2], off_t *ix[2])
+{
+	int *J, slen[2], *class, *member;
+	struct line *nfile[2], *sfile[2];
+	int pref = 0, suff = 0, i, j, delta;
+
+	/* Lines of both files are hashed, and in the process
+	 * their offsets are stored in the array ix[fileno]
+	 * where fileno == 0 points to the old file, and
+	 * fileno == 1 points to the new one.
+	 */
+	for (i = 0; i < 2; i++) {
+		unsigned hash;
+		token_t tok;
+		size_t sz = 100;
+		nfile[i] = xmalloc((sz + 3) * sizeof(nfile[i][0]));
+		/* ft gets here without the correct position, cant use seek_ft */
+		ft[i].ft_pos = 0;
+		fseeko(ft[i].ft_fp, 0, SEEK_SET);
+
+		nlen[i] = 0;
+		/* We could zalloc nfile, but then zalloc starts showing in gprof at ~1% */
+		nfile[i][0].offset = 0;
+		goto start; /* saves code */
+		while (1) {
+			tok = read_token(&ft[i], tok);
+			if (!(tok & TOK_EMPTY)) {
+				/* Hash algorithm taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578. */
+				/*hash = hash * 128 - hash + TOK2CHAR(tok);
+				 * gcc insists on optimizing above to "hash * 127 + ...", thus... */
+				unsigned o = hash - TOK2CHAR(tok);
+				hash = hash * 128 - o; /* we want SPEED here */
+				continue;
+			}
+			if (nlen[i]++ == sz) {
+				sz = sz * 3 / 2;
+				nfile[i] = xrealloc(nfile[i], (sz + 3) * sizeof(nfile[i][0]));
+			}
+			/* line_compar needs hashes fit into positive int */
+			nfile[i][nlen[i]].value = hash & INT_MAX;
+			/* like ftello(ft[i].ft_fp) but faster (avoids lseek syscall) */
+			nfile[i][nlen[i]].offset = ft[i].ft_pos;
+			if (tok & TOK_EOF) {
+				/* EOF counts as a token, so we have to adjust it here */
+				nfile[i][nlen[i]].offset++;
+				break;
+			}
+start:
+			hash = tok = 0;
+		}
+		/* Exclude lone EOF line from the end of the file, to make fetch()'s job easier */
+		if (nfile[i][nlen[i]].offset - nfile[i][nlen[i] - 1].offset == 1)
+			nlen[i]--;
+		/* Now we copy the line offsets into ix */
+		ix[i] = xmalloc((nlen[i] + 2) * sizeof(ix[i][0]));
+		for (j = 0; j < nlen[i] + 1; j++)
+			ix[i][j] = nfile[i][j].offset;
+	}
+
+	/* length of prefix and suffix is calculated */
+	for (; pref < nlen[0] && pref < nlen[1] &&
+	       nfile[0][pref + 1].value == nfile[1][pref + 1].value;
+	       pref++);
+	for (; suff < nlen[0] - pref && suff < nlen[1] - pref &&
+	       nfile[0][nlen[0] - suff].value == nfile[1][nlen[1] - suff].value;
+	       suff++);
+	/* Arrays are pruned by the suffix and prefix length,
+	 * the result being sorted and stored in sfile[fileno],
+	 * and their sizes are stored in slen[fileno]
+	 */
+	for (j = 0; j < 2; j++) {
+		sfile[j] = nfile[j] + pref;
+		slen[j] = nlen[j] - pref - suff;
+		for (i = 0; i <= slen[j]; i++)
+			sfile[j][i].serial = i;
+		qsort(sfile[j] + 1, slen[j], sizeof(*sfile[j]), line_compar);
+	}
+	/* nfile arrays are reused to reduce memory pressure
+	 * The #if zeroed out section performs the same task as the
+	 * one in the #else section.
+	 * Peak memory usage is higher, but one array copy is avoided
+	 * by not using unsort()
+	 */
+#if 0
+	member = xmalloc((slen[1] + 2) * sizeof(member[0]));
+	equiv(sfile[0], slen[0], sfile[1], slen[1], member);
+	free(nfile[1]);
+
+	class = xmalloc((slen[0] + 1) * sizeof(class[0]));
+	for (i = 1; i <= slen[0]; i++) /* Unsorting */
+		class[sfile[0][i].serial] = sfile[0][i].value;
+	free(nfile[0]);
+#else
+	member = (int *)nfile[1];
+	equiv(sfile[0], slen[0], sfile[1], slen[1], member);
+	member = xrealloc(member, (slen[1] + 2) * sizeof(member[0]));
+
+	class = (int *)nfile[0];
+	unsort(sfile[0], slen[0], (int *)nfile[0]);
+	class = xrealloc(class, (slen[0] + 2) * sizeof(class[0]));
+#endif
+	J = xmalloc((nlen[0] + 2) * sizeof(J[0]));
+	/* The elements of J which fall inside the prefix and suffix regions
+	 * are marked as unchanged, while the ones which fall outside
+	 * are initialized with 0 (no matches), so that function stone can
+	 * then assign them their right values
+	 */
+	for (i = 0, delta = nlen[1] - nlen[0]; i <= nlen[0]; i++)
+		J[i] = i <= pref            ?  i :
+		       i > (nlen[0] - suff) ? (i + delta) : 0;
+	/* Here the magic is performed */
+	stone(class, slen[0], member, J, pref);
+	J[nlen[0] + 1] = nlen[1] + 1;
+
+	free(class);
+	free(member);
+
+	/* Both files are rescanned, in an effort to find any lines
+	 * which, due to limitations intrinsic to any hashing algorithm,
+	 * are different but ended up confounded as the same
+	 */
+	for (i = 1; i <= nlen[0]; i++) {
+		if (!J[i])
+			continue;
+
+		seek_ft(&ft[0], ix[0][i - 1]);
+		seek_ft(&ft[1], ix[1][J[i] - 1]);
+
+		for (j = J[i]; i <= nlen[0] && J[i] == j; i++, j++) {
+			token_t tok0 = 0, tok1 = 0;
+			do {
+				tok0 = read_token(&ft[0], tok0);
+				tok1 = read_token(&ft[1], tok1);
+
+				if (((tok0 ^ tok1) & TOK_EMPTY) != 0 /* one is empty (not both) */
+				 || (!(tok0 & TOK_EMPTY) && TOK2CHAR(tok0) != TOK2CHAR(tok1))
+				) {
+					J[i] = 0; /* Break the correspondence */
+				}
+			} while (!(tok0 & tok1 & TOK_EMPTY));
+		}
+	}
+
+	return J;
+}
+
+static bool diff(FILE* fp[2], char *file[2])
+{
+	int nlen[2];
+	off_t *ix[2];
+	FILE_and_pos_t ft[2];
+	typedef struct { int a, b; } vec_t[2];
+	vec_t *vec = NULL;
+	int i = 1, j, k, idx = -1;
+	bool anychange = false;
+	int *J;
+
+	ft[0].ft_fp = fp[0];
+	ft[1].ft_fp = fp[1];
+	/* note that ft[i].ft_pos is unintitalized, create_J()
+	 * must not assume otherwise */
+	J = create_J(ft, nlen, ix);
+
+	do {
+		bool nonempty = false;
+
+		while (1) {
+			vec_t v;
+
+			for (v[0].a = i; v[0].a <= nlen[0] && J[v[0].a] == J[v[0].a - 1] + 1; v[0].a++)
+				continue;
+			v[1].a = J[v[0].a - 1] + 1;
+
+			for (v[0].b = v[0].a - 1; v[0].b < nlen[0] && !J[v[0].b + 1]; v[0].b++)
+				continue;
+			v[1].b = J[v[0].b + 1] - 1;
+			/*
+			 * Indicate that there is a difference between lines a and b of the 'from' file
+			 * to get to lines c to d of the 'to' file. If a is greater than b then there
+			 * are no lines in the 'from' file involved and this means that there were
+			 * lines appended (beginning at b).  If c is greater than d then there are
+			 * lines missing from the 'to' file.
+			 */
+			if (v[0].a <= v[0].b || v[1].a <= v[1].b) {
+				/*
+				 * If this change is more than 'context' lines from the
+				 * previous change, dump the record and reset it.
+				 */
+				int ct = (2 * opt_U_context) + 1;
+				if (idx >= 0
+				 && v[0].a > vec[idx][0].b + ct
+				 && v[1].a > vec[idx][1].b + ct
+				) {
+					break;
+				}
+
+				for (j = 0; j < 2; j++)
+					for (k = v[j].a; k < v[j].b; k++)
+						nonempty |= (ix[j][k+1] - ix[j][k] != 1);
+
+				vec = xrealloc_vector(vec, 6, ++idx);
+				memcpy(vec[idx], v, sizeof(v));
+			}
+
+			i = v[0].b + 1;
+			if (i > nlen[0])
+				break;
+			J[v[0].b] = v[1].b;
+		}
+		if (idx < 0 || ((option_mask32 & FLAG(B)) && !nonempty))
+			goto cont;
+		if (!(option_mask32 & FLAG(q))) {
+			int lowa;
+			vec_t span, *cvp = vec;
+
+			if (!anychange) {
+				/* Print the context/unidiff header first time through */
+				printf("--- %s\n", label[0] ? label[0] : file[0]);
+				printf("+++ %s\n", label[1] ? label[1] : file[1]);
+			}
+
+			printf("@@");
+			for (j = 0; j < 2; j++) {
+				int a = span[j].a = MAX(1, (*cvp)[j].a - opt_U_context);
+				int b = span[j].b = MIN(nlen[j], vec[idx][j].b + opt_U_context);
+
+				printf(" %c%d", j ? '+' : '-', MIN(a, b));
+				if (a == b)
+					continue;
+				printf(",%d", (a < b) ? b - a + 1 : 0);
+			}
+			printf(" @@\n");
+			/*
+			 * Output changes in "unified" diff format--the old and new lines
+			 * are printed together.
+			 */
+			for (lowa = span[0].a; ; lowa = (*cvp++)[0].b + 1) {
+				bool end = cvp > &vec[idx];
+				fetch(&ft[0], ix[0], lowa, end ? span[0].b : (*cvp)[0].a - 1, ' ');
+				if (end)
+					break;
+				for (j = 0; j < 2; j++)
+					fetch(&ft[j], ix[j], (*cvp)[j].a, (*cvp)[j].b, j ? '+' : '-');
+			}
+		}
+		anychange = true;
+ cont:
+		idx = -1;
+	} while (i <= nlen[0]);
+
+	free(vec);
+	free(ix[0]);
+	free(ix[1]);
+	free(J);
+	return anychange;
+}
+
+static int diffreg(char *file[2])
+{
+	FILE *fp[2];
+	bool binary = false, differ = false;
+	int status = STATUS_SAME, i;
+
+	fp[0] = stdin;
+	fp[1] = stdin;
+	for (i = 0; i < 2; i++) {
+		int fd = open_or_warn_stdin(file[i]);
+		if (fd == -1)
+			goto out;
+		/* Our diff implementation is using seek.
+		 * When we meet non-seekable file, we must make a temp copy.
+		 */
+		if (lseek(fd, 0, SEEK_SET) == -1 && errno == ESPIPE) {
+			char name[] = "/tmp/difXXXXXX";
+			int fd_tmp = xmkstemp(name);
+
+			unlink(name);
+			if (bb_copyfd_eof(fd, fd_tmp) < 0)
+				xfunc_die();
+			if (fd) /* Prevents closing of stdin */
+				close(fd);
+			fd = fd_tmp;
+		}
+		fp[i] = fdopen(fd, "r");
+	}
+
+	while (1) {
+		const size_t sz = COMMON_BUFSIZE / 2;
+		char *const buf0 = bb_common_bufsiz1;
+		char *const buf1 = buf0 + sz;
+		int j, k;
+		i = fread(buf0, 1, sz, fp[0]);
+		j = fread(buf1, 1, sz, fp[1]);
+		if (i != j) {
+			differ = true;
+			i = MIN(i, j);
+		}
+		if (i == 0)
+			break;
+		for (k = 0; k < i; k++) {
+			if (!buf0[k] || !buf1[k])
+				binary = true;
+			if (buf0[k] != buf1[k])
+				differ = true;
+		}
+	}
+	if (differ) {
+		if (binary && !(option_mask32 & FLAG(a)))
+			status = STATUS_BINARY;
+		else if (diff(fp, file))
+			status = STATUS_DIFFER;
+	}
+	if (status != STATUS_SAME)
+		exit_status |= 1;
+out:
+	fclose_if_not_stdin(fp[0]);
+	fclose_if_not_stdin(fp[1]);
+
+	return status;
+}
+
+static void print_status(int status, char *path[2])
+{
+	switch (status) {
+	case STATUS_BINARY:
+	case STATUS_DIFFER:
+		if ((option_mask32 & FLAG(q)) || status == STATUS_BINARY)
+			printf("Files %s and %s differ\n", path[0], path[1]);
+		break;
+	case STATUS_SAME:
+		if (option_mask32 & FLAG(s))
+			printf("Files %s and %s are identical\n", path[0], path[1]);
+		break;
+	}
+}
+
+#if ENABLE_FEATURE_DIFF_DIR
+struct dlist {
+	size_t len;
+	int s, e;
+	char **dl;
+};
+
+/* This function adds a filename to dl, the directory listing. */
+static int FAST_FUNC add_to_dirlist(const char *filename,
+		struct stat *sb UNUSED_PARAM,
+		void *userdata, int depth UNUSED_PARAM)
+{
+	struct dlist *const l = userdata;
+	const char *file = filename + l->len;
+	while (*file == '/')
+		file++;
+	l->dl = xrealloc_vector(l->dl, 6, l->e);
+	l->dl[l->e] = xstrdup(file);
+	l->e++;
+	return TRUE;
+}
+
+/* If recursion is not set, this function adds the directory
+ * to the list and prevents recursive_action from recursing into it.
+ */
+static int FAST_FUNC skip_dir(const char *filename,
+		struct stat *sb, void *userdata,
+		int depth)
+{
+	if (!(option_mask32 & FLAG(r)) && depth) {
+		add_to_dirlist(filename, sb, userdata, depth);
+		return SKIP;
+	}
+	if (!(option_mask32 & FLAG(N))) {
+		/* -r without -N: no need to recurse into dirs
+		 * which do not exist on the "other side".
+		 * Testcase: diff -r /tmp /
+		 * (it would recurse deep into /proc without this code) */
+		struct dlist *const l = userdata;
+		filename += l->len;
+		if (filename[0]) {
+			struct stat osb;
+			char *othername = concat_path_file(G.other_dir, filename);
+			int r = stat(othername, &osb);
+			free(othername);
+			if (r != 0 || !S_ISDIR(osb.st_mode)) {
+				/* other dir doesn't have similarly named
+				 * directory, don't recurse; return 1 upon
+				 * exit, just like diffutils' diff */
+				exit_status |= 1;
+				return SKIP;
+			}
+		}
+	}
+	return TRUE;
+}
+
+static void diffdir(char *p[2], const char *s_start)
+{
+	struct dlist list[2];
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	for (i = 0; i < 2; i++) {
+		/*list[i].s = list[i].e = 0; - memset did it */
+		/*list[i].dl = NULL; */
+
+		G.other_dir = p[1 - i];
+		/* We need to trim root directory prefix.
+		 * Using list.len to specify its length,
+		 * add_to_dirlist will remove it. */
+		list[i].len = strlen(p[i]);
+		recursive_action(p[i], ACTION_RECURSE | ACTION_FOLLOWLINKS,
+				add_to_dirlist, skip_dir, &list[i], 0);
+		/* Sort dl alphabetically.
+		 * GNU diff does this ignoring any number of trailing dots.
+		 * We don't, so for us dotted files almost always are
+		 * first on the list.
+		 */
+		qsort_string_vector(list[i].dl, list[i].e);
+		/* If -S was set, find the starting point. */
+		if (!s_start)
+			continue;
+		while (list[i].s < list[i].e && strcmp(list[i].dl[list[i].s], s_start) < 0)
+			list[i].s++;
+	}
+	/* Now that both dirlist1 and dirlist2 contain sorted directory
+	 * listings, we can start to go through dirlist1. If both listings
+	 * contain the same file, then do a normal diff. Otherwise, behaviour
+	 * is determined by whether the -N flag is set. */
+	while (1) {
+		char *dp[2];
+		int pos;
+		int k;
+
+		dp[0] = list[0].s < list[0].e ? list[0].dl[list[0].s] : NULL;
+		dp[1] = list[1].s < list[1].e ? list[1].dl[list[1].s] : NULL;
+		if (!dp[0] && !dp[1])
+			break;
+		pos = !dp[0] ? 1 : (!dp[1] ? -1 : strcmp(dp[0], dp[1]));
+		k = pos > 0;
+		if (pos && !(option_mask32 & FLAG(N))) {
+			printf("Only in %s: %s\n", p[k], dp[k]);
+			exit_status |= 1;
+		} else {
+			char *fullpath[2], *path[2]; /* if -N */
+
+			for (i = 0; i < 2; i++) {
+				if (pos == 0 || i == k) {
+					path[i] = fullpath[i] = concat_path_file(p[i], dp[i]);
+					stat(fullpath[i], &stb[i]);
+				} else {
+					fullpath[i] = concat_path_file(p[i], dp[1 - i]);
+					path[i] = (char *)bb_dev_null;
+				}
+			}
+			if (pos)
+				stat(fullpath[k], &stb[1 - k]);
+
+			if (S_ISDIR(stb[0].st_mode) && S_ISDIR(stb[1].st_mode))
+				printf("Common subdirectories: %s and %s\n", fullpath[0], fullpath[1]);
+			else if (!S_ISREG(stb[0].st_mode) && !S_ISDIR(stb[0].st_mode))
+				printf("File %s is not a regular file or directory and was skipped\n", fullpath[0]);
+			else if (!S_ISREG(stb[1].st_mode) && !S_ISDIR(stb[1].st_mode))
+				printf("File %s is not a regular file or directory and was skipped\n", fullpath[1]);
+			else if (S_ISDIR(stb[0].st_mode) != S_ISDIR(stb[1].st_mode)) {
+				if (S_ISDIR(stb[0].st_mode))
+					printf("File %s is a %s while file %s is a %s\n", fullpath[0], "directory", fullpath[1], "regular file");
+				else
+					printf("File %s is a %s while file %s is a %s\n", fullpath[0], "regular file", fullpath[1], "directory");
+			} else
+				print_status(diffreg(path), fullpath);
+
+			free(fullpath[0]);
+			free(fullpath[1]);
+		}
+		free(dp[k]);
+		list[k].s++;
+		if (pos == 0) {
+			free(dp[1 - k]);
+			list[1 - k].s++;
+		}
+	}
+	if (ENABLE_FEATURE_CLEAN_UP) {
+		free(list[0].dl);
+		free(list[1].dl);
+	}
+}
+#endif
+
+#if ENABLE_FEATURE_DIFF_LONG_OPTIONS
+static const char diff_longopts[] ALIGN1 =
+	"ignore-case\0"              No_argument       "i"
+	"ignore-tab-expansion\0"     No_argument       "E"
+	"ignore-space-change\0"      No_argument       "b"
+	"ignore-all-space\0"         No_argument       "w"
+	"ignore-blank-lines\0"       No_argument       "B"
+	"text\0"                     No_argument       "a"
+	"unified\0"                  Required_argument "U"
+	"label\0"                    Required_argument "L"
+	"show-c-function\0"          No_argument       "p"
+	"brief\0"                    No_argument       "q"
+	"expand-tabs\0"              No_argument       "t"
+	"initial-tab\0"              No_argument       "T"
+	"recursive\0"                No_argument       "r"
+	"new-file\0"                 No_argument       "N"
+	"report-identical-files\0"   No_argument       "s"
+	"starting-file\0"            Required_argument "S"
+	"minimal\0"                  No_argument       "d"
+	;
+#endif
+
+int diff_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int diff_main(int argc UNUSED_PARAM, char **argv)
+{
+	int gotstdin = 0, i;
+	char *file[2], *s_start = NULL;
+	llist_t *L_arg = NULL;
+
+	INIT_G();
+
+	/* exactly 2 params; collect multiple -L <label>; -U N */
+	opt_complementary = "=2:L::U+";
+#if ENABLE_FEATURE_DIFF_LONG_OPTIONS
+	applet_long_options = diff_longopts;
+#endif
+	getopt32(argv, "abdiL:NqrsS:tTU:wupBE",
+			&L_arg, &s_start, &opt_U_context);
+	argv += optind;
+	while (L_arg)
+		label[!!label[0]] = llist_pop(&L_arg);
+	xfunc_error_retval = 2;
+	for (i = 0; i < 2; i++) {
+		file[i] = argv[i];
+		/* Compat: "diff file name_which_doesnt_exist" exits with 2 */
+		if (LONE_DASH(file[i])) {
+			fstat(STDIN_FILENO, &stb[i]);
+			gotstdin++;
+		} else
+			xstat(file[i], &stb[i]);
+	}
+	xfunc_error_retval = 1;
+	if (gotstdin && (S_ISDIR(stb[0].st_mode) || S_ISDIR(stb[1].st_mode)))
+		bb_error_msg_and_die("can't compare stdin to a directory");
+
+	/* Compare metadata to check if the files are the same physical file.
+	 *
+	 * Comment from diffutils source says:
+	 * POSIX says that two files are identical if st_ino and st_dev are
+	 * the same, but many file systems incorrectly assign the same (device,
+	 * inode) pair to two distinct files, including:
+	 * GNU/Linux NFS servers that export all local file systems as a
+	 * single NFS file system, if a local device number (st_dev) exceeds
+	 * 255, or if a local inode number (st_ino) exceeds 16777215.
+	 */
+	if (ENABLE_DESKTOP
+	 && stb[0].st_ino == stb[1].st_ino
+	 && stb[0].st_dev == stb[1].st_dev
+	 && stb[0].st_size == stb[1].st_size
+	 && stb[0].st_mtime == stb[1].st_mtime
+	 && stb[0].st_ctime == stb[1].st_ctime
+	 && stb[0].st_mode == stb[1].st_mode
+	 && stb[0].st_nlink == stb[1].st_nlink
+	 && stb[0].st_uid == stb[1].st_uid
+	 && stb[0].st_gid == stb[1].st_gid
+	) {
+		/* files are physically the same; no need to compare them */
+		return STATUS_SAME;
+	}
+
+	if (S_ISDIR(stb[0].st_mode) && S_ISDIR(stb[1].st_mode)) {
+#if ENABLE_FEATURE_DIFF_DIR
+		diffdir(file, s_start);
+#else
+		bb_error_msg_and_die("no support for directory comparison");
+#endif
+	} else {
+		bool dirfile = S_ISDIR(stb[0].st_mode) || S_ISDIR(stb[1].st_mode);
+		bool dir = S_ISDIR(stb[1].st_mode);
+		if (dirfile) {
+			const char *slash = strrchr(file[!dir], '/');
+			file[dir] = concat_path_file(file[dir], slash ? slash + 1 : file[!dir]);
+			xstat(file[dir], &stb[dir]);
+		}
+		/* diffreg can get non-regular files here */
+		print_status(gotstdin > 1 ? STATUS_SAME : diffreg(file), file);
+
+		if (dirfile)
+			free(file[dir]);
+	}
+
+	return exit_status;
+}
diff --git a/ap/app/busybox/src/editors/ed.c b/ap/app/busybox/src/editors/ed.c
new file mode 100644
index 0000000..dbb5130
--- /dev/null
+++ b/ap/app/busybox/src/editors/ed.c
@@ -0,0 +1,1039 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (c) 2002 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "ed" built-in command (much simplified)
+ */
+
+//usage:#define ed_trivial_usage ""
+//usage:#define ed_full_usage ""
+
+#include "libbb.h"
+
+typedef struct LINE {
+	struct LINE *next;
+	struct LINE *prev;
+	int len;
+	char data[1];
+} LINE;
+
+
+#define searchString bb_common_bufsiz1
+
+enum {
+	USERSIZE = sizeof(searchString) > 1024 ? 1024
+	         : sizeof(searchString) - 1, /* max line length typed in by user */
+	INITBUF_SIZE = 1024, /* initial buffer size */
+};
+
+struct globals {
+	int curNum;
+	int lastNum;
+	int bufUsed;
+	int bufSize;
+	LINE *curLine;
+	char *bufBase;
+	char *bufPtr;
+	char *fileName;
+	LINE lines;
+	smallint dirty;
+	int marks[26];
+};
+#define G (*ptr_to_globals)
+#define curLine            (G.curLine           )
+#define bufBase            (G.bufBase           )
+#define bufPtr             (G.bufPtr            )
+#define fileName           (G.fileName          )
+#define curNum             (G.curNum            )
+#define lastNum            (G.lastNum           )
+#define bufUsed            (G.bufUsed           )
+#define bufSize            (G.bufSize           )
+#define dirty              (G.dirty             )
+#define lines              (G.lines             )
+#define marks              (G.marks             )
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+static void doCommands(void);
+static void subCommand(const char *cmd, int num1, int num2);
+static int getNum(const char **retcp, smallint *retHaveNum, int *retNum);
+static int setCurNum(int num);
+static void addLines(int num);
+static int insertLine(int num, const char *data, int len);
+static void deleteLines(int num1, int num2);
+static int printLines(int num1, int num2, int expandFlag);
+static int writeLines(const char *file, int num1, int num2);
+static int readLines(const char *file, int num);
+static int searchLines(const char *str, int num1, int num2);
+static LINE *findLine(int num);
+static int findString(const LINE *lp, const char * str, int len, int offset);
+
+
+static int bad_nums(int num1, int num2, const char *for_what)
+{
+	if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
+		bb_error_msg("bad line range for %s", for_what);
+		return 1;
+	}
+	return 0;
+}
+
+
+static char *skip_blank(const char *cp)
+{
+	while (isblank(*cp))
+		cp++;
+	return (char *)cp;
+}
+
+
+int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ed_main(int argc UNUSED_PARAM, char **argv)
+{
+	INIT_G();
+
+	bufSize = INITBUF_SIZE;
+	bufBase = xmalloc(bufSize);
+	bufPtr = bufBase;
+	lines.next = &lines;
+	lines.prev = &lines;
+
+	if (argv[1]) {
+		fileName = xstrdup(argv[1]);
+		if (!readLines(fileName, 1)) {
+			return EXIT_SUCCESS;
+		}
+		if (lastNum)
+			setCurNum(1);
+		dirty = FALSE;
+	}
+
+	doCommands();
+	return EXIT_SUCCESS;
+}
+
+/*
+ * Read commands until we are told to stop.
+ */
+static void doCommands(void)
+{
+	const char *cp;
+	char *endbuf, buf[USERSIZE];
+	int len, num1, num2;
+	smallint have1, have2;
+
+	while (TRUE) {
+		/* Returns:
+		 * -1 on read errors or EOF, or on bare Ctrl-D.
+		 * 0  on ctrl-C,
+		 * >0 length of input string, including terminating '\n'
+		 */
+		len = read_line_input(NULL, ": ", buf, sizeof(buf), /*timeout*/ -1);
+		if (len <= 0)
+			return;
+		endbuf = &buf[len - 1];
+		while ((endbuf > buf) && isblank(endbuf[-1]))
+			endbuf--;
+		*endbuf = '\0';
+
+		cp = skip_blank(buf);
+		have1 = FALSE;
+		have2 = FALSE;
+
+		if ((curNum == 0) && (lastNum > 0)) {
+			curNum = 1;
+			curLine = lines.next;
+		}
+
+		if (!getNum(&cp, &have1, &num1))
+			continue;
+
+		cp = skip_blank(cp);
+
+		if (*cp == ',') {
+			cp++;
+			if (!getNum(&cp, &have2, &num2))
+				continue;
+			if (!have1)
+				num1 = 1;
+			if (!have2)
+				num2 = lastNum;
+			have1 = TRUE;
+			have2 = TRUE;
+		}
+		if (!have1)
+			num1 = curNum;
+		if (!have2)
+			num2 = num1;
+
+		switch (*cp++) {
+		case 'a':
+			addLines(num1 + 1);
+			break;
+
+		case 'c':
+			deleteLines(num1, num2);
+			addLines(num1);
+			break;
+
+		case 'd':
+			deleteLines(num1, num2);
+			break;
+
+		case 'f':
+			if (*cp && !isblank(*cp)) {
+				bb_error_msg("bad file command");
+				break;
+			}
+			cp = skip_blank(cp);
+			if (*cp == '\0') {
+				if (fileName)
+					printf("\"%s\"\n", fileName);
+				else
+					printf("No file name\n");
+				break;
+			}
+			free(fileName);
+			fileName = xstrdup(cp);
+			break;
+
+		case 'i':
+			addLines(num1);
+			break;
+
+		case 'k':
+			cp = skip_blank(cp);
+			if ((*cp < 'a') || (*cp > 'z') || cp[1]) {
+				bb_error_msg("bad mark name");
+				break;
+			}
+			marks[*cp - 'a'] = num2;
+			break;
+
+		case 'l':
+			printLines(num1, num2, TRUE);
+			break;
+
+		case 'p':
+			printLines(num1, num2, FALSE);
+			break;
+
+		case 'q':
+			cp = skip_blank(cp);
+			if (have1 || *cp) {
+				bb_error_msg("bad quit command");
+				break;
+			}
+			if (!dirty)
+				return;
+			len = read_line_input(NULL, "Really quit? ", buf, 16, /*timeout*/ -1);
+			/* read error/EOF - no way to continue */
+			if (len < 0)
+				return;
+			cp = skip_blank(buf);
+			if ((*cp | 0x20) == 'y') /* Y or y */
+				return;
+			break;
+
+		case 'r':
+			if (*cp && !isblank(*cp)) {
+				bb_error_msg("bad read command");
+				break;
+			}
+			cp = skip_blank(cp);
+			if (*cp == '\0') {
+				bb_error_msg("no file name");
+				break;
+			}
+			if (!have1)
+				num1 = lastNum;
+			if (readLines(cp, num1 + 1))
+				break;
+			if (fileName == NULL)
+				fileName = xstrdup(cp);
+			break;
+
+		case 's':
+			subCommand(cp, num1, num2);
+			break;
+
+		case 'w':
+			if (*cp && !isblank(*cp)) {
+				bb_error_msg("bad write command");
+				break;
+			}
+			cp = skip_blank(cp);
+			if (!have1) {
+				num1 = 1;
+				num2 = lastNum;
+			}
+			if (*cp == '\0')
+				cp = fileName;
+			if (cp == NULL) {
+				bb_error_msg("no file name specified");
+				break;
+			}
+			writeLines(cp, num1, num2);
+			break;
+
+		case 'z':
+			switch (*cp) {
+			case '-':
+				printLines(curNum - 21, curNum, FALSE);
+				break;
+			case '.':
+				printLines(curNum - 11, curNum + 10, FALSE);
+				break;
+			default:
+				printLines(curNum, curNum + 21, FALSE);
+				break;
+			}
+			break;
+
+		case '.':
+			if (have1) {
+				bb_error_msg("no arguments allowed");
+				break;
+			}
+			printLines(curNum, curNum, FALSE);
+			break;
+
+		case '-':
+			if (setCurNum(curNum - 1))
+				printLines(curNum, curNum, FALSE);
+			break;
+
+		case '=':
+			printf("%d\n", num1);
+			break;
+		case '\0':
+			if (have1) {
+				printLines(num2, num2, FALSE);
+				break;
+			}
+			if (setCurNum(curNum + 1))
+				printLines(curNum, curNum, FALSE);
+			break;
+
+		default:
+			bb_error_msg("unimplemented command");
+			break;
+		}
+	}
+}
+
+
+/*
+ * Do the substitute command.
+ * The current line is set to the last substitution done.
+ */
+static void subCommand(const char *cmd, int num1, int num2)
+{
+	char *cp, *oldStr, *newStr, buf[USERSIZE];
+	int delim, oldLen, newLen, deltaLen, offset;
+	LINE *lp, *nlp;
+	int globalFlag, printFlag, didSub, needPrint;
+
+	if (bad_nums(num1, num2, "substitute"))
+		return;
+
+	globalFlag = FALSE;
+	printFlag = FALSE;
+	didSub = FALSE;
+	needPrint = FALSE;
+
+	/*
+	 * Copy the command so we can modify it.
+	 */
+	strcpy(buf, cmd);
+	cp = buf;
+
+	if (isblank(*cp) || (*cp == '\0')) {
+		bb_error_msg("bad delimiter for substitute");
+		return;
+	}
+
+	delim = *cp++;
+	oldStr = cp;
+
+	cp = strchr(cp, delim);
+	if (cp == NULL) {
+		bb_error_msg("missing 2nd delimiter for substitute");
+		return;
+	}
+
+	*cp++ = '\0';
+
+	newStr = cp;
+	cp = strchr(cp, delim);
+
+	if (cp)
+		*cp++ = '\0';
+	else
+		cp = (char*)"";
+
+	while (*cp) switch (*cp++) {
+		case 'g':
+			globalFlag = TRUE;
+			break;
+		case 'p':
+			printFlag = TRUE;
+			break;
+		default:
+			bb_error_msg("unknown option for substitute");
+			return;
+	}
+
+	if (*oldStr == '\0') {
+		if (searchString[0] == '\0') {
+			bb_error_msg("no previous search string");
+			return;
+		}
+		oldStr = searchString;
+	}
+
+	if (oldStr != searchString)
+		strcpy(searchString, oldStr);
+
+	lp = findLine(num1);
+	if (lp == NULL)
+		return;
+
+	oldLen = strlen(oldStr);
+	newLen = strlen(newStr);
+	deltaLen = newLen - oldLen;
+	offset = 0;
+	nlp = NULL;
+
+	while (num1 <= num2) {
+		offset = findString(lp, oldStr, oldLen, offset);
+
+		if (offset < 0) {
+			if (needPrint) {
+				printLines(num1, num1, FALSE);
+				needPrint = FALSE;
+			}
+			offset = 0;
+			lp = lp->next;
+			num1++;
+			continue;
+		}
+
+		needPrint = printFlag;
+		didSub = TRUE;
+		dirty = TRUE;
+
+		/*
+		 * If the replacement string is the same size or shorter
+		 * than the old string, then the substitution is easy.
+		 */
+		if (deltaLen <= 0) {
+			memcpy(&lp->data[offset], newStr, newLen);
+			if (deltaLen) {
+				memcpy(&lp->data[offset + newLen],
+					&lp->data[offset + oldLen],
+					lp->len - offset - oldLen);
+
+				lp->len += deltaLen;
+			}
+			offset += newLen;
+			if (globalFlag)
+				continue;
+			if (needPrint) {
+				printLines(num1, num1, FALSE);
+				needPrint = FALSE;
+			}
+			lp = lp->next;
+			num1++;
+			continue;
+		}
+
+		/*
+		 * The new string is larger, so allocate a new line
+		 * structure and use that.  Link it in place of
+		 * the old line structure.
+		 */
+		nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen);
+
+		nlp->len = lp->len + deltaLen;
+
+		memcpy(nlp->data, lp->data, offset);
+		memcpy(&nlp->data[offset], newStr, newLen);
+		memcpy(&nlp->data[offset + newLen],
+			&lp->data[offset + oldLen],
+			lp->len - offset - oldLen);
+
+		nlp->next = lp->next;
+		nlp->prev = lp->prev;
+		nlp->prev->next = nlp;
+		nlp->next->prev = nlp;
+
+		if (curLine == lp)
+			curLine = nlp;
+
+		free(lp);
+		lp = nlp;
+
+		offset += newLen;
+
+		if (globalFlag)
+			continue;
+
+		if (needPrint) {
+			printLines(num1, num1, FALSE);
+			needPrint = FALSE;
+		}
+
+		lp = lp->next;
+		num1++;
+	}
+
+	if (!didSub)
+		bb_error_msg("no substitutions found for \"%s\"", oldStr);
+}
+
+
+/*
+ * Search a line for the specified string starting at the specified
+ * offset in the line.  Returns the offset of the found string, or -1.
+ */
+static int findString(const LINE *lp, const char *str, int len, int offset)
+{
+	int left;
+	const char *cp, *ncp;
+
+	cp = &lp->data[offset];
+	left = lp->len - offset;
+
+	while (left >= len) {
+		ncp = memchr(cp, *str, left);
+		if (ncp == NULL)
+			return -1;
+		left -= (ncp - cp);
+		if (left < len)
+			return -1;
+		cp = ncp;
+		if (memcmp(cp, str, len) == 0)
+			return (cp - lp->data);
+		cp++;
+		left--;
+	}
+
+	return -1;
+}
+
+
+/*
+ * Add lines which are typed in by the user.
+ * The lines are inserted just before the specified line number.
+ * The lines are terminated by a line containing a single dot (ugly!),
+ * or by an end of file.
+ */
+static void addLines(int num)
+{
+	int len;
+	char buf[USERSIZE + 1];
+
+	while (1) {
+		/* Returns:
+		 * -1 on read errors or EOF, or on bare Ctrl-D.
+		 * 0  on ctrl-C,
+		 * >0 length of input string, including terminating '\n'
+		 */
+		len = read_line_input(NULL, "", buf, sizeof(buf), /*timeout*/ -1);
+		if (len <= 0) {
+			/* Previously, ctrl-C was exiting to shell.
+			 * Now we exit to ed prompt. Is in important? */
+			return;
+		}
+		if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
+			return;
+		if (!insertLine(num++, buf, len))
+			return;
+	}
+}
+
+
+/*
+ * Parse a line number argument if it is present.  This is a sum
+ * or difference of numbers, '.', '$', 'x, or a search string.
+ * Returns TRUE if successful (whether or not there was a number).
+ * Returns FALSE if there was a parsing error, with a message output.
+ * Whether there was a number is returned indirectly, as is the number.
+ * The character pointer which stopped the scan is also returned.
+ */
+static int getNum(const char **retcp, smallint *retHaveNum, int *retNum)
+{
+	const char *cp;
+	char *endStr, str[USERSIZE];
+	int value, num;
+	smallint haveNum, minus;
+
+	cp = *retcp;
+	value = 0;
+	haveNum = FALSE;
+	minus = 0;
+
+	while (TRUE) {
+		cp = skip_blank(cp);
+
+		switch (*cp) {
+			case '.':
+				haveNum = TRUE;
+				num = curNum;
+				cp++;
+				break;
+
+			case '$':
+				haveNum = TRUE;
+				num = lastNum;
+				cp++;
+				break;
+
+			case '\'':
+				cp++;
+				if ((*cp < 'a') || (*cp > 'z')) {
+					bb_error_msg("bad mark name");
+					return FALSE;
+				}
+				haveNum = TRUE;
+				num = marks[*cp++ - 'a'];
+				break;
+
+			case '/':
+				strcpy(str, ++cp);
+				endStr = strchr(str, '/');
+				if (endStr) {
+					*endStr++ = '\0';
+					cp += (endStr - str);
+				} else
+					cp = "";
+				num = searchLines(str, curNum, lastNum);
+				if (num == 0)
+					return FALSE;
+				haveNum = TRUE;
+				break;
+
+			default:
+				if (!isdigit(*cp)) {
+					*retcp = cp;
+					*retHaveNum = haveNum;
+					*retNum = value;
+					return TRUE;
+				}
+				num = 0;
+				while (isdigit(*cp))
+					num = num * 10 + *cp++ - '0';
+				haveNum = TRUE;
+				break;
+		}
+
+		value += (minus ? -num : num);
+
+		cp = skip_blank(cp);
+
+		switch (*cp) {
+			case '-':
+				minus = 1;
+				cp++;
+				break;
+
+			case '+':
+				minus = 0;
+				cp++;
+				break;
+
+			default:
+				*retcp = cp;
+				*retHaveNum = haveNum;
+				*retNum = value;
+				return TRUE;
+		}
+	}
+}
+
+
+/*
+ * Read lines from a file at the specified line number.
+ * Returns TRUE if the file was successfully read.
+ */
+static int readLines(const char *file, int num)
+{
+	int fd, cc;
+	int len, lineCount, charCount;
+	char *cp;
+
+	if ((num < 1) || (num > lastNum + 1)) {
+		bb_error_msg("bad line for read");
+		return FALSE;
+	}
+
+	fd = open(file, 0);
+	if (fd < 0) {
+		bb_simple_perror_msg(file);
+		return FALSE;
+	}
+
+	bufPtr = bufBase;
+	bufUsed = 0;
+	lineCount = 0;
+	charCount = 0;
+	cc = 0;
+
+	printf("\"%s\", ", file);
+	fflush_all();
+
+	do {
+		cp = memchr(bufPtr, '\n', bufUsed);
+
+		if (cp) {
+			len = (cp - bufPtr) + 1;
+			if (!insertLine(num, bufPtr, len)) {
+				close(fd);
+				return FALSE;
+			}
+			bufPtr += len;
+			bufUsed -= len;
+			charCount += len;
+			lineCount++;
+			num++;
+			continue;
+		}
+
+		if (bufPtr != bufBase) {
+			memcpy(bufBase, bufPtr, bufUsed);
+			bufPtr = bufBase + bufUsed;
+		}
+
+		if (bufUsed >= bufSize) {
+			len = (bufSize * 3) / 2;
+			cp = xrealloc(bufBase, len);
+			bufBase = cp;
+			bufPtr = bufBase + bufUsed;
+			bufSize = len;
+		}
+
+		cc = safe_read(fd, bufPtr, bufSize - bufUsed);
+		bufUsed += cc;
+		bufPtr = bufBase;
+
+	} while (cc > 0);
+
+	if (cc < 0) {
+		bb_simple_perror_msg(file);
+		close(fd);
+		return FALSE;
+	}
+
+	if (bufUsed) {
+		if (!insertLine(num, bufPtr, bufUsed)) {
+			close(fd);
+			return -1;
+		}
+		lineCount++;
+		charCount += bufUsed;
+	}
+
+	close(fd);
+
+	printf("%d lines%s, %d chars\n", lineCount,
+		(bufUsed ? " (incomplete)" : ""), charCount);
+
+	return TRUE;
+}
+
+
+/*
+ * Write the specified lines out to the specified file.
+ * Returns TRUE if successful, or FALSE on an error with a message output.
+ */
+static int writeLines(const char *file, int num1, int num2)
+{
+	LINE *lp;
+	int fd, lineCount, charCount;
+
+	if (bad_nums(num1, num2, "write"))
+		return FALSE;
+
+	lineCount = 0;
+	charCount = 0;
+
+	fd = creat(file, 0666);
+	if (fd < 0) {
+		bb_simple_perror_msg(file);
+		return FALSE;
+	}
+
+	printf("\"%s\", ", file);
+	fflush_all();
+
+	lp = findLine(num1);
+	if (lp == NULL) {
+		close(fd);
+		return FALSE;
+	}
+
+	while (num1++ <= num2) {
+		if (full_write(fd, lp->data, lp->len) != lp->len) {
+			bb_simple_perror_msg(file);
+			close(fd);
+			return FALSE;
+		}
+		charCount += lp->len;
+		lineCount++;
+		lp = lp->next;
+	}
+
+	if (close(fd) < 0) {
+		bb_simple_perror_msg(file);
+		return FALSE;
+	}
+
+	printf("%d lines, %d chars\n", lineCount, charCount);
+	return TRUE;
+}
+
+
+/*
+ * Print lines in a specified range.
+ * The last line printed becomes the current line.
+ * If expandFlag is TRUE, then the line is printed specially to
+ * show magic characters.
+ */
+static int printLines(int num1, int num2, int expandFlag)
+{
+	const LINE *lp;
+	const char *cp;
+	int ch, count;
+
+	if (bad_nums(num1, num2, "print"))
+		return FALSE;
+
+	lp = findLine(num1);
+	if (lp == NULL)
+		return FALSE;
+
+	while (num1 <= num2) {
+		if (!expandFlag) {
+			write(STDOUT_FILENO, lp->data, lp->len);
+			setCurNum(num1++);
+			lp = lp->next;
+			continue;
+		}
+
+		/*
+		 * Show control characters and characters with the
+		 * high bit set specially.
+		 */
+		cp = lp->data;
+		count = lp->len;
+
+		if ((count > 0) && (cp[count - 1] == '\n'))
+			count--;
+
+		while (count-- > 0) {
+			ch = (unsigned char) *cp++;
+			fputc_printable(ch | PRINTABLE_META, stdout);
+		}
+
+		fputs("$\n", stdout);
+
+		setCurNum(num1++);
+		lp = lp->next;
+	}
+
+	return TRUE;
+}
+
+
+/*
+ * Insert a new line with the specified text.
+ * The line is inserted so as to become the specified line,
+ * thus pushing any existing and further lines down one.
+ * The inserted line is also set to become the current line.
+ * Returns TRUE if successful.
+ */
+static int insertLine(int num, const char *data, int len)
+{
+	LINE *newLp, *lp;
+
+	if ((num < 1) || (num > lastNum + 1)) {
+		bb_error_msg("inserting at bad line number");
+		return FALSE;
+	}
+
+	newLp = xmalloc(sizeof(LINE) + len - 1);
+
+	memcpy(newLp->data, data, len);
+	newLp->len = len;
+
+	if (num > lastNum)
+		lp = &lines;
+	else {
+		lp = findLine(num);
+		if (lp == NULL) {
+			free((char *) newLp);
+			return FALSE;
+		}
+	}
+
+	newLp->next = lp;
+	newLp->prev = lp->prev;
+	lp->prev->next = newLp;
+	lp->prev = newLp;
+
+	lastNum++;
+	dirty = TRUE;
+	return setCurNum(num);
+}
+
+
+/*
+ * Delete lines from the given range.
+ */
+static void deleteLines(int num1, int num2)
+{
+	LINE *lp, *nlp, *plp;
+	int count;
+
+	if (bad_nums(num1, num2, "delete"))
+		return;
+
+	lp = findLine(num1);
+	if (lp == NULL)
+		return;
+
+	if ((curNum >= num1) && (curNum <= num2)) {
+		if (num2 < lastNum)
+			setCurNum(num2 + 1);
+		else if (num1 > 1)
+			setCurNum(num1 - 1);
+		else
+			curNum = 0;
+	}
+
+	count = num2 - num1 + 1;
+	if (curNum > num2)
+		curNum -= count;
+	lastNum -= count;
+
+	while (count-- > 0) {
+		nlp = lp->next;
+		plp = lp->prev;
+		plp->next = nlp;
+		nlp->prev = plp;
+		free(lp);
+		lp = nlp;
+	}
+
+	dirty = TRUE;
+}
+
+
+/*
+ * Search for a line which contains the specified string.
+ * If the string is "", then the previously searched for string
+ * is used.  The currently searched for string is saved for future use.
+ * Returns the line number which matches, or 0 if there was no match
+ * with an error printed.
+ */
+static NOINLINE int searchLines(const char *str, int num1, int num2)
+{
+	const LINE *lp;
+	int len;
+
+	if (bad_nums(num1, num2, "search"))
+		return 0;
+
+	if (*str == '\0') {
+		if (searchString[0] == '\0') {
+			bb_error_msg("no previous search string");
+			return 0;
+		}
+		str = searchString;
+	}
+
+	if (str != searchString)
+		strcpy(searchString, str);
+
+	len = strlen(str);
+
+	lp = findLine(num1);
+	if (lp == NULL)
+		return 0;
+
+	while (num1 <= num2) {
+		if (findString(lp, str, len, 0) >= 0)
+			return num1;
+		num1++;
+		lp = lp->next;
+	}
+
+	bb_error_msg("can't find string \"%s\"", str);
+	return 0;
+}
+
+
+/*
+ * Return a pointer to the specified line number.
+ */
+static LINE *findLine(int num)
+{
+	LINE *lp;
+	int lnum;
+
+	if ((num < 1) || (num > lastNum)) {
+		bb_error_msg("line number %d does not exist", num);
+		return NULL;
+	}
+
+	if (curNum <= 0) {
+		curNum = 1;
+		curLine = lines.next;
+	}
+
+	if (num == curNum)
+		return curLine;
+
+	lp = curLine;
+	lnum = curNum;
+	if (num < (curNum / 2)) {
+		lp = lines.next;
+		lnum = 1;
+	} else if (num > ((curNum + lastNum) / 2)) {
+		lp = lines.prev;
+		lnum = lastNum;
+	}
+
+	while (lnum < num) {
+		lp = lp->next;
+		lnum++;
+	}
+
+	while (lnum > num) {
+		lp = lp->prev;
+		lnum--;
+	}
+	return lp;
+}
+
+
+/*
+ * Set the current line number.
+ * Returns TRUE if successful.
+ */
+static int setCurNum(int num)
+{
+	LINE *lp;
+
+	lp = findLine(num);
+	if (lp == NULL)
+		return FALSE;
+	curNum = num;
+	curLine = lp;
+	return TRUE;
+}
diff --git a/ap/app/busybox/src/editors/patch.c b/ap/app/busybox/src/editors/patch.c
new file mode 100644
index 0000000..13785ef
--- /dev/null
+++ b/ap/app/busybox/src/editors/patch.c
@@ -0,0 +1,552 @@
+/* vi: set sw=4 ts=4:
+ *
+ * Apply a "universal" diff.
+ * Adapted from toybox's patch implementation.
+ *
+ * Copyright 2007 Rob Landley <rob@landley.net>
+ *
+ * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
+ * (But only does -u, because who still cares about "ed"?)
+ *
+ * TODO:
+ * -b backup
+ * -l treat all whitespace as a single space
+ * -d chdir first
+ * -D define wrap #ifdef and #ifndef around changes
+ * -o outfile output here instead of in place
+ * -r rejectfile write rejected hunks to this file
+ * --dry-run (regression!)
+ *
+ * -f force (no questions asked)
+ * -F fuzz (number, default 2)
+ * [file] which file to patch
+ */
+
+//config:config PATCH
+//config:	bool "patch"
+//config:	default y
+//config:	help
+//config:	  Apply a unified diff formatted patch.
+
+//applet:IF_PATCH(APPLET(patch, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_PATCH) += patch.o
+
+//usage:#define patch_trivial_usage
+//usage:       "[OPTIONS] [ORIGFILE [PATCHFILE]]"
+//usage:#define patch_full_usage "\n\n"
+//usage:	IF_LONG_OPTS(
+//usage:       "	-p,--strip N		Strip N leading components from file names"
+//usage:     "\n	-i,--input DIFF		Read DIFF instead of stdin"
+//usage:     "\n	-R,--reverse		Reverse patch"
+//usage:     "\n	-N,--forward		Ignore already applied patches"
+/*usage:     "\n	--dry-run		Don't actually change files" - TODO */
+//usage:     "\n	-E,--remove-empty-files	Remove output files if they become empty"
+//usage:	)
+//usage:	IF_NOT_LONG_OPTS(
+//usage:       "	-p N	Strip N leading components from file names"
+//usage:     "\n	-i DIFF	Read DIFF instead of stdin"
+//usage:     "\n	-R	Reverse patch"
+//usage:     "\n	-N	Ignore already applied patches"
+//usage:     "\n	-E	Remove output files if they become empty"
+//usage:	)
+/* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */
+/* -x "debug" is supported but does nothing */
+//usage:
+//usage:#define patch_example_usage
+//usage:       "$ patch -p1 < example.diff\n"
+//usage:       "$ patch -p0 -i example.diff"
+
+#include "libbb.h"
+
+
+// libbb candidate?
+
+struct double_list {
+	struct double_list *next;
+	struct double_list *prev;
+	char *data;
+};
+
+// Free all the elements of a linked list
+// Call freeit() on each element before freeing it.
+static void dlist_free(struct double_list *list, void (*freeit)(void *data))
+{
+	while (list) {
+		void *pop = list;
+		list = list->next;
+		freeit(pop);
+		// Bail out also if list is circular.
+		if (list == pop) break;
+	}
+}
+
+// Add an entry before "list" element in (circular) doubly linked list
+static struct double_list *dlist_add(struct double_list **list, char *data)
+{
+	struct double_list *llist;
+	struct double_list *line = xmalloc(sizeof(*line));
+
+	line->data = data;
+	llist = *list;
+	if (llist) {
+		struct double_list *p;
+		line->next = llist;
+		p = line->prev = llist->prev;
+		// (list is circular, we assume p is never NULL)
+		p->next = line;
+		llist->prev = line;
+	} else
+		*list = line->next = line->prev = line;
+
+	return line;
+}
+
+
+struct globals {
+	char *infile;
+	long prefix;
+
+	struct double_list *current_hunk;
+
+	long oldline, oldlen, newline, newlen;
+	long linenum;
+	int context, state, hunknum;
+	int filein, fileout;
+	char *tempname;
+
+	int exitval;
+};
+#define TT (*ptr_to_globals)
+#define INIT_TT() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \
+} while (0)
+
+
+#define FLAG_STR "Rup:i:NEx"
+/* FLAG_REVERSE must be == 1! Code uses this fact. */
+#define FLAG_REVERSE (1 << 0)
+#define FLAG_u       (1 << 1)
+#define FLAG_PATHLEN (1 << 2)
+#define FLAG_INPUT   (1 << 3)
+#define FLAG_IGNORE  (1 << 4)
+#define FLAG_RMEMPTY (1 << 5)
+/* Enable this bit and use -x for debug output: */
+#define FLAG_DEBUG   (0 << 6)
+
+// Dispose of a line of input, either by writing it out or discarding it.
+
+// state < 2: just free
+// state = 2: write whole line to stderr
+// state = 3: write whole line to fileout
+// state > 3: write line+1 to fileout when *line != state
+
+#define PATCH_DEBUG (option_mask32 & FLAG_DEBUG)
+
+static void do_line(void *data)
+{
+	struct double_list *dlist = data;
+
+	if (TT.state>1 && *dlist->data != TT.state)
+		fdprintf(TT.state == 2 ? 2 : TT.fileout,
+			"%s\n", dlist->data+(TT.state>3 ? 1 : 0));
+
+	if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data);
+
+	free(dlist->data);
+	free(dlist);
+}
+
+static void finish_oldfile(void)
+{
+	if (TT.tempname) {
+		// Copy the rest of the data and replace the original with the copy.
+		char *temp;
+
+		if (TT.filein != -1) {
+			bb_copyfd_eof(TT.filein, TT.fileout);
+			xclose(TT.filein);
+		}
+		xclose(TT.fileout);
+
+		temp = xstrdup(TT.tempname);
+		temp[strlen(temp) - 6] = '\0';
+		rename(TT.tempname, temp);
+		free(temp);
+
+		free(TT.tempname);
+		TT.tempname = NULL;
+	}
+	TT.fileout = TT.filein = -1;
+}
+
+static void fail_hunk(void)
+{
+	if (!TT.current_hunk) return;
+
+	fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline);
+	TT.exitval = 1;
+
+	// If we got to this point, we've seeked to the end.  Discard changes to
+	// this file and advance to next file.
+
+	TT.state = 2;
+	TT.current_hunk->prev->next = NULL;
+	dlist_free(TT.current_hunk, do_line);
+	TT.current_hunk = NULL;
+
+	// Abort the copy and delete the temporary file.
+	close(TT.filein);
+	close(TT.fileout);
+	unlink(TT.tempname);
+	free(TT.tempname);
+	TT.tempname = NULL;
+
+	TT.state = 0;
+}
+
+// Given a hunk of a unified diff, make the appropriate change to the file.
+// This does not use the location information, but instead treats a hunk
+// as a sort of regex.  Copies data from input to output until it finds
+// the change to be made, then outputs the changed data and returns.
+// (Finding EOF first is an error.)  This is a single pass operation, so
+// multiple hunks must occur in order in the file.
+
+static int apply_one_hunk(void)
+{
+	struct double_list *plist, *buf = NULL, *check;
+	int matcheof = 0, reverse = option_mask32 & FLAG_REVERSE, backwarn = 0;
+	/* Do we try "dummy" revert to check whether
+	 * to silently skip this hunk? Used to implement -N.
+	 */
+	int dummy_revert = 0;
+
+	// Break doubly linked list so we can use singly linked traversal function.
+	TT.current_hunk->prev->next = NULL;
+
+	// Match EOF if there aren't as many ending context lines as beginning
+	for (plist = TT.current_hunk; plist; plist = plist->next) {
+		if (plist->data[0]==' ') matcheof++;
+		else matcheof = 0;
+		if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data);
+	}
+	matcheof = !matcheof || matcheof < TT.context;
+
+	if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
+
+	// Loop through input data searching for this hunk.  Match all context
+	// lines and all lines to be removed until we've found the end of a
+	// complete hunk.
+	plist = TT.current_hunk;
+	buf = NULL;
+	if (reverse ? TT.oldlen : TT.newlen) for (;;) {
+		char *data = xmalloc_reads(TT.filein, NULL);
+
+		TT.linenum++;
+
+		// Figure out which line of hunk to compare with next.  (Skip lines
+		// of the hunk we'd be adding.)
+		while (plist && *plist->data == "+-"[reverse]) {
+			if (data && !strcmp(data, plist->data+1)) {
+				if (!backwarn) {
+					backwarn = TT.linenum;
+					if (option_mask32 & FLAG_IGNORE) {
+						dummy_revert = 1;
+						reverse ^= 1;
+						continue;
+					}
+				}
+			}
+			plist = plist->next;
+		}
+
+		// Is this EOF?
+		if (!data) {
+			if (PATCH_DEBUG) fdprintf(2, "INEOF\n");
+
+			// Does this hunk need to match EOF?
+			if (!plist && matcheof) break;
+
+			if (backwarn)
+				fdprintf(2,"Possibly reversed hunk %d at %ld\n",
+					TT.hunknum, TT.linenum);
+
+			// File ended before we found a place for this hunk.
+			fail_hunk();
+			goto done;
+		}
+
+		if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data);
+		check = dlist_add(&buf, data);
+
+		// Compare this line with next expected line of hunk.
+		// todo: teach the strcmp() to ignore whitespace.
+
+		// A match can fail because the next line doesn't match, or because
+		// we hit the end of a hunk that needed EOF, and this isn't EOF.
+
+		// If match failed, flush first line of buffered data and
+		// recheck buffered data for a new match until we find one or run
+		// out of buffer.
+
+		for (;;) {
+			if (!plist || strcmp(check->data, plist->data+1)) {
+				// Match failed.  Write out first line of buffered data and
+				// recheck remaining buffered data for a new match.
+
+				if (PATCH_DEBUG)
+					fdprintf(2, "NOT: %s\n", plist->data);
+
+				TT.state = 3;
+				check = buf;
+				buf = buf->next;
+				check->prev->next = buf;
+				buf->prev = check->prev;
+				do_line(check);
+				plist = TT.current_hunk;
+
+				// If we've reached the end of the buffer without confirming a
+				// match, read more lines.
+				if (check == buf) {
+					buf = NULL;
+					break;
+				}
+				check = buf;
+			} else {
+				if (PATCH_DEBUG)
+					fdprintf(2, "MAYBE: %s\n", plist->data);
+				// This line matches.  Advance plist, detect successful match.
+				plist = plist->next;
+				if (!plist && !matcheof) goto out;
+				check = check->next;
+				if (check == buf) break;
+			}
+		}
+	}
+out:
+	// We have a match.  Emit changed data.
+	TT.state = "-+"[reverse ^ dummy_revert];
+	dlist_free(TT.current_hunk, do_line);
+	TT.current_hunk = NULL;
+	TT.state = 1;
+done:
+	if (buf) {
+		buf->prev->next = NULL;
+		dlist_free(buf, do_line);
+	}
+
+	return TT.state;
+}
+
+// Read a patch file and find hunks, opening/creating/deleting files.
+// Call apply_one_hunk() on each hunk.
+
+// state 0: Not in a hunk, look for +++.
+// state 1: Found +++ file indicator, look for @@
+// state 2: In hunk: counting initial context lines
+// state 3: In hunk: getting body
+
+int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int patch_main(int argc UNUSED_PARAM, char **argv)
+{
+	int opts;
+	int reverse, state = 0;
+	char *oldname = NULL, *newname = NULL;
+	char *opt_p, *opt_i;
+	long oldlen = oldlen; /* for compiler */
+	long newlen = newlen; /* for compiler */
+
+	INIT_TT();
+
+	opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
+	argv += optind;
+	reverse = opts & FLAG_REVERSE;
+	TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
+	TT.filein = TT.fileout = -1;
+	if (opts & FLAG_INPUT) {
+		xmove_fd(xopen_stdin(opt_i), STDIN_FILENO);
+	} else {
+		if (argv[0] && argv[1]) {
+			xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);
+		}
+	}
+	if (argv[0]) {
+		oldname = xstrdup(argv[0]);
+		newname = xstrdup(argv[0]);
+	}
+
+	// Loop through the lines in the patch
+	for(;;) {
+		char *patchline;
+
+		patchline = xmalloc_fgetline(stdin);
+		if (!patchline) break;
+
+		// Other versions of patch accept damaged patches,
+		// so we need to also.
+		if (!*patchline) {
+			free(patchline);
+			patchline = xstrdup(" ");
+		}
+
+		// Are we assembling a hunk?
+		if (state >= 2) {
+			if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
+				dlist_add(&TT.current_hunk, patchline);
+
+				if (*patchline != '+') oldlen--;
+				if (*patchline != '-') newlen--;
+
+				// Context line?
+				if (*patchline==' ' && state==2) TT.context++;
+				else state=3;
+
+				// If we've consumed all expected hunk lines, apply the hunk.
+
+				if (!oldlen && !newlen) state = apply_one_hunk();
+				continue;
+			}
+			fail_hunk();
+			state = 0;
+			continue;
+		}
+
+		// Open a new file?
+		if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) {
+			char *s, **name = reverse ? &newname : &oldname;
+			int i;
+
+			if (*patchline == '+') {
+				name = reverse ? &oldname : &newname;
+				state = 1;
+			}
+
+			finish_oldfile();
+
+			if (!argv[0]) {
+				free(*name);
+				// Trim date from end of filename (if any).  We don't care.
+				for (s = patchline+4; *s && *s!='\t'; s++)
+					if (*s=='\\' && s[1]) s++;
+				i = atoi(s);
+				if (i>1900 && i<=1970)
+					*name = xstrdup("/dev/null");
+				else {
+					*s = 0;
+					*name = xstrdup(patchline+4);
+				}
+			}
+
+			// We defer actually opening the file because svn produces broken
+			// patches that don't signal they want to create a new file the
+			// way the patch man page says, so you have to read the first hunk
+			// and _guess_.
+
+		// Start a new hunk?  Usually @@ -oldline,oldlen +newline,newlen @@
+		// but a missing ,value means the value is 1.
+		} else if (state == 1 && !strncmp("@@ -", patchline, 4)) {
+			int i;
+			char *s = patchline+4;
+
+			// Read oldline[,oldlen] +newline[,newlen]
+
+			TT.oldlen = oldlen = TT.newlen = newlen = 1;
+			TT.oldline = strtol(s, &s, 10);
+			if (*s == ',') TT.oldlen = oldlen = strtol(s+1, &s, 10);
+			TT.newline = strtol(s+2, &s, 10);
+			if (*s == ',') TT.newlen = newlen = strtol(s+1, &s, 10);
+
+			if (oldlen < 1 && newlen < 1)
+				bb_error_msg_and_die("Really? %s", patchline);
+
+			TT.context = 0;
+			state = 2;
+
+			// If this is the first hunk, open the file.
+			if (TT.filein == -1) {
+				int oldsum, newsum, empty = 0;
+				char *name;
+
+				oldsum = TT.oldline + oldlen;
+				newsum = TT.newline + newlen;
+
+				name = reverse ? oldname : newname;
+
+				// We're deleting oldname if new file is /dev/null (before -p)
+				// or if new hunk is empty (zero context) after patching
+				if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) {
+					name = reverse ? newname : oldname;
+					empty++;
+				}
+
+				// handle -p path truncation.
+				for (i = 0, s = name; *s;) {
+					if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i)
+						break;
+					if (*s++ != '/')
+						continue;
+					while (*s == '/')
+						s++;
+					i++;
+					name = s;
+				}
+
+				if (empty) {
+					// File is empty after the patches have been applied
+					state = 0;
+					if (option_mask32 & FLAG_RMEMPTY) {
+						// If flag -E or --remove-empty-files is set
+						printf("removing %s\n", name);
+						xunlink(name);
+					} else {
+						printf("patching file %s\n", name);
+						xclose(xopen(name, O_WRONLY | O_TRUNC));
+					}
+				// If we've got a file to open, do so.
+				} else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) {
+					struct stat statbuf;
+
+					// If the old file was null, we're creating a new one.
+					if (!strcmp(oldname, "/dev/null") || !oldsum) {
+						printf("creating %s\n", name);
+						s = strrchr(name, '/');
+						if (s) {
+							*s = 0;
+							bb_make_directory(name, -1, FILEUTILS_RECUR);
+							*s = '/';
+						}
+						TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
+					} else {
+						printf("patching file %s\n", name);
+						TT.filein = xopen(name, O_RDONLY);
+					}
+
+					TT.tempname = xasprintf("%sXXXXXX", name);
+					TT.fileout = xmkstemp(TT.tempname);
+					// Set permissions of output file
+					fstat(TT.filein, &statbuf);
+					fchmod(TT.fileout, statbuf.st_mode);
+
+					TT.linenum = 0;
+					TT.hunknum = 0;
+				}
+			}
+
+			TT.hunknum++;
+
+			continue;
+		}
+
+		// If we didn't continue above, discard this line.
+		free(patchline);
+	}
+
+	finish_oldfile();
+
+	if (ENABLE_FEATURE_CLEAN_UP) {
+		free(oldname);
+		free(newname);
+	}
+
+	return TT.exitval;
+}
diff --git a/ap/app/busybox/src/editors/patch_bbox.c b/ap/app/busybox/src/editors/patch_bbox.c
new file mode 100644
index 0000000..78aa5fd
--- /dev/null
+++ b/ap/app/busybox/src/editors/patch_bbox.c
@@ -0,0 +1,306 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * busybox patch applet to handle the unified diff format.
+ * Copyright (C) 2003 Glenn McGrath
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * This applet is written to work with patches generated by GNU diff,
+ * where there is equivalent functionality busybox patch shall behave
+ * as per GNU patch.
+ *
+ * There is a SUSv3 specification for patch, however it looks to be
+ * incomplete, it doesnt even mention unified diff format.
+ * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html
+ *
+ * Issues
+ * - Non-interactive
+ * - Patches must apply cleanly or patch (not just one hunk) will fail.
+ * - Reject file isnt saved
+ */
+
+#include "libbb.h"
+
+static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count)
+{
+	while (src_stream && lines_count) {
+		char *line;
+		line = xmalloc_fgets(src_stream);
+		if (line == NULL) {
+			break;
+		}
+		if (fputs(line, dst_stream) == EOF) {
+			bb_perror_msg_and_die("error writing to new file");
+		}
+		free(line);
+		lines_count--;
+	}
+	return lines_count;
+}
+
+/* If patch_level is -1 it will remove all directory names
+ * char *line must be greater than 4 chars
+ * returns NULL if the file doesnt exist or error
+ * returns malloc'ed filename
+ * NB: frees 1st argument!
+ */
+static char *extract_filename(char *line, int patch_level, const char *pat)
+{
+	char *temp = NULL, *filename_start_ptr = line + 4;
+
+	if (strncmp(line, pat, 4) == 0) {
+		/* Terminate string at end of source filename */
+		line[strcspn(line, "\t\n\r")] = '\0';
+
+		/* Skip over (patch_level) number of leading directories */
+		while (patch_level--) {
+			temp = strchr(filename_start_ptr, '/');
+			if (!temp)
+				break;
+			filename_start_ptr = temp + 1;
+		}
+		temp = xstrdup(filename_start_ptr);
+	}
+	free(line);
+	return temp;
+}
+
+int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int patch_main(int argc UNUSED_PARAM, char **argv)
+{
+	struct stat saved_stat;
+	char *patch_line;
+	FILE *patch_file;
+	int patch_level;
+	int ret = 0;
+	char plus = '+';
+	unsigned opt;
+	enum {
+		OPT_R = (1 << 2),
+		OPT_N = (1 << 3),
+		/*OPT_f = (1 << 4), ignored */
+		/*OPT_E = (1 << 5), ignored, this is the default */
+		/*OPT_g = (1 << 6), ignored */
+		OPT_dry_run = (1 << 7) * ENABLE_LONG_OPTS,
+	};
+
+	xfunc_error_retval = 2;
+	{
+		const char *p = "-1";
+		const char *i = "-"; /* compat */
+#if ENABLE_LONG_OPTS
+		static const char patch_longopts[] ALIGN1 =
+			"strip\0"                 Required_argument "p"
+			"input\0"                 Required_argument "i"
+			"reverse\0"               No_argument       "R"
+			"forward\0"               No_argument       "N"
+		/* "Assume user knows what [s]he is doing, do not ask any questions": */
+			"force\0"                 No_argument       "f" /*ignored*/
+# if ENABLE_DESKTOP
+			"remove-empty-files\0"    No_argument       "E" /*ignored*/
+		/* "Controls actions when a file is under RCS or SCCS control,
+		 * and does not exist or is read-only and matches the default version,
+		 * or when a file is under ClearCase control and does not exist..."
+		 * IOW: rather obscure option.
+		 * But Gentoo's portage does use -g0 */
+			"get\0"                   Required_argument "g" /*ignored*/
+# endif
+			"dry-run\0"               No_argument       "\xfd"
+# if ENABLE_DESKTOP
+			"backup-if-mismatch\0"    No_argument       "\xfe" /*ignored*/
+			"no-backup-if-mismatch\0" No_argument       "\xff" /*ignored*/
+# endif
+			;
+		applet_long_options = patch_longopts;
+#endif
+		/* -f,-E,-g are ignored */
+		opt = getopt32(argv, "p:i:RN""fEg:", &p, &i, NULL);
+		if (opt & OPT_R)
+			plus = '-';
+		patch_level = xatoi(p); /* can be negative! */
+		patch_file = xfopen_stdin(i);
+	}
+
+	patch_line = xmalloc_fgetline(patch_file);
+	while (patch_line) {
+		FILE *src_stream;
+		FILE *dst_stream;
+		//char *old_filename;
+		char *new_filename;
+		char *backup_filename = NULL;
+		unsigned src_cur_line = 1;
+		unsigned dst_cur_line = 0;
+		unsigned dst_beg_line;
+		unsigned bad_hunk_count = 0;
+		unsigned hunk_count = 0;
+		smallint copy_trailing_lines_flag = 0;
+
+		/* Skip everything upto the "---" marker
+		 * No need to parse the lines "Only in <dir>", and "diff <args>"
+		 */
+		do {
+			/* Extract the filename used before the patch was generated */
+			new_filename = extract_filename(patch_line, patch_level, "--- ");
+			// was old_filename above
+			patch_line = xmalloc_fgetline(patch_file);
+			if (!patch_line) goto quit;
+		} while (!new_filename);
+		free(new_filename); // "source" filename is irrelevant
+
+		new_filename = extract_filename(patch_line, patch_level, "+++ ");
+		if (!new_filename) {
+			bb_error_msg_and_die("invalid patch");
+		}
+
+		/* Get access rights from the file to be patched */
+		if (stat(new_filename, &saved_stat) != 0) {
+			char *slash = strrchr(new_filename, '/');
+			if (slash) {
+				/* Create leading directories */
+				*slash = '\0';
+				bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
+				*slash = '/';
+			}
+			src_stream = NULL;
+			saved_stat.st_mode = 0644;
+		} else if (!(opt & OPT_dry_run)) {
+			backup_filename = xasprintf("%s.orig", new_filename);
+			xrename(new_filename, backup_filename);
+			src_stream = xfopen_for_read(backup_filename);
+		} else
+			src_stream = xfopen_for_read(new_filename);
+
+		if (opt & OPT_dry_run) {
+			dst_stream = xfopen_for_write("/dev/null");
+		} else {
+			dst_stream = xfopen_for_write(new_filename);
+			fchmod(fileno(dst_stream), saved_stat.st_mode);
+		}
+
+		printf("patching file %s\n", new_filename);
+
+		/* Handle all hunks for this file */
+		patch_line = xmalloc_fgets(patch_file);
+		while (patch_line) {
+			unsigned count;
+			unsigned src_beg_line;
+			unsigned hunk_offset_start;
+			unsigned src_last_line = 1;
+			unsigned dst_last_line = 1;
+
+			if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
+			 && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
+			) {
+				/* No more hunks for this file */
+				break;
+			}
+			if (plus != '+') {
+				/* reverse patch */
+				unsigned tmp = src_last_line;
+				src_last_line = dst_last_line;
+				dst_last_line = tmp;
+				tmp = src_beg_line;
+				src_beg_line = dst_beg_line;
+				dst_beg_line = tmp;
+			}
+			hunk_count++;
+
+			if (src_beg_line && dst_beg_line) {
+				/* Copy unmodified lines upto start of hunk */
+				/* src_beg_line will be 0 if it's a new file */
+				count = src_beg_line - src_cur_line;
+				if (copy_lines(src_stream, dst_stream, count)) {
+					bb_error_msg_and_die("bad src file");
+				}
+				src_cur_line += count;
+				dst_cur_line += count;
+				copy_trailing_lines_flag = 1;
+			}
+			src_last_line += hunk_offset_start = src_cur_line;
+			dst_last_line += dst_cur_line;
+
+			while (1) {
+				free(patch_line);
+				patch_line = xmalloc_fgets(patch_file);
+				if (patch_line == NULL)
+					break; /* EOF */
+				if (!*patch_line) {
+					/* whitespace-damaged patch with "" lines */
+					free(patch_line);
+					patch_line = xstrdup(" ");
+				}
+				if ((*patch_line != '-') && (*patch_line != '+')
+				 && (*patch_line != ' ')
+				) {
+					break; /* End of hunk */
+				}
+				if (*patch_line != plus) { /* '-' or ' ' */
+					char *src_line = NULL;
+					if (src_cur_line == src_last_line)
+						break;
+					if (src_stream) {
+						src_line = xmalloc_fgets(src_stream);
+						if (src_line) {
+							int diff = strcmp(src_line, patch_line + 1);
+							src_cur_line++;
+							free(src_line);
+							if (diff)
+								src_line = NULL;
+						}
+					}
+					/* Do not patch an already patched hunk with -N */
+					if (src_line == 0 && (opt & OPT_N)) {
+						continue;
+					}
+					if (!src_line) {
+						bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start);
+						bad_hunk_count++;
+						break;
+					}
+					if (*patch_line != ' ') { /* '-' */
+						continue;
+					}
+				}
+				if (dst_cur_line == dst_last_line)
+					break;
+				fputs(patch_line + 1, dst_stream);
+				dst_cur_line++;
+			} /* end of while loop handling one hunk */
+		} /* end of while loop handling one file */
+
+		/* Cleanup last patched file */
+		if (copy_trailing_lines_flag) {
+			copy_lines(src_stream, dst_stream, (unsigned)(-1));
+		}
+		if (src_stream) {
+			fclose(src_stream);
+		}
+		fclose(dst_stream);
+		if (bad_hunk_count) {
+			ret = 1;
+			bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count);
+		} else {
+			/* It worked, we can remove the backup */
+			if (backup_filename) {
+				unlink(backup_filename);
+			}
+			if (!(opt & OPT_dry_run)
+			 && ((dst_cur_line == 0) || (dst_beg_line == 0))
+			) {
+				/* The new patched file is empty, remove it */
+				xunlink(new_filename);
+				// /* old_filename and new_filename may be the same file */
+				// unlink(old_filename);
+			}
+		}
+		free(backup_filename);
+		//free(old_filename);
+		free(new_filename);
+	} /* end of "while there are patch lines" */
+ quit:
+	/* 0 = SUCCESS
+	 * 1 = Some hunks failed
+	 * 2 = More serious problems (exited earlier)
+	 */
+	return ret;
+}
diff --git a/ap/app/busybox/src/editors/patch_toybox.c b/ap/app/busybox/src/editors/patch_toybox.c
new file mode 100644
index 0000000..a60bf07
--- /dev/null
+++ b/ap/app/busybox/src/editors/patch_toybox.c
@@ -0,0 +1,591 @@
+/* Adapted from toybox's patch. */
+
+/* vi: set sw=4 ts=4:
+ *
+ * patch.c - Apply a "universal" diff.
+ *
+ * Copyright 2007 Rob Landley <rob@landley.net>
+ *
+ * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
+ * (But only does -u, because who still cares about "ed"?)
+ *
+ * TODO:
+ * -b backup
+ * -l treat all whitespace as a single space
+ * -N ignore already applied
+ * -d chdir first
+ * -D define wrap #ifdef and #ifndef around changes
+ * -o outfile output here instead of in place
+ * -r rejectfile write rejected hunks to this file
+ *
+ * -E remove empty files --remove-empty-files
+ * -f force (no questions asked)
+ * -F fuzz (number, default 2)
+ * [file] which file to patch
+
+USE_PATCH(NEWTOY(patch, USE_TOYBOX_DEBUG("x")"up#i:R", TOYFLAG_USR|TOYFLAG_BIN))
+
+config PATCH
+	bool "patch"
+	default y
+	help
+	  usage: patch [-i file] [-p depth] [-Ru]
+
+	  Apply a unified diff to one or more files.
+
+	  -i	Input file (defaults=stdin)
+	  -p	number of '/' to strip from start of file paths (default=all)
+	  -R	Reverse patch.
+	  -u	Ignored (only handles "unified" diffs)
+
+	  This version of patch only handles unified diffs, and only modifies
+	  a file when all all hunks to that file apply.  Patch prints failed
+	  hunks to stderr, and exits with nonzero status if any hunks fail.
+
+	  A file compared against /dev/null (or with a date <= the epoch) is
+	  created/deleted as appropriate.
+*/
+#include "libbb.h"
+
+struct double_list {
+	struct double_list *next;
+	struct double_list *prev;
+	char *data;
+};
+
+// Return the first item from the list, advancing the list (which must be called
+// as &list)
+static
+void *TOY_llist_pop(void *list)
+{
+	// I'd use a void ** for the argument, and even accept the typecast in all
+	// callers as documentation you need the &, except the stupid compiler
+	// would then scream about type-punned pointers.  Screw it.
+	void **llist = (void **)list;
+	void **next = (void **)*llist;
+	*llist = *next;
+
+	return (void *)next;
+}
+
+// Free all the elements of a linked list
+// if freeit!=NULL call freeit() on each element before freeing it.
+static
+void TOY_llist_free(void *list, void (*freeit)(void *data))
+{
+	while (list) {
+		void *pop = TOY_llist_pop(&list);
+		if (freeit) freeit(pop);
+		else free(pop);
+
+		// End doubly linked list too.
+		if (list==pop) break;
+	}
+}
+
+// Add an entry to the end off a doubly linked list
+static
+struct double_list *dlist_add(struct double_list **list, char *data)
+{
+	struct double_list *line = xmalloc(sizeof(struct double_list));
+
+	line->data = data;
+	if (*list) {
+		line->next = *list;
+		line->prev = (*list)->prev;
+		(*list)->prev->next = line;
+		(*list)->prev = line;
+	} else *list = line->next = line->prev = line;
+
+	return line;
+}
+
+// Ensure entire path exists.
+// If mode != -1 set permissions on newly created dirs.
+// Requires that path string be writable (for temporary null terminators).
+static
+void xmkpath(char *path, int mode)
+{
+	char *p, old;
+	mode_t mask;
+	int rc;
+	struct stat st;
+
+	for (p = path; ; p++) {
+		if (!*p || *p == '/') {
+			old = *p;
+			*p = rc = 0;
+			if (stat(path, &st) || !S_ISDIR(st.st_mode)) {
+				if (mode != -1) {
+					mask = umask(0);
+					rc = mkdir(path, mode);
+					umask(mask);
+				} else rc = mkdir(path, 0777);
+			}
+			*p = old;
+			if(rc) bb_perror_msg_and_die("mkpath '%s'", path);
+		}
+		if (!*p) break;
+	}
+}
+
+// Slow, but small.
+static
+char *get_rawline(int fd, long *plen, char end)
+{
+	char c, *buf = NULL;
+	long len = 0;
+
+	for (;;) {
+		if (1>read(fd, &c, 1)) break;
+		if (!(len & 63)) buf=xrealloc(buf, len+65);
+		if ((buf[len++]=c) == end) break;
+	}
+	if (buf) buf[len]=0;
+	if (plen) *plen = len;
+
+	return buf;
+}
+
+static
+char *get_line(int fd)
+{
+	long len;
+	char *buf = get_rawline(fd, &len, '\n');
+
+	if (buf && buf[--len]=='\n') buf[len]=0;
+
+	return buf;
+}
+
+// Copy the rest of in to out and close both files.
+static
+void xsendfile(int in, int out)
+{
+	long len;
+	char buf[4096];
+
+	if (in<0) return;
+	for (;;) {
+		len = safe_read(in, buf, 4096);
+		if (len<1) break;
+		xwrite(out, buf, len);
+	}
+}
+
+// Copy the rest of the data and replace the original with the copy.
+static
+void replace_tempfile(int fdin, int fdout, char **tempname)
+{
+	char *temp = xstrdup(*tempname);
+
+	temp[strlen(temp)-6]=0;
+	if (fdin != -1) {
+		xsendfile(fdin, fdout);
+		xclose(fdin);
+	}
+	xclose(fdout);
+	rename(*tempname, temp);
+	free(*tempname);
+	free(temp);
+	*tempname = NULL;
+}
+
+// Open a temporary file to copy an existing file into.
+static
+int copy_tempfile(int fdin, char *name, char **tempname)
+{
+	struct stat statbuf;
+	int fd;
+
+	*tempname = xasprintf("%sXXXXXX", name);
+	fd = mkstemp(*tempname);
+	if(-1 == fd) bb_perror_msg_and_die("no temp file");
+
+	// Set permissions of output file
+	fstat(fdin, &statbuf);
+	fchmod(fd, statbuf.st_mode);
+
+	return fd;
+}
+
+// Abort the copy and delete the temporary file.
+static
+void delete_tempfile(int fdin, int fdout, char **tempname)
+{
+	close(fdin);
+	close(fdout);
+	unlink(*tempname);
+	free(*tempname);
+	*tempname = NULL;
+}
+
+
+
+struct globals {
+	char *infile;
+	long prefix;
+
+	struct double_list *current_hunk;
+	long oldline, oldlen, newline, newlen, linenum;
+	int context, state, filein, fileout, filepatch, hunknum;
+	char *tempname;
+
+	// was toys.foo:
+	int exitval;
+};
+#define TT (*ptr_to_globals)
+#define INIT_TT() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \
+} while (0)
+
+
+//bbox had: "p:i:RN"
+#define FLAG_STR "Rup:i:x"
+/* FLAG_REVERSE must be == 1! Code uses this fact. */
+#define FLAG_REVERSE (1 << 0)
+#define FLAG_u       (1 << 1)
+#define FLAG_PATHLEN (1 << 2)
+#define FLAG_INPUT   (1 << 3)
+//non-standard:
+#define FLAG_DEBUG   (1 << 4)
+
+// Dispose of a line of input, either by writing it out or discarding it.
+
+// state < 2: just free
+// state = 2: write whole line to stderr
+// state = 3: write whole line to fileout
+// state > 3: write line+1 to fileout when *line != state
+
+#define PATCH_DEBUG (option_mask32 & FLAG_DEBUG)
+
+static void do_line(void *data)
+{
+	struct double_list *dlist = (struct double_list *)data;
+
+	if (TT.state>1 && *dlist->data != TT.state)
+		fdprintf(TT.state == 2 ? 2 : TT.fileout,
+			"%s\n", dlist->data+(TT.state>3 ? 1 : 0));
+
+	if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data);
+
+	free(dlist->data);
+	free(data);
+}
+
+static void finish_oldfile(void)
+{
+	if (TT.tempname) replace_tempfile(TT.filein, TT.fileout, &TT.tempname);
+	TT.fileout = TT.filein = -1;
+}
+
+static void fail_hunk(void)
+{
+	if (!TT.current_hunk) return;
+	TT.current_hunk->prev->next = 0;
+
+	fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline);
+	TT.exitval = 1;
+
+	// If we got to this point, we've seeked to the end.  Discard changes to
+	// this file and advance to next file.
+
+	TT.state = 2;
+	TOY_llist_free(TT.current_hunk, do_line);
+	TT.current_hunk = NULL;
+	delete_tempfile(TT.filein, TT.fileout, &TT.tempname);
+	TT.state = 0;
+}
+
+// Given a hunk of a unified diff, make the appropriate change to the file.
+// This does not use the location information, but instead treats a hunk
+// as a sort of regex.  Copies data from input to output until it finds
+// the change to be made, then outputs the changed data and returns.
+// (Finding EOF first is an error.)  This is a single pass operation, so
+// multiple hunks must occur in order in the file.
+
+static int apply_one_hunk(void)
+{
+	struct double_list *plist, *buf = NULL, *check;
+	int matcheof = 0, reverse = option_mask32 & FLAG_REVERSE, backwarn = 0;
+
+	// Break doubly linked list so we can use singly linked traversal function.
+	TT.current_hunk->prev->next = NULL;
+
+	// Match EOF if there aren't as many ending context lines as beginning
+	for (plist = TT.current_hunk; plist; plist = plist->next) {
+		if (plist->data[0]==' ') matcheof++;
+		else matcheof = 0;
+		if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data);
+	}
+	matcheof = matcheof < TT.context;
+
+	if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
+
+	// Loop through input data searching for this hunk.  Match all context
+	// lines and all lines to be removed until we've found the end of a
+	// complete hunk.
+	plist = TT.current_hunk;
+	buf = NULL;
+	if (TT.context) for (;;) {
+		char *data = get_line(TT.filein);
+
+		TT.linenum++;
+
+		// Figure out which line of hunk to compare with next.  (Skip lines
+		// of the hunk we'd be adding.)
+		while (plist && *plist->data == "+-"[reverse]) {
+			if (data && !strcmp(data, plist->data+1)) {
+				if (!backwarn) {
+					fdprintf(2,"Possibly reversed hunk %d at %ld\n",
+						TT.hunknum, TT.linenum);
+					backwarn++;
+				}
+			}
+			plist = plist->next;
+		}
+
+		// Is this EOF?
+		if (!data) {
+			if (PATCH_DEBUG) fdprintf(2, "INEOF\n");
+
+			// Does this hunk need to match EOF?
+			if (!plist && matcheof) break;
+
+			// File ended before we found a place for this hunk.
+			fail_hunk();
+			goto done;
+		} else if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data);
+		check = dlist_add(&buf, data);
+
+		// Compare this line with next expected line of hunk.
+		// todo: teach the strcmp() to ignore whitespace.
+
+		// A match can fail because the next line doesn't match, or because
+		// we hit the end of a hunk that needed EOF, and this isn't EOF.
+
+		// If match failed, flush first line of buffered data and
+		// recheck buffered data for a new match until we find one or run
+		// out of buffer.
+
+		for (;;) {
+			if (!plist || strcmp(check->data, plist->data+1)) {
+				// Match failed.  Write out first line of buffered data and
+				// recheck remaining buffered data for a new match.
+
+				if (PATCH_DEBUG)
+					fdprintf(2, "NOT: %s\n", plist->data);
+
+				TT.state = 3;
+				check = TOY_llist_pop(&buf);
+				check->prev->next = buf;
+				buf->prev = check->prev;
+				do_line(check);
+				plist = TT.current_hunk;
+
+				// If we've reached the end of the buffer without confirming a
+				// match, read more lines.
+				if (check==buf) {
+					buf = 0;
+					break;
+				}
+				check = buf;
+			} else {
+				if (PATCH_DEBUG)
+					fdprintf(2, "MAYBE: %s\n", plist->data);
+				// This line matches.  Advance plist, detect successful match.
+				plist = plist->next;
+				if (!plist && !matcheof) goto out;
+				check = check->next;
+				if (check == buf) break;
+			}
+		}
+	}
+out:
+	// We have a match.  Emit changed data.
+	TT.state = "-+"[reverse];
+	TOY_llist_free(TT.current_hunk, do_line);
+	TT.current_hunk = NULL;
+	TT.state = 1;
+done:
+	if (buf) {
+		buf->prev->next = NULL;
+		TOY_llist_free(buf, do_line);
+	}
+
+	return TT.state;
+}
+
+// Read a patch file and find hunks, opening/creating/deleting files.
+// Call apply_one_hunk() on each hunk.
+
+// state 0: Not in a hunk, look for +++.
+// state 1: Found +++ file indicator, look for @@
+// state 2: In hunk: counting initial context lines
+// state 3: In hunk: getting body
+
+int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int patch_main(int argc UNUSED_PARAM, char **argv)
+{
+	int opts;
+	int reverse, state = 0;
+	char *oldname = NULL, *newname = NULL;
+	char *opt_p, *opt_i;
+
+	INIT_TT();
+
+	opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
+	reverse = opts & FLAG_REVERSE;
+	TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
+	if (opts & FLAG_INPUT) TT.filepatch = xopen(opt_i, O_RDONLY);
+	TT.filein = TT.fileout = -1;
+
+	// Loop through the lines in the patch
+	for(;;) {
+		char *patchline;
+
+		patchline = get_line(TT.filepatch);
+		if (!patchline) break;
+
+		// Other versions of patch accept damaged patches,
+		// so we need to also.
+		if (!*patchline) {
+			free(patchline);
+			patchline = xstrdup(" ");
+		}
+
+		// Are we assembling a hunk?
+		if (state >= 2) {
+			if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
+				dlist_add(&TT.current_hunk, patchline);
+
+				if (*patchline != '+') TT.oldlen--;
+				if (*patchline != '-') TT.newlen--;
+
+				// Context line?
+				if (*patchline==' ' && state==2) TT.context++;
+				else state=3;
+
+				// If we've consumed all expected hunk lines, apply the hunk.
+
+				if (!TT.oldlen && !TT.newlen) state = apply_one_hunk();
+				continue;
+			}
+			fail_hunk();
+			state = 0;
+			continue;
+		}
+
+		// Open a new file?
+		if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) {
+			char *s, **name = reverse ? &newname : &oldname;
+			int i;
+
+			if (*patchline == '+') {
+				name = reverse ? &oldname : &newname;
+				state = 1;
+			}
+
+			free(*name);
+			finish_oldfile();
+
+			// Trim date from end of filename (if any).  We don't care.
+			for (s = patchline+4; *s && *s!='\t'; s++)
+				if (*s=='\\' && s[1]) s++;
+			i = atoi(s);
+			if (i>1900 && i<=1970)
+				*name = xstrdup("/dev/null");
+			else {
+				*s = 0;
+				*name = xstrdup(patchline+4);
+			}
+
+			// We defer actually opening the file because svn produces broken
+			// patches that don't signal they want to create a new file the
+			// way the patch man page says, so you have to read the first hunk
+			// and _guess_.
+
+		// Start a new hunk?
+		} else if (state == 1 && !strncmp("@@ -", patchline, 4)) {
+			int i;
+
+			i = sscanf(patchline+4, "%ld,%ld +%ld,%ld", &TT.oldline,
+						&TT.oldlen, &TT.newline, &TT.newlen);
+			if (i != 4)
+				bb_error_msg_and_die("corrupt hunk %d at %ld", TT.hunknum, TT.linenum);
+
+			TT.context = 0;
+			state = 2;
+
+			// If this is the first hunk, open the file.
+			if (TT.filein == -1) {
+				int oldsum, newsum, del = 0;
+				char *s, *name;
+
+				oldsum = TT.oldline + TT.oldlen;
+				newsum = TT.newline + TT.newlen;
+
+				name = reverse ? oldname : newname;
+
+				// We're deleting oldname if new file is /dev/null (before -p)
+				// or if new hunk is empty (zero context) after patching
+				if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum))
+				{
+					name = reverse ? newname : oldname;
+					del++;
+				}
+
+				// handle -p path truncation.
+				for (i=0, s = name; *s;) {
+					if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i) break;
+					if (*(s++)=='/') {
+						name = s;
+						i++;
+					}
+				}
+
+				if (del) {
+					printf("removing %s\n", name);
+					xunlink(name);
+					state = 0;
+				// If we've got a file to open, do so.
+				} else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) {
+					// If the old file was null, we're creating a new one.
+					if (!strcmp(oldname, "/dev/null") || !oldsum) {
+						printf("creating %s\n", name);
+						s = strrchr(name, '/');
+						if (s) {
+							*s = 0;
+							xmkpath(name, -1);
+							*s = '/';
+						}
+						TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
+					} else {
+						printf("patching file %s\n", name);
+						TT.filein = xopen(name, O_RDWR);
+					}
+					TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname);
+					TT.linenum = 0;
+					TT.hunknum = 0;
+				}
+			}
+
+			TT.hunknum++;
+
+			continue;
+		}
+
+		// If we didn't continue above, discard this line.
+		free(patchline);
+	}
+
+	finish_oldfile();
+
+	if (ENABLE_FEATURE_CLEAN_UP) {
+		close(TT.filepatch);
+		free(oldname);
+		free(newname);
+	}
+
+	return TT.exitval;
+}
diff --git a/ap/app/busybox/src/editors/sed.c b/ap/app/busybox/src/editors/sed.c
new file mode 100644
index 0000000..f8ca5d3
--- /dev/null
+++ b/ap/app/busybox/src/editors/sed.c
@@ -0,0 +1,1552 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * sed.c - very minimalist version of sed
+ *
+ * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley
+ * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org>
+ * Copyright (C) 2002  Matt Kraai
+ * Copyright (C) 2003 by Glenn McGrath
+ * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
+ *
+ * MAINTAINER: Rob Landley <rob@landley.net>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+/* Code overview.
+ *
+ * Files are laid out to avoid unnecessary function declarations.  So for
+ * example, every function add_cmd calls occurs before add_cmd in this file.
+ *
+ * add_cmd() is called on each line of sed command text (from a file or from
+ * the command line).  It calls get_address() and parse_cmd_args().  The
+ * resulting sed_cmd_t structures are appended to a linked list
+ * (G.sed_cmd_head/G.sed_cmd_tail).
+ *
+ * add_input_file() adds a FILE* to the list of input files.  We need to
+ * know all input sources ahead of time to find the last line for the $ match.
+ *
+ * process_files() does actual sedding, reading data lines from each input FILE*
+ * (which could be stdin) and applying the sed command list (sed_cmd_head) to
+ * each of the resulting lines.
+ *
+ * sed_main() is where external code calls into this, with a command line.
+ */
+
+/* Supported features and commands in this version of sed:
+ *
+ * - comments ('#')
+ * - address matching: num|/matchstr/[,num|/matchstr/|$]command
+ * - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags)
+ * - edit commands: (a)ppend, (i)nsert, (c)hange
+ * - file commands: (r)ead
+ * - backreferences in substitution expressions (\0, \1, \2...\9)
+ * - grouped commands: {cmd1;cmd2}
+ * - transliteration (y/source-chars/dest-chars/)
+ * - pattern space hold space storing / swapping (g, h, x)
+ * - labels / branching (: label, b, t, T)
+ *
+ * (Note: Specifying an address (range) to match is *optional*; commands
+ * default to the whole pattern space if no specific address match was
+ * requested.)
+ *
+ * Todo:
+ * - Create a wrapper around regex to make libc's regex conform with sed
+ *
+ * Reference
+ * http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
+ */
+
+//usage:#define sed_trivial_usage
+//usage:       "[-inr] [-f FILE]... [-e CMD]... [FILE]...\n"
+//usage:       "or: sed [-inr] CMD [FILE]..."
+//usage:#define sed_full_usage "\n\n"
+//usage:       "	-e CMD	Add CMD to sed commands to be executed"
+//usage:     "\n	-f FILE	Add FILE contents to sed commands to be executed"
+//usage:     "\n	-i[SFX]	Edit files in-place (otherwise sends to stdout)"
+//usage:     "\n		Optionally back files up, appending SFX"
+//usage:     "\n	-n	Suppress automatic printing of pattern space"
+//usage:     "\n	-r	Use extended regex syntax"
+//usage:     "\n"
+//usage:     "\nIf no -e or -f, the first non-option argument is the sed command string."
+//usage:     "\nRemaining arguments are input files (stdin if none)."
+//usage:
+//usage:#define sed_example_usage
+//usage:       "$ echo \"foo\" | sed -e 's/f[a-zA-Z]o/bar/g'\n"
+//usage:       "bar\n"
+
+#include "libbb.h"
+#include "xregex.h"
+
+#if 0
+# define dbg(...) bb_error_msg(__VA_ARGS__)
+#else
+# define dbg(...) ((void)0)
+#endif
+
+
+enum {
+	OPT_in_place = 1 << 0,
+};
+
+/* Each sed command turns into one of these structures. */
+typedef struct sed_cmd_s {
+	/* Ordered by alignment requirements: currently 36 bytes on x86 */
+	struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
+
+	/* address storage */
+	regex_t *beg_match;     /* sed -e '/match/cmd' */
+	regex_t *end_match;     /* sed -e '/match/,/end_match/cmd' */
+	regex_t *sub_match;     /* For 's/sub_match/string/' */
+	int beg_line;           /* 'sed 1p'   0 == apply commands to all lines */
+	int beg_line_orig;      /* copy of the above, needed for -i */
+	int end_line;           /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
+
+	FILE *sw_file;          /* File (sw) command writes to, -1 for none. */
+	char *string;           /* Data string for (saicytb) commands. */
+
+	unsigned which_match;   /* (s) Which match to replace (0 for all) */
+
+	/* Bitfields (gcc won't group them if we don't) */
+	unsigned invert:1;      /* the '!' after the address */
+	unsigned in_match:1;    /* Next line also included in match? */
+	unsigned sub_p:1;       /* (s) print option */
+
+	char sw_last_char;      /* Last line written by (sw) had no '\n' */
+
+	/* GENERAL FIELDS */
+	char cmd;               /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
+} sed_cmd_t;
+
+static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v";
+
+struct globals {
+	/* options */
+	int be_quiet, regex_type;
+	FILE *nonstdout;
+	char *outname, *hold_space;
+
+	/* List of input files */
+	int input_file_count, current_input_file;
+	FILE **input_file_list;
+
+	regmatch_t regmatch[10];
+	regex_t *previous_regex_ptr;
+
+	/* linked list of sed commands */
+	sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
+
+	/* Linked list of append lines */
+	llist_t *append_head;
+
+	char *add_cmd_line;
+
+	struct pipeline {
+		char *buf;  /* Space to hold string */
+		int idx;    /* Space used */
+		int len;    /* Space allocated */
+	} pipeline;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+	char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define INIT_G() do { \
+	G.sed_cmd_tail = &G.sed_cmd_head; \
+} while (0)
+
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void sed_free_and_close_stuff(void)
+{
+	sed_cmd_t *sed_cmd = G.sed_cmd_head;
+
+	llist_free(G.append_head, free);
+
+	while (sed_cmd) {
+		sed_cmd_t *sed_cmd_next = sed_cmd->next;
+
+		if (sed_cmd->sw_file)
+			xprint_and_close_file(sed_cmd->sw_file);
+
+		if (sed_cmd->beg_match) {
+			regfree(sed_cmd->beg_match);
+			free(sed_cmd->beg_match);
+		}
+		if (sed_cmd->end_match) {
+			regfree(sed_cmd->end_match);
+			free(sed_cmd->end_match);
+		}
+		if (sed_cmd->sub_match) {
+			regfree(sed_cmd->sub_match);
+			free(sed_cmd->sub_match);
+		}
+		free(sed_cmd->string);
+		free(sed_cmd);
+		sed_cmd = sed_cmd_next;
+	}
+
+	free(G.hold_space);
+
+	while (G.current_input_file < G.input_file_count)
+		fclose(G.input_file_list[G.current_input_file++]);
+}
+#else
+void sed_free_and_close_stuff(void);
+#endif
+
+/* If something bad happens during -i operation, delete temp file */
+
+static void cleanup_outname(void)
+{
+	if (G.outname) unlink(G.outname);
+}
+
+/* strcpy, replacing "\from" with 'to'. If to is NUL, replacing "\any" with 'any' */
+
+static void parse_escapes(char *dest, const char *string, int len, char from, char to)
+{
+	int i = 0;
+
+	while (i < len) {
+		if (string[i] == '\\') {
+			if (!to || string[i+1] == from) {
+				*dest++ = to ? to : string[i+1];
+				i += 2;
+				continue;
+			}
+			*dest++ = string[i++];
+		}
+		/* TODO: is it safe wrt a string with trailing '\\' ? */
+		*dest++ = string[i++];
+	}
+	*dest = '\0';
+}
+
+static char *copy_parsing_escapes(const char *string, int len)
+{
+	const char *s;
+	char *dest = xmalloc(len + 1);
+
+	/* sed recognizes \n */
+	/* GNU sed also recognizes \t and \r */
+	for (s = "\nn\tt\rr"; *s; s += 2) {
+		parse_escapes(dest, string, len, s[1], s[0]);
+		string = dest;
+		len = strlen(dest);
+	}
+	return dest;
+}
+
+
+/*
+ * index_of_next_unescaped_regexp_delim - walks left to right through a string
+ * beginning at a specified index and returns the index of the next regular
+ * expression delimiter (typically a forward slash ('/')) not preceded by
+ * a backslash ('\').  A negative delimiter disables square bracket checking.
+ */
+static int index_of_next_unescaped_regexp_delim(int delimiter, const char *str)
+{
+	int bracket = -1;
+	int escaped = 0;
+	int idx = 0;
+	char ch;
+
+	if (delimiter < 0) {
+		bracket--;
+		delimiter = -delimiter;
+	}
+
+	for (; (ch = str[idx]) != '\0'; idx++) {
+		if (bracket >= 0) {
+			if (ch == ']'
+			 && !(bracket == idx - 1 || (bracket == idx - 2 && str[idx - 1] == '^'))
+			) {
+				bracket = -1;
+			}
+		} else if (escaped)
+			escaped = 0;
+		else if (ch == '\\')
+			escaped = 1;
+		else if (bracket == -1 && ch == '[')
+			bracket = idx;
+		else if (ch == delimiter)
+			return idx;
+	}
+
+	/* if we make it to here, we've hit the end of the string */
+	bb_error_msg_and_die("unmatched '%c'", delimiter);
+}
+
+/*
+ *  Returns the index of the third delimiter
+ */
+static int parse_regex_delim(const char *cmdstr, char **match, char **replace)
+{
+	const char *cmdstr_ptr = cmdstr;
+	unsigned char delimiter;
+	int idx = 0;
+
+	/* verify that the 's' or 'y' is followed by something.  That something
+	 * (typically a 'slash') is now our regexp delimiter... */
+	if (*cmdstr == '\0')
+		bb_error_msg_and_die("bad format in substitution expression");
+	delimiter = *cmdstr_ptr++;
+
+	/* save the match string */
+	idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
+	*match = copy_parsing_escapes(cmdstr_ptr, idx);
+
+	/* save the replacement string */
+	cmdstr_ptr += idx + 1;
+	idx = index_of_next_unescaped_regexp_delim(- (int)delimiter, cmdstr_ptr);
+	*replace = copy_parsing_escapes(cmdstr_ptr, idx);
+
+	return ((cmdstr_ptr - cmdstr) + idx);
+}
+
+/*
+ * returns the index in the string just past where the address ends.
+ */
+static int get_address(const char *my_str, int *linenum, regex_t ** regex)
+{
+	const char *pos = my_str;
+
+	if (isdigit(*my_str)) {
+		*linenum = strtol(my_str, (char**)&pos, 10);
+		/* endstr shouldnt ever equal NULL */
+	} else if (*my_str == '$') {
+		*linenum = -1;
+		pos++;
+	} else if (*my_str == '/' || *my_str == '\\') {
+		int next;
+		char delimiter;
+		char *temp;
+
+		delimiter = '/';
+		if (*my_str == '\\')
+			delimiter = *++pos;
+		next = index_of_next_unescaped_regexp_delim(delimiter, ++pos);
+		temp = copy_parsing_escapes(pos, next);
+		*regex = xzalloc(sizeof(regex_t));
+		xregcomp(*regex, temp, G.regex_type|REG_NEWLINE);
+		free(temp);
+		/* Move position to next character after last delimiter */
+		pos += (next+1);
+	}
+	return pos - my_str;
+}
+
+/* Grab a filename.  Whitespace at start is skipped, then goes to EOL. */
+static int parse_file_cmd(/*sed_cmd_t *sed_cmd,*/ const char *filecmdstr, char **retval)
+{
+	int start = 0, idx, hack = 0;
+
+	/* Skip whitespace, then grab filename to end of line */
+	while (isspace(filecmdstr[start]))
+		start++;
+	idx = start;
+	while (filecmdstr[idx] && filecmdstr[idx] != '\n')
+		idx++;
+
+	/* If lines glued together, put backslash back. */
+	if (filecmdstr[idx] == '\n')
+		hack = 1;
+	if (idx == start)
+		bb_error_msg_and_die("empty filename");
+	*retval = xstrndup(filecmdstr+start, idx-start+hack+1);
+	if (hack)
+		(*retval)[idx] = '\\';
+
+	return idx;
+}
+
+static int parse_subst_cmd(sed_cmd_t *sed_cmd, const char *substr)
+{
+	int cflags = G.regex_type;
+	char *match;
+	int idx;
+
+	/*
+	 * A substitution command should look something like this:
+	 *    s/match/replace/ #gIpw
+	 *    ||     |        |||
+	 *    mandatory       optional
+	 */
+	idx = parse_regex_delim(substr, &match, &sed_cmd->string);
+
+	/* determine the number of back references in the match string */
+	/* Note: we compute this here rather than in the do_subst_command()
+	 * function to save processor time, at the expense of a little more memory
+	 * (4 bits) per sed_cmd */
+
+	/* process the flags */
+
+	sed_cmd->which_match = 1;
+	while (substr[++idx]) {
+		/* Parse match number */
+		if (isdigit(substr[idx])) {
+			if (match[0] != '^') {
+				/* Match 0 treated as all, multiple matches we take the last one. */
+				const char *pos = substr + idx;
+/* FIXME: error check? */
+				sed_cmd->which_match = (unsigned)strtol(substr+idx, (char**) &pos, 10);
+				idx = pos - substr;
+			}
+			continue;
+		}
+		/* Skip spaces */
+		if (isspace(substr[idx]))
+			continue;
+
+		switch (substr[idx]) {
+		/* Replace all occurrences */
+		case 'g':
+			if (match[0] != '^')
+				sed_cmd->which_match = 0;
+			break;
+		/* Print pattern space */
+		case 'p':
+			sed_cmd->sub_p = 1;
+			break;
+		/* Write to file */
+		case 'w':
+		{
+			char *temp;
+			idx += parse_file_cmd(/*sed_cmd,*/ substr+idx, &temp);
+			break;
+		}
+		/* Ignore case (gnu exension) */
+		case 'I':
+			cflags |= REG_ICASE;
+			break;
+		/* Comment */
+		case '#':
+			// while (substr[++idx]) continue;
+			idx += strlen(substr + idx); // same
+			/* Fall through */
+		/* End of command */
+		case ';':
+		case '}':
+			goto out;
+		default:
+			bb_error_msg_and_die("bad option in substitution expression");
+		}
+	}
+ out:
+	/* compile the match string into a regex */
+	if (*match != '\0') {
+		/* If match is empty, we use last regex used at runtime */
+		sed_cmd->sub_match = xzalloc(sizeof(regex_t));
+		dbg("xregcomp('%s',%x)", match, cflags);
+		xregcomp(sed_cmd->sub_match, match, cflags);
+		dbg("regcomp ok");
+	}
+	free(match);
+
+	return idx;
+}
+
+/*
+ *  Process the commands arguments
+ */
+static const char *parse_cmd_args(sed_cmd_t *sed_cmd, const char *cmdstr)
+{
+	static const char cmd_letters[] = "saicrw:btTydDgGhHlnNpPqx={}";
+	enum {
+		IDX_s = 0,
+		IDX_a,
+		IDX_i,
+		IDX_c,
+		IDX_r,
+		IDX_w,
+		IDX_colon,
+		IDX_b,
+		IDX_t,
+		IDX_T,
+		IDX_y,
+		IDX_d,
+		IDX_D,
+		IDX_g,
+		IDX_G,
+		IDX_h,
+		IDX_H,
+		IDX_l,
+		IDX_n,
+		IDX_N,
+		IDX_p,
+		IDX_P,
+		IDX_q,
+		IDX_x,
+		IDX_equal,
+		IDX_lbrace,
+		IDX_rbrace,
+		IDX_nul
+	};
+	struct chk { char chk[sizeof(cmd_letters)-1 == IDX_nul ? 1 : -1]; };
+
+	unsigned idx = strchrnul(cmd_letters, sed_cmd->cmd) - cmd_letters;
+
+	/* handle (s)ubstitution command */
+	if (idx == IDX_s) {
+		cmdstr += parse_subst_cmd(sed_cmd, cmdstr);
+	}
+	/* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
+	else if (idx <= IDX_c) { /* a,i,c */
+		if (idx < IDX_c) { /* a,i */
+			if (sed_cmd->end_line || sed_cmd->end_match)
+				bb_error_msg_and_die("command '%c' uses only one address", sed_cmd->cmd);
+		}
+		for (;;) {
+			if (*cmdstr == '\n' || *cmdstr == '\\') {
+				cmdstr++;
+				break;
+			}
+			if (!isspace(*cmdstr))
+				break;
+			cmdstr++;
+		}
+		sed_cmd->string = xstrdup(cmdstr);
+		/* "\anychar" -> "anychar" */
+		parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), '\0', '\0');
+		cmdstr += strlen(cmdstr);
+	}
+	/* handle file cmds: (r)ead */
+	else if (idx <= IDX_w) { /* r,w */
+		if (idx < IDX_w) { /* r */
+			if (sed_cmd->end_line || sed_cmd->end_match)
+				bb_error_msg_and_die("command '%c' uses only one address", sed_cmd->cmd);
+		}
+		cmdstr += parse_file_cmd(/*sed_cmd,*/ cmdstr, &sed_cmd->string);
+		if (sed_cmd->cmd == 'w') {
+			sed_cmd->sw_file = xfopen_for_write(sed_cmd->string);
+			sed_cmd->sw_last_char = '\n';
+		}
+	}
+	/* handle branch commands */
+	else if (idx <= IDX_T) { /* :,b,t,T */
+		int length;
+
+		cmdstr = skip_whitespace(cmdstr);
+		length = strcspn(cmdstr, semicolon_whitespace);
+		if (length) {
+			sed_cmd->string = xstrndup(cmdstr, length);
+			cmdstr += length;
+		}
+	}
+	/* translation command */
+	else if (idx == IDX_y) {
+		char *match, *replace;
+		int i = cmdstr[0];
+
+		cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1;
+		/* \n already parsed, but \delimiter needs unescaping. */
+		parse_escapes(match, match, strlen(match), i, i);
+		parse_escapes(replace, replace, strlen(replace), i, i);
+
+		sed_cmd->string = xzalloc((strlen(match) + 1) * 2);
+		for (i = 0; match[i] && replace[i]; i++) {
+			sed_cmd->string[i*2] = match[i];
+			sed_cmd->string[i*2+1] = replace[i];
+		}
+		free(match);
+		free(replace);
+	}
+	/* if it wasnt a single-letter command that takes no arguments
+	 * then it must be an invalid command.
+	 */
+	else if (idx >= IDX_nul) { /* not d,D,g,G,h,H,l,n,N,p,P,q,x,=,{,} */
+		bb_error_msg_and_die("unsupported command %c", sed_cmd->cmd);
+	}
+
+	/* give back whatever's left over */
+	return cmdstr;
+}
+
+
+/* Parse address+command sets, skipping comment lines. */
+
+static void add_cmd(const char *cmdstr)
+{
+	sed_cmd_t *sed_cmd;
+	unsigned len, n;
+
+	/* Append this line to any unfinished line from last time. */
+	if (G.add_cmd_line) {
+		char *tp = xasprintf("%s\n%s", G.add_cmd_line, cmdstr);
+		free(G.add_cmd_line);
+		cmdstr = G.add_cmd_line = tp;
+	}
+
+	/* If this line ends with unescaped backslash, request next line. */
+	n = len = strlen(cmdstr);
+	while (n && cmdstr[n-1] == '\\')
+		n--;
+	if ((len - n) & 1) { /* if odd number of trailing backslashes */
+		if (!G.add_cmd_line)
+			G.add_cmd_line = xstrdup(cmdstr);
+		G.add_cmd_line[len-1] = '\0';
+		return;
+	}
+
+	/* Loop parsing all commands in this line. */
+	while (*cmdstr) {
+		/* Skip leading whitespace and semicolons */
+		cmdstr += strspn(cmdstr, semicolon_whitespace);
+
+		/* If no more commands, exit. */
+		if (!*cmdstr) break;
+
+		/* if this is a comment, jump past it and keep going */
+		if (*cmdstr == '#') {
+			/* "#n" is the same as using -n on the command line */
+			if (cmdstr[1] == 'n')
+				G.be_quiet++;
+			cmdstr = strpbrk(cmdstr, "\n\r");
+			if (!cmdstr) break;
+			continue;
+		}
+
+		/* parse the command
+		 * format is: [addr][,addr][!]cmd
+		 *            |----||-----||-|
+		 *            part1 part2  part3
+		 */
+
+		sed_cmd = xzalloc(sizeof(sed_cmd_t));
+
+		/* first part (if present) is an address: either a '$', a number or a /regex/ */
+		cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
+		sed_cmd->beg_line_orig = sed_cmd->beg_line;
+
+		/* second part (if present) will begin with a comma */
+		if (*cmdstr == ',') {
+			int idx;
+
+			cmdstr++;
+			idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
+			if (!idx)
+				bb_error_msg_and_die("no address after comma");
+			cmdstr += idx;
+		}
+
+		/* skip whitespace before the command */
+		cmdstr = skip_whitespace(cmdstr);
+
+		/* Check for inversion flag */
+		if (*cmdstr == '!') {
+			sed_cmd->invert = 1;
+			cmdstr++;
+
+			/* skip whitespace before the command */
+			cmdstr = skip_whitespace(cmdstr);
+		}
+
+		/* last part (mandatory) will be a command */
+		if (!*cmdstr)
+			bb_error_msg_and_die("missing command");
+		sed_cmd->cmd = *cmdstr++;
+		cmdstr = parse_cmd_args(sed_cmd, cmdstr);
+
+		/* Add the command to the command array */
+		*G.sed_cmd_tail = sed_cmd;
+		G.sed_cmd_tail = &sed_cmd->next;
+	}
+
+	/* If we glued multiple lines together, free the memory. */
+	free(G.add_cmd_line);
+	G.add_cmd_line = NULL;
+}
+
+/* Append to a string, reallocating memory as necessary. */
+
+#define PIPE_GROW 64
+
+static void pipe_putc(char c)
+{
+	if (G.pipeline.idx == G.pipeline.len) {
+		G.pipeline.buf = xrealloc(G.pipeline.buf,
+				G.pipeline.len + PIPE_GROW);
+		G.pipeline.len += PIPE_GROW;
+	}
+	G.pipeline.buf[G.pipeline.idx++] = c;
+}
+
+static void do_subst_w_backrefs(char *line, char *replace)
+{
+	int i, j;
+
+	/* go through the replacement string */
+	for (i = 0; replace[i]; i++) {
+		/* if we find a backreference (\1, \2, etc.) print the backref'ed text */
+		if (replace[i] == '\\') {
+			unsigned backref = replace[++i] - '0';
+			if (backref <= 9) {
+				/* print out the text held in G.regmatch[backref] */
+				if (G.regmatch[backref].rm_so != -1) {
+					j = G.regmatch[backref].rm_so;
+					while (j < G.regmatch[backref].rm_eo)
+						pipe_putc(line[j++]);
+				}
+				continue;
+			}
+			/* I _think_ it is impossible to get '\' to be
+			 * the last char in replace string. Thus we dont check
+			 * for replace[i] == NUL. (counterexample anyone?) */
+			/* if we find a backslash escaped character, print the character */
+			pipe_putc(replace[i]);
+			continue;
+		}
+		/* if we find an unescaped '&' print out the whole matched text. */
+		if (replace[i] == '&') {
+			j = G.regmatch[0].rm_so;
+			while (j < G.regmatch[0].rm_eo)
+				pipe_putc(line[j++]);
+			continue;
+		}
+		/* Otherwise just output the character. */
+		pipe_putc(replace[i]);
+	}
+}
+
+static int do_subst_command(sed_cmd_t *sed_cmd, char **line_p)
+{
+	char *line = *line_p;
+	unsigned match_count = 0;
+	bool altered = 0;
+	bool prev_match_empty = 1;
+	bool tried_at_eol = 0;
+	regex_t *current_regex;
+
+	current_regex = sed_cmd->sub_match;
+	/* Handle empty regex. */
+	if (!current_regex) {
+		current_regex = G.previous_regex_ptr;
+		if (!current_regex)
+			bb_error_msg_and_die("no previous regexp");
+	}
+	G.previous_regex_ptr = current_regex;
+
+	/* Find the first match */
+	dbg("matching '%s'", line);
+	if (REG_NOMATCH == regexec(current_regex, line, 10, G.regmatch, 0)) {
+		dbg("no match");
+		return 0;
+	}
+	dbg("match");
+
+	/* Initialize temporary output buffer. */
+	G.pipeline.buf = xmalloc(PIPE_GROW);
+	G.pipeline.len = PIPE_GROW;
+	G.pipeline.idx = 0;
+
+	/* Now loop through, substituting for matches */
+	do {
+		int start = G.regmatch[0].rm_so;
+		int end = G.regmatch[0].rm_eo;
+		int i;
+
+		match_count++;
+
+		/* If we aren't interested in this match, output old line to
+		 * end of match and continue */
+		if (sed_cmd->which_match
+		 && (sed_cmd->which_match != match_count)
+		) {
+			for (i = 0; i < end; i++)
+				pipe_putc(*line++);
+			/* Null match? Print one more char */
+			if (start == end && *line)
+				pipe_putc(*line++);
+			goto next;
+		}
+
+		/* Print everything before the match */
+		for (i = 0; i < start; i++)
+			pipe_putc(line[i]);
+
+		/* Then print the substitution string,
+		 * unless we just matched empty string after non-empty one.
+		 * Example: string "cccd", pattern "c*", repl "R":
+		 * result is "RdR", not "RRdR": first match "ccc",
+		 * second is "" before "d", third is "" after "d".
+		 * Second match is NOT replaced!
+		 */
+		if (prev_match_empty || start != 0 || start != end) {
+			//dbg("%d %d %d", prev_match_empty, start, end);
+			dbg("inserting replacement at %d in '%s'", start, line);
+			do_subst_w_backrefs(line, sed_cmd->string);
+			/* Flag that something has changed */
+			altered = 1;
+		} else {
+			dbg("NOT inserting replacement at %d in '%s'", start, line);
+		}
+
+		/* If matched string is empty (f.e. "c*" pattern),
+		 * copy verbatim one char after it before attempting more matches
+		 */
+		prev_match_empty = (start == end);
+		if (prev_match_empty) {
+			if (!line[end]) {
+				tried_at_eol = 1;
+			} else {
+				pipe_putc(line[end]);
+				end++;
+			}
+		}
+
+		/* Advance past the match */
+		dbg("line += %d", end);
+		line += end;
+
+		/* if we're not doing this globally, get out now */
+		if (sed_cmd->which_match != 0)
+			break;
+ next:
+		/* Exit if we are at EOL and already tried matching at it */
+		if (*line == '\0') {
+			if (tried_at_eol)
+				break;
+			tried_at_eol = 1;
+		}
+
+//maybe (end ? REG_NOTBOL : 0) instead of unconditional REG_NOTBOL?
+	} while (regexec(current_regex, line, 10, G.regmatch, REG_NOTBOL) != REG_NOMATCH);
+
+	/* Copy rest of string into output pipeline */
+	while (1) {
+		char c = *line++;
+		pipe_putc(c);
+		if (c == '\0')
+			break;
+	}
+
+	free(*line_p);
+	*line_p = G.pipeline.buf;
+	return altered;
+}
+
+/* Set command pointer to point to this label.  (Does not handle null label.) */
+static sed_cmd_t *branch_to(char *label)
+{
+	sed_cmd_t *sed_cmd;
+
+	for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
+		if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) {
+			return sed_cmd;
+		}
+	}
+	bb_error_msg_and_die("can't find label for jump to '%s'", label);
+}
+
+static void append(char *s)
+{
+	llist_add_to_end(&G.append_head, xstrdup(s));
+}
+
+static void flush_append(void)
+{
+	char *data;
+
+	/* Output appended lines. */
+	while ((data = (char *)llist_pop(&G.append_head))) {
+		fprintf(G.nonstdout, "%s\n", data);
+		free(data);
+	}
+}
+
+static void add_input_file(FILE *file)
+{
+	G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
+	G.input_file_list[G.input_file_count++] = file;
+}
+
+/* Get next line of input from G.input_file_list, flushing append buffer and
+ * noting if we ran out of files without a newline on the last line we read.
+ */
+enum {
+	NO_EOL_CHAR = 1,
+	LAST_IS_NUL = 2,
+};
+static char *get_next_line(char *gets_char)
+{
+	char *temp = NULL;
+	int len;
+	char gc;
+
+	flush_append();
+
+	/* will be returned if last line in the file
+	 * doesn't end with either '\n' or '\0' */
+	gc = NO_EOL_CHAR;
+	while (G.current_input_file < G.input_file_count) {
+		FILE *fp = G.input_file_list[G.current_input_file];
+		/* Read line up to a newline or NUL byte, inclusive,
+		 * return malloc'ed char[]. length of the chunk read
+		 * is stored in len. NULL if EOF/error */
+		temp = bb_get_chunk_from_file(fp, &len);
+		if (temp) {
+			/* len > 0 here, it's ok to do temp[len-1] */
+			char c = temp[len-1];
+			if (c == '\n' || c == '\0') {
+				temp[len-1] = '\0';
+				gc = c;
+				if (c == '\0') {
+					int ch = fgetc(fp);
+					if (ch != EOF)
+						ungetc(ch, fp);
+					else
+						gc = LAST_IS_NUL;
+				}
+			}
+			/* else we put NO_EOL_CHAR into *gets_char */
+			break;
+
+		/* NB: I had the idea of peeking next file(s) and returning
+		 * NO_EOL_CHAR only if it is the *last* non-empty
+		 * input file. But there is a case where this won't work:
+		 * file1: "a woo\nb woo"
+		 * file2: "c no\nd no"
+		 * sed -ne 's/woo/bang/p' input1 input2 => "a bang\nb bang"
+		 * (note: *no* newline after "b bang"!) */
+		}
+		/* Close this file and advance to next one */
+		fclose(fp);
+		G.current_input_file++;
+	}
+	*gets_char = gc;
+	return temp;
+}
+
+/* Output line of text. */
+/* Note:
+ * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
+ * Without them, we had this:
+ * echo -n thingy >z1
+ * echo -n again >z2
+ * >znull
+ * sed "s/i/z/" z1 z2 znull | hexdump -vC
+ * output:
+ * gnu sed 4.1.5:
+ * 00000000  74 68 7a 6e 67 79 0a 61  67 61 7a 6e              |thzngy.agazn|
+ * bbox:
+ * 00000000  74 68 7a 6e 67 79 61 67  61 7a 6e                 |thzngyagazn|
+ */
+static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
+{
+	char lpc = *last_puts_char;
+
+	/* Need to insert a '\n' between two files because first file's
+	 * last line wasn't terminated? */
+	if (lpc != '\n' && lpc != '\0') {
+		fputc('\n', file);
+		lpc = '\n';
+	}
+	fputs(s, file);
+
+	/* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
+	if (s[0])
+		lpc = 'x';
+
+	/* had trailing '\0' and it was last char of file? */
+	if (last_gets_char == LAST_IS_NUL) {
+		fputc('\0', file);
+		lpc = 'x'; /* */
+	} else
+	/* had trailing '\n' or '\0'? */
+	if (last_gets_char != NO_EOL_CHAR) {
+		fputc(last_gets_char, file);
+		lpc = last_gets_char;
+	}
+
+	if (ferror(file)) {
+		xfunc_error_retval = 4;  /* It's what gnu sed exits with... */
+		bb_error_msg_and_die(bb_msg_write_error);
+	}
+	*last_puts_char = lpc;
+}
+
+#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n))
+
+static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space)
+{
+	int retval = sed_cmd->beg_match && !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0);
+	if (retval)
+		G.previous_regex_ptr = sed_cmd->beg_match;
+	return retval;
+}
+
+/* Process all the lines in all the files */
+
+static void process_files(void)
+{
+	char *pattern_space, *next_line;
+	int linenum = 0;
+	char last_puts_char = '\n';
+	char last_gets_char, next_gets_char;
+	sed_cmd_t *sed_cmd;
+	int substituted;
+
+	/* Prime the pump */
+	next_line = get_next_line(&next_gets_char);
+
+	/* Go through every line in each file */
+ again:
+	substituted = 0;
+
+	/* Advance to next line.  Stop if out of lines. */
+	pattern_space = next_line;
+	if (!pattern_space)
+		return;
+	last_gets_char = next_gets_char;
+
+	/* Read one line in advance so we can act on the last line,
+	 * the '$' address */
+	next_line = get_next_line(&next_gets_char);
+	linenum++;
+
+	/* For every line, go through all the commands */
+ restart:
+	for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
+		int old_matched, matched;
+
+		old_matched = sed_cmd->in_match;
+
+		/* Determine if this command matches this line: */
+
+		dbg("match1:%d", sed_cmd->in_match);
+		dbg("match2:%d", (!sed_cmd->beg_line && !sed_cmd->end_line
+				&& !sed_cmd->beg_match && !sed_cmd->end_match));
+		dbg("match3:%d", (sed_cmd->beg_line > 0
+			&& (sed_cmd->end_line || sed_cmd->end_match
+			    ? (sed_cmd->beg_line <= linenum)
+			    : (sed_cmd->beg_line == linenum)
+			    )
+			));
+		dbg("match4:%d", (beg_match(sed_cmd, pattern_space)));
+		dbg("match5:%d", (sed_cmd->beg_line == -1 && next_line == NULL));
+
+		/* Are we continuing a previous multi-line match? */
+		sed_cmd->in_match = sed_cmd->in_match
+			/* Or is no range necessary? */
+			|| (!sed_cmd->beg_line && !sed_cmd->end_line
+				&& !sed_cmd->beg_match && !sed_cmd->end_match)
+			/* Or did we match the start of a numerical range? */
+			|| (sed_cmd->beg_line > 0
+			    && (sed_cmd->end_line || sed_cmd->end_match
+				  /* note: even if end is numeric and is < linenum too,
+				   * GNU sed matches! We match too, therefore we don't
+				   * check here that linenum <= end.
+				   * Example:
+				   * printf '1\n2\n3\n4\n' | sed -n '1{N;N;d};1p;2,3p;3p;4p'
+				   * first three input lines are deleted;
+				   * 4th line is matched and printed
+				   * by "2,3" (!) and by "4" ranges
+				   */
+				? (sed_cmd->beg_line <= linenum)    /* N,end */
+				: (sed_cmd->beg_line == linenum)    /* N */
+				)
+			    )
+			/* Or does this line match our begin address regex? */
+			|| (beg_match(sed_cmd, pattern_space))
+			/* Or did we match last line of input? */
+			|| (sed_cmd->beg_line == -1 && next_line == NULL);
+
+		/* Snapshot the value */
+		matched = sed_cmd->in_match;
+
+		dbg("cmd:'%c' matched:%d beg_line:%d end_line:%d linenum:%d",
+			sed_cmd->cmd, matched, sed_cmd->beg_line, sed_cmd->end_line, linenum);
+
+		/* Is this line the end of the current match? */
+
+		if (matched) {
+			/* once matched, "n,xxx" range is dead, disabling it */
+			if (sed_cmd->beg_line > 0) {
+				sed_cmd->beg_line = -2;
+			}
+			sed_cmd->in_match = !(
+				/* has the ending line come, or is this a single address command? */
+				(sed_cmd->end_line
+					? sed_cmd->end_line == -1
+						? !next_line
+						: (sed_cmd->end_line <= linenum)
+					: !sed_cmd->end_match
+				)
+				/* or does this line matches our last address regex */
+				|| (sed_cmd->end_match && old_matched
+				     && (regexec(sed_cmd->end_match,
+						pattern_space, 0, NULL, 0) == 0)
+				)
+			);
+		}
+
+		/* Skip blocks of commands we didn't match */
+		if (sed_cmd->cmd == '{') {
+			if (sed_cmd->invert ? matched : !matched) {
+				unsigned nest_cnt = 0;
+				while (1) {
+					if (sed_cmd->cmd == '{')
+						nest_cnt++;
+					if (sed_cmd->cmd == '}') {
+						nest_cnt--;
+						if (nest_cnt == 0)
+							break;
+					}
+					sed_cmd = sed_cmd->next;
+					if (!sed_cmd)
+						bb_error_msg_and_die("unterminated {");
+				}
+			}
+			continue;
+		}
+
+		/* Okay, so did this line match? */
+		if (sed_cmd->invert ? matched : !matched)
+			continue; /* no */
+
+		/* Update last used regex in case a blank substitute BRE is found */
+		if (sed_cmd->beg_match) {
+			G.previous_regex_ptr = sed_cmd->beg_match;
+		}
+
+		/* actual sedding */
+		dbg("pattern_space:'%s' next_line:'%s' cmd:%c",
+				pattern_space, next_line, sed_cmd->cmd);
+		switch (sed_cmd->cmd) {
+
+		/* Print line number */
+		case '=':
+			fprintf(G.nonstdout, "%d\n", linenum);
+			break;
+
+		/* Write the current pattern space up to the first newline */
+		case 'P':
+		{
+			char *tmp = strchr(pattern_space, '\n');
+			if (tmp) {
+				*tmp = '\0';
+				/* TODO: explain why '\n' below */
+				sed_puts(pattern_space, '\n');
+				*tmp = '\n';
+				break;
+			}
+			/* Fall Through */
+		}
+
+		/* Write the current pattern space to output */
+		case 'p':
+			/* NB: we print this _before_ the last line
+			 * (of current file) is printed. Even if
+			 * that line is nonterminated, we print
+			 * '\n' here (gnu sed does the same) */
+			sed_puts(pattern_space, '\n');
+			break;
+		/* Delete up through first newline */
+		case 'D':
+		{
+			char *tmp = strchr(pattern_space, '\n');
+			if (tmp) {
+				overlapping_strcpy(pattern_space, tmp + 1);
+				goto restart;
+			}
+		}
+		/* discard this line. */
+		case 'd':
+			goto discard_line;
+
+		/* Substitute with regex */
+		case 's':
+			if (!do_subst_command(sed_cmd, &pattern_space))
+				break;
+			dbg("do_subst_command succeeded:'%s'", pattern_space);
+			substituted |= 1;
+
+			/* handle p option */
+			if (sed_cmd->sub_p)
+				sed_puts(pattern_space, last_gets_char);
+			/* handle w option */
+			if (sed_cmd->sw_file)
+				puts_maybe_newline(
+					pattern_space, sed_cmd->sw_file,
+					&sed_cmd->sw_last_char, last_gets_char);
+			break;
+
+		/* Append line to linked list to be printed later */
+		case 'a':
+			append(sed_cmd->string);
+			break;
+
+		/* Insert text before this line */
+		case 'i':
+			sed_puts(sed_cmd->string, '\n');
+			break;
+
+		/* Cut and paste text (replace) */
+		case 'c':
+			/* Only triggers on last line of a matching range. */
+			if (!sed_cmd->in_match)
+				sed_puts(sed_cmd->string, '\n');
+			goto discard_line;
+
+		/* Read file, append contents to output */
+		case 'r':
+		{
+			FILE *rfile;
+			rfile = fopen_for_read(sed_cmd->string);
+			if (rfile) {
+				char *line;
+
+				while ((line = xmalloc_fgetline(rfile))
+						!= NULL)
+					append(line);
+				xprint_and_close_file(rfile);
+			}
+
+			break;
+		}
+
+		/* Write pattern space to file. */
+		case 'w':
+			puts_maybe_newline(
+				pattern_space, sed_cmd->sw_file,
+				&sed_cmd->sw_last_char, last_gets_char);
+			break;
+
+		/* Read next line from input */
+		case 'n':
+			if (!G.be_quiet)
+				sed_puts(pattern_space, last_gets_char);
+			if (next_line) {
+				free(pattern_space);
+				pattern_space = next_line;
+				last_gets_char = next_gets_char;
+				next_line = get_next_line(&next_gets_char);
+				substituted = 0;
+				linenum++;
+				break;
+			}
+			/* fall through */
+
+		/* Quit.  End of script, end of input. */
+		case 'q':
+			/* Exit the outer while loop */
+			free(next_line);
+			next_line = NULL;
+			goto discard_commands;
+
+		/* Append the next line to the current line */
+		case 'N':
+		{
+			int len;
+			/* If no next line, jump to end of script and exit. */
+			/* http://www.gnu.org/software/sed/manual/sed.html:
+			 * "Most versions of sed exit without printing anything
+			 * when the N command is issued on the last line of
+			 * a file. GNU sed prints pattern space before exiting
+			 * unless of course the -n command switch has been
+			 * specified. This choice is by design."
+			 */
+			if (next_line == NULL) {
+				//goto discard_line;
+				goto discard_commands; /* GNU behavior */
+			}
+			/* Append next_line, read new next_line. */
+			len = strlen(pattern_space);
+			pattern_space = xrealloc(pattern_space, len + strlen(next_line) + 2);
+			pattern_space[len] = '\n';
+			strcpy(pattern_space + len+1, next_line);
+			last_gets_char = next_gets_char;
+			next_line = get_next_line(&next_gets_char);
+			linenum++;
+			break;
+		}
+
+		/* Test/branch if substitution occurred */
+		case 't':
+			if (!substituted) break;
+			substituted = 0;
+			/* Fall through */
+		/* Test/branch if substitution didn't occur */
+		case 'T':
+			if (substituted) break;
+			/* Fall through */
+		/* Branch to label */
+		case 'b':
+			if (!sed_cmd->string) goto discard_commands;
+			else sed_cmd = branch_to(sed_cmd->string);
+			break;
+		/* Transliterate characters */
+		case 'y':
+		{
+			int i, j;
+			for (i = 0; pattern_space[i]; i++) {
+				for (j = 0; sed_cmd->string[j]; j += 2) {
+					if (pattern_space[i] == sed_cmd->string[j]) {
+						pattern_space[i] = sed_cmd->string[j + 1];
+						break;
+					}
+				}
+			}
+
+			break;
+		}
+		case 'g':	/* Replace pattern space with hold space */
+			free(pattern_space);
+			pattern_space = xstrdup(G.hold_space ? G.hold_space : "");
+			break;
+		case 'G':	/* Append newline and hold space to pattern space */
+		{
+			int pattern_space_size = 2;
+			int hold_space_size = 0;
+
+			if (pattern_space)
+				pattern_space_size += strlen(pattern_space);
+			if (G.hold_space)
+				hold_space_size = strlen(G.hold_space);
+			pattern_space = xrealloc(pattern_space,
+					pattern_space_size + hold_space_size);
+			if (pattern_space_size == 2)
+				pattern_space[0] = 0;
+			strcat(pattern_space, "\n");
+			if (G.hold_space)
+				strcat(pattern_space, G.hold_space);
+			last_gets_char = '\n';
+
+			break;
+		}
+		case 'h':	/* Replace hold space with pattern space */
+			free(G.hold_space);
+			G.hold_space = xstrdup(pattern_space);
+			break;
+		case 'H':	/* Append newline and pattern space to hold space */
+		{
+			int hold_space_size = 2;
+			int pattern_space_size = 0;
+
+			if (G.hold_space)
+				hold_space_size += strlen(G.hold_space);
+			if (pattern_space)
+				pattern_space_size = strlen(pattern_space);
+			G.hold_space = xrealloc(G.hold_space,
+					hold_space_size + pattern_space_size);
+
+			if (hold_space_size == 2)
+				*G.hold_space = 0;
+			strcat(G.hold_space, "\n");
+			if (pattern_space)
+				strcat(G.hold_space, pattern_space);
+
+			break;
+		}
+		case 'x': /* Exchange hold and pattern space */
+		{
+			char *tmp = pattern_space;
+			pattern_space = G.hold_space ? G.hold_space : xzalloc(1);
+			last_gets_char = '\n';
+			G.hold_space = tmp;
+			break;
+		}
+		} /* switch */
+	} /* for each cmd */
+
+	/*
+	 * Exit point from sedding...
+	 */
+ discard_commands:
+	/* we will print the line unless we were told to be quiet ('-n')
+	   or if the line was suppressed (ala 'd'elete) */
+	if (!G.be_quiet)
+		sed_puts(pattern_space, last_gets_char);
+
+	/* Delete and such jump here. */
+ discard_line:
+	flush_append();
+	free(pattern_space);
+
+	goto again;
+}
+
+/* It is possible to have a command line argument with embedded
+ * newlines.  This counts as multiple command lines.
+ * However, newline can be escaped: 's/e/z\<newline>z/'
+ * We check for this.
+ */
+
+static void add_cmd_block(char *cmdstr)
+{
+	char *sv, *eol;
+
+	cmdstr = sv = xstrdup(cmdstr);
+	do {
+		eol = strchr(cmdstr, '\n');
+ next:
+		if (eol) {
+			/* Count preceding slashes */
+			int slashes = 0;
+			char *sl = eol;
+
+			while (sl != cmdstr && *--sl == '\\')
+				slashes++;
+			/* Odd number of preceding slashes - newline is escaped */
+			if (slashes & 1) {
+				overlapping_strcpy(eol - 1, eol);
+				eol = strchr(eol, '\n');
+				goto next;
+			}
+			*eol = '\0';
+		}
+		add_cmd(cmdstr);
+		cmdstr = eol + 1;
+	} while (eol);
+	free(sv);
+}
+
+int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sed_main(int argc UNUSED_PARAM, char **argv)
+{
+	unsigned opt;
+	llist_t *opt_e, *opt_f;
+	char *opt_i;
+
+#if ENABLE_LONG_OPTS
+	static const char sed_longopts[] ALIGN1 =
+		/* name             has_arg             short */
+		"in-place\0"        Optional_argument   "i"
+		"regexp-extended\0" No_argument         "r"
+		"quiet\0"           No_argument         "n"
+		"silent\0"          No_argument         "n"
+		"expression\0"      Required_argument   "e"
+		"file\0"            Required_argument   "f";
+#endif
+
+	int status = EXIT_SUCCESS;
+
+	INIT_G();
+
+	/* destroy command strings on exit */
+	if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);
+
+	/* Lie to autoconf when it starts asking stupid questions. */
+	if (argv[1] && strcmp(argv[1], "--version") == 0) {
+		puts("This is not GNU sed version 4.0");
+		return 0;
+	}
+
+	/* do normal option parsing */
+	opt_e = opt_f = NULL;
+	opt_i = NULL;
+	opt_complementary = "e::f::" /* can occur multiple times */
+	                    "nn"; /* count -n */
+
+	IF_LONG_OPTS(applet_long_options = sed_longopts);
+
+	/* -i must be first, to match OPT_in_place definition */
+	opt = getopt32(argv, "i::rne:f:", &opt_i, &opt_e, &opt_f,
+			    &G.be_quiet); /* counter for -n */
+	//argc -= optind;
+	argv += optind;
+	if (opt & OPT_in_place) { // -i
+		atexit(cleanup_outname);
+	}
+	if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r
+	//if (opt & 0x4) G.be_quiet++; // -n
+	while (opt_e) { // -e
+		add_cmd_block(llist_pop(&opt_e));
+	}
+	while (opt_f) { // -f
+		char *line;
+		FILE *cmdfile;
+		cmdfile = xfopen_for_read(llist_pop(&opt_f));
+		while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
+			add_cmd(line);
+			free(line);
+		}
+		fclose(cmdfile);
+	}
+	/* if we didn't get a pattern from -e or -f, use argv[0] */
+	if (!(opt & 0x18)) {
+		if (!*argv)
+			bb_show_usage();
+		add_cmd_block(*argv++);
+	}
+	/* Flush any unfinished commands. */
+	add_cmd("");
+
+	/* By default, we write to stdout */
+	G.nonstdout = stdout;
+
+	/* argv[0..(argc-1)] should be names of file to process. If no
+	 * files were specified or '-' was specified, take input from stdin.
+	 * Otherwise, we process all the files specified. */
+	if (argv[0] == NULL) {
+		if (opt & OPT_in_place)
+			bb_error_msg_and_die(bb_msg_requires_arg, "-i");
+		add_input_file(stdin);
+	} else {
+		int i;
+
+		for (i = 0; argv[i]; i++) {
+			struct stat statbuf;
+			int nonstdoutfd;
+			FILE *file;
+			sed_cmd_t *sed_cmd;
+
+			if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) {
+				add_input_file(stdin);
+				process_files();
+				continue;
+			}
+			file = fopen_or_warn(argv[i], "r");
+			if (!file) {
+				status = EXIT_FAILURE;
+				continue;
+			}
+			add_input_file(file);
+			if (!(opt & OPT_in_place)) {
+				continue;
+			}
+
+			/* -i: process each FILE separately: */
+
+			G.outname = xasprintf("%sXXXXXX", argv[i]);
+			nonstdoutfd = xmkstemp(G.outname);
+			G.nonstdout = xfdopen_for_write(nonstdoutfd);
+
+			/* Set permissions/owner of output file */
+			fstat(fileno(file), &statbuf);
+			/* chmod'ing AFTER chown would preserve suid/sgid bits,
+			 * but GNU sed 4.2.1 does not preserve them either */
+			fchmod(nonstdoutfd, statbuf.st_mode);
+			fchown(nonstdoutfd, statbuf.st_uid, statbuf.st_gid);
+
+			process_files();
+			fclose(G.nonstdout);
+			G.nonstdout = stdout;
+
+			if (opt_i) {
+				char *backupname = xasprintf("%s%s", argv[i], opt_i);
+				xrename(argv[i], backupname);
+				free(backupname);
+			}
+			/* else unlink(argv[i]); - rename below does this */
+			xrename(G.outname, argv[i]); //TODO: rollback backup on error?
+			free(G.outname);
+			G.outname = NULL;
+
+			/* Re-enable disabled range matches */
+			for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
+				sed_cmd->beg_line = sed_cmd->beg_line_orig;
+			}
+		}
+		/* Here, to handle "sed 'cmds' nonexistent_file" case we did:
+		 * if (G.current_input_file >= G.input_file_count)
+		 *	return status;
+		 * but it's not needed since process_files() works correctly
+		 * in this case too. */
+	}
+	process_files();
+
+	return status;
+}
diff --git a/ap/app/busybox/src/editors/sed1line.txt b/ap/app/busybox/src/editors/sed1line.txt
new file mode 100644
index 0000000..11a2e36
--- /dev/null
+++ b/ap/app/busybox/src/editors/sed1line.txt
@@ -0,0 +1,425 @@
+http://www.student.northpark.edu/pemente/sed/sed1line.txt
+-------------------------------------------------------------------------
+HANDY ONE-LINERS FOR SED (Unix stream editor)               Apr. 26, 2004
+compiled by Eric Pement - pemente[at]northpark[dot]edu        version 5.4
+Latest version of this file is usually at:
+   http://sed.sourceforge.net/sed1line.txt
+   http://www.student.northpark.edu/pemente/sed/sed1line.txt
+This file is also available in Portuguese at:
+   http://www.lrv.ufsc.br/wmaker/sed_ptBR.html
+
+FILE SPACING:
+
+ # double space a file
+ sed G
+
+ # double space a file which already has blank lines in it. Output file
+ # should contain no more than one blank line between lines of text.
+ sed '/^$/d;G'
+
+ # triple space a file
+ sed 'G;G'
+
+ # undo double-spacing (assumes even-numbered lines are always blank)
+ sed 'n;d'
+
+ # insert a blank line above every line which matches "regex"
+ sed '/regex/{x;p;x;}'
+
+ # insert a blank line below every line which matches "regex"
+ sed '/regex/G'
+
+ # insert a blank line above and below every line which matches "regex"
+ sed '/regex/{x;p;x;G;}'
+
+NUMBERING:
+
+ # number each line of a file (simple left alignment). Using a tab (see
+ # note on '\t' at end of file) instead of space will preserve margins.
+ sed = filename | sed 'N;s/\n/\t/'
+
+ # number each line of a file (number on left, right-aligned)
+ sed = filename | sed 'N; s/^/     /; s/ *\(.\{6,\}\)\n/\1  /'
+
+ # number each line of file, but only print numbers if line is not blank
+ sed '/./=' filename | sed '/./N; s/\n/ /'
+
+ # count lines (emulates "wc -l")
+ sed -n '$='
+
+TEXT CONVERSION AND SUBSTITUTION:
+
+ # IN UNIX ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format
+ sed 's/.$//'               # assumes that all lines end with CR/LF
+ sed 's/^M$//'              # in bash/tcsh, press Ctrl-V then Ctrl-M
+ sed 's/\x0D$//'            # gsed 3.02.80, but top script is easier
+
+ # IN UNIX ENVIRONMENT: convert Unix newlines (LF) to DOS format
+ sed "s/$/`echo -e \\\r`/"            # command line under ksh
+ sed 's/$'"/`echo \\\r`/"             # command line under bash
+ sed "s/$/`echo \\\r`/"               # command line under zsh
+ sed 's/$/\r/'                        # gsed 3.02.80
+
+ # IN DOS ENVIRONMENT: convert Unix newlines (LF) to DOS format
+ sed "s/$//"                          # method 1
+ sed -n p                             # method 2
+
+ # IN DOS ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format
+ # Can only be done with UnxUtils sed, version 4.0.7 or higher.
+ # Cannot be done with other DOS versions of sed. Use "tr" instead.
+ sed "s/\r//" infile >outfile         # UnxUtils sed v4.0.7 or higher
+ tr -d \r <infile >outfile            # GNU tr version 1.22 or higher
+
+ # delete leading whitespace (spaces, tabs) from front of each line
+ # aligns all text flush left
+ sed 's/^[ \t]*//'                    # see note on '\t' at end of file
+
+ # delete trailing whitespace (spaces, tabs) from end of each line
+ sed 's/[ \t]*$//'                    # see note on '\t' at end of file
+
+ # delete BOTH leading and trailing whitespace from each line
+ sed 's/^[ \t]*//;s/[ \t]*$//'
+
+ # insert 5 blank spaces at beginning of each line (make page offset)
+ sed 's/^/     /'
+
+ # align all text flush right on a 79-column width
+ sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # set at 78 plus 1 space
+
+ # center all text in the middle of 79-column width. In method 1,
+ # spaces at the beginning of the line are significant, and trailing
+ # spaces are appended at the end of the line. In method 2, spaces at
+ # the beginning of the line are discarded in centering the line, and
+ # no trailing spaces appear at the end of lines.
+ sed  -e :a -e 's/^.\{1,77\}$/ & /;ta'                     # method 1
+ sed  -e :a -e 's/^.\{1,77\}$/ &/;ta' -e 's/\( *\)\1/\1/'  # method 2
+
+ # substitute (find and replace) "foo" with "bar" on each line
+ sed 's/foo/bar/'             # replaces only 1st instance in a line
+ sed 's/foo/bar/4'            # replaces only 4th instance in a line
+ sed 's/foo/bar/g'            # replaces ALL instances in a line
+ sed 's/\(.*\)foo\(.*foo\)/\1bar\2/' # replace the next-to-last case
+ sed 's/\(.*\)foo/\1bar/'            # replace only the last case
+
+ # substitute "foo" with "bar" ONLY for lines which contain "baz"
+ sed '/baz/s/foo/bar/g'
+
+ # substitute "foo" with "bar" EXCEPT for lines which contain "baz"
+ sed '/baz/!s/foo/bar/g'
+
+ # change "scarlet" or "ruby" or "puce" to "red"
+ sed 's/scarlet/red/g;s/ruby/red/g;s/puce/red/g'   # most seds
+ gsed 's/scarlet\|ruby\|puce/red/g'                # GNU sed only
+
+ # reverse order of lines (emulates "tac")
+ # bug/feature in HHsed v1.5 causes blank lines to be deleted
+ sed '1!G;h;$!d'               # method 1
+ sed -n '1!G;h;$p'             # method 2
+
+ # reverse each character on the line (emulates "rev")
+ sed '/\n/!G;s/\(.\)\(.*\n\)/&\2\1/;//D;s/.//'
+
+ # join pairs of lines side-by-side (like "paste")
+ sed '$!N;s/\n/ /'
+
+ # if a line ends with a backslash, append the next line to it
+ sed -e :a -e '/\\$/N; s/\\\n//; ta'
+
+ # if a line begins with an equal sign, append it to the previous line
+ # and replace the "=" with a single space
+ sed -e :a -e '$!N;s/\n=/ /;ta' -e 'P;D'
+
+ # add commas to numeric strings, changing "1234567" to "1,234,567"
+ gsed ':a;s/\B[0-9]\{3\}\>/,&/;ta'                     # GNU sed
+ sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta'  # other seds
+
+ # add commas to numbers with decimal points and minus signs (GNU sed)
+ gsed ':a;s/\(^\|[^0-9.]\)\([0-9]\+\)\([0-9]\{3\}\)/\1\2,\3/g;ta'
+
+ # add a blank line every 5 lines (after lines 5, 10, 15, 20, etc.)
+ gsed '0~5G'                  # GNU sed only
+ sed 'n;n;n;n;G;'             # other seds
+
+SELECTIVE PRINTING OF CERTAIN LINES:
+
+ # print first 10 lines of file (emulates behavior of "head")
+ sed 10q
+
+ # print first line of file (emulates "head -1")
+ sed q
+
+ # print the last 10 lines of a file (emulates "tail")
+ sed -e :a -e '$q;N;11,$D;ba'
+
+ # print the last 2 lines of a file (emulates "tail -2")
+ sed '$!N;$!D'
+
+ # print the last line of a file (emulates "tail -1")
+ sed '$!d'                    # method 1
+ sed -n '$p'                  # method 2
+
+ # print only lines which match regular expression (emulates "grep")
+ sed -n '/regexp/p'           # method 1
+ sed '/regexp/!d'             # method 2
+
+ # print only lines which do NOT match regexp (emulates "grep -v")
+ sed -n '/regexp/!p'          # method 1, corresponds to above
+ sed '/regexp/d'              # method 2, simpler syntax
+
+ # print the line immediately before a regexp, but not the line
+ # containing the regexp
+ sed -n '/regexp/{g;1!p;};h'
+
+ # print the line immediately after a regexp, but not the line
+ # containing the regexp
+ sed -n '/regexp/{n;p;}'
+
+ # print 1 line of context before and after regexp, with line number
+ # indicating where the regexp occurred (similar to "grep -A1 -B1")
+ sed -n -e '/regexp/{=;x;1!p;g;$!N;p;D;}' -e h
+
+ # grep for AAA and BBB and CCC (in any order)
+ sed '/AAA/!d; /BBB/!d; /CCC/!d'
+
+ # grep for AAA and BBB and CCC (in that order)
+ sed '/AAA.*BBB.*CCC/!d'
+
+ # grep for AAA or BBB or CCC (emulates "egrep")
+ sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d    # most seds
+ gsed '/AAA\|BBB\|CCC/!d'                        # GNU sed only
+
+ # print paragraph if it contains AAA (blank lines separate paragraphs)
+ # HHsed v1.5 must insert a 'G;' after 'x;' in the next 3 scripts below
+ sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;'
+
+ # print paragraph if it contains AAA and BBB and CCC (in any order)
+ sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;/BBB/!d;/CCC/!d'
+
+ # print paragraph if it contains AAA or BBB or CCC
+ sed -e '/./{H;$!d;}' -e 'x;/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d
+ gsed '/./{H;$!d;};x;/AAA\|BBB\|CCC/b;d'         # GNU sed only
+
+ # print only lines of 65 characters or longer
+ sed -n '/^.\{65\}/p'
+
+ # print only lines of less than 65 characters
+ sed -n '/^.\{65\}/!p'        # method 1, corresponds to above
+ sed '/^.\{65\}/d'            # method 2, simpler syntax
+
+ # print section of file from regular expression to end of file
+ sed -n '/regexp/,$p'
+
+ # print section of file based on line numbers (lines 8-12, inclusive)
+ sed -n '8,12p'               # method 1
+ sed '8,12!d'                 # method 2
+
+ # print line number 52
+ sed -n '52p'                 # method 1
+ sed '52!d'                   # method 2
+ sed '52q;d'                  # method 3, efficient on large files
+
+ # beginning at line 3, print every 7th line
+ gsed -n '3~7p'               # GNU sed only
+ sed -n '3,${p;n;n;n;n;n;n;}' # other seds
+
+ # print section of file between two regular expressions (inclusive)
+ sed -n '/Iowa/,/Montana/p'             # case sensitive
+
+SELECTIVE DELETION OF CERTAIN LINES:
+
+ # print all of file EXCEPT section between 2 regular expressions
+ sed '/Iowa/,/Montana/d'
+
+ # delete duplicate, consecutive lines from a file (emulates "uniq").
+ # First line in a set of duplicate lines is kept, rest are deleted.
+ sed '$!N; /^\(.*\)\n\1$/!P; D'
+
+ # delete duplicate, nonconsecutive lines from a file. Beware not to
+ # overflow the buffer size of the hold space, or else use GNU sed.
+ sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
+
+ # delete all lines except duplicate lines (emulates "uniq -d").
+ sed '$!N; s/^\(.*\)\n\1$/\1/; t; D'
+
+ # delete the first 10 lines of a file
+ sed '1,10d'
+
+ # delete the last line of a file
+ sed '$d'
+
+ # delete the last 2 lines of a file
+ sed 'N;$!P;$!D;$d'
+
+ # delete the last 10 lines of a file
+ sed -e :a -e '$d;N;2,10ba' -e 'P;D'   # method 1
+ sed -n -e :a -e '1,10!{P;N;D;};N;ba'  # method 2
+
+ # delete every 8th line
+ gsed '0~8d'                           # GNU sed only
+ sed 'n;n;n;n;n;n;n;d;'                # other seds
+
+ # delete ALL blank lines from a file (same as "grep '.' ")
+ sed '/^$/d'                           # method 1
+ sed '/./!d'                           # method 2
+
+ # delete all CONSECUTIVE blank lines from file except the first; also
+ # deletes all blank lines from top and end of file (emulates "cat -s")
+ sed '/./,/^$/!d'          # method 1, allows 0 blanks at top, 1 at EOF
+ sed '/^$/N;/\n$/D'        # method 2, allows 1 blank at top, 0 at EOF
+
+ # delete all CONSECUTIVE blank lines from file except the first 2:
+ sed '/^$/N;/\n$/N;//D'
+
+ # delete all leading blank lines at top of file
+ sed '/./,$!d'
+
+ # delete all trailing blank lines at end of file
+ sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'  # works on all seds
+ sed -e :a -e '/^\n*$/N;/\n$/ba'        # ditto, except for gsed 3.02*
+
+ # delete the last line of each paragraph
+ sed -n '/^$/{p;h;};/./{x;/./p;}'
+
+SPECIAL APPLICATIONS:
+
+ # remove nroff overstrikes (char, backspace) from man pages. The 'echo'
+ # command may need an -e switch if you use Unix System V or bash shell.
+ sed "s/.`echo \\\b`//g"    # double quotes required for Unix environment
+ sed 's/.^H//g'             # in bash/tcsh, press Ctrl-V and then Ctrl-H
+ sed 's/.\x08//g'           # hex expression for sed v1.5
+
+ # get Usenet/e-mail message header
+ sed '/^$/q'                # deletes everything after first blank line
+
+ # get Usenet/e-mail message body
+ sed '1,/^$/d'              # deletes everything up to first blank line
+
+ # get Subject header, but remove initial "Subject: " portion
+ sed '/^Subject: */!d; s///;q'
+
+ # get return address header
+ sed '/^Reply-To:/q; /^From:/h; /./d;g;q'
+
+ # parse out the address proper. Pulls out the e-mail address by itself
+ # from the 1-line return address header (see preceding script)
+ sed 's/ *(.*)//; s/>.*//; s/.*[:<] *//'
+
+ # add a leading angle bracket and space to each line (quote a message)
+ sed 's/^/> /'
+
+ # delete leading angle bracket & space from each line (unquote a message)
+ sed 's/^> //'
+
+ # remove most HTML tags (accommodates multiple-line tags)
+ sed -e :a -e 's/<[^>]*>//g;/</N;//ba'
+
+ # extract multi-part uuencoded binaries, removing extraneous header
+ # info, so that only the uuencoded portion remains. Files passed to
+ # sed must be passed in the proper order. Version 1 can be entered
+ # from the command line; version 2 can be made into an executable
+ # Unix shell script. (Modified from a script by Rahul Dhesi.)
+ sed '/^end/,/^begin/d' file1 file2 ... fileX | uudecode   # vers. 1
+ sed '/^end/,/^begin/d' "$@" | uudecode                    # vers. 2
+
+ # zip up each .TXT file individually, deleting the source file and
+ # setting the name of each .ZIP file to the basename of the .TXT file
+ # (under DOS: the "dir /b" switch returns bare filenames in all caps).
+ echo @echo off >zipup.bat
+ dir /b *.txt | sed "s/^\(.*\)\.TXT/pkzip -mo \1 \1.TXT/" >>zipup.bat
+
+TYPICAL USE: Sed takes one or more editing commands and applies all of
+them, in sequence, to each line of input. After all the commands have
+been applied to the first input line, that line is output and a second
+input line is taken for processing, and the cycle repeats. The
+preceding examples assume that input comes from the standard input
+device (i.e, the console, normally this will be piped input). One or
+more filenames can be appended to the command line if the input does
+not come from stdin. Output is sent to stdout (the screen). Thus:
+
+ cat filename | sed '10q'        # uses piped input
+ sed '10q' filename              # same effect, avoids a useless "cat"
+ sed '10q' filename > newfile    # redirects output to disk
+
+For additional syntax instructions, including the way to apply editing
+commands from a disk file instead of the command line, consult "sed &
+awk, 2nd Edition," by Dale Dougherty and Arnold Robbins (O'Reilly,
+1997; http://www.ora.com), "UNIX Text Processing," by Dale Dougherty
+and Tim O'Reilly (Hayden Books, 1987) or the tutorials by Mike Arst
+distributed in U-SEDIT2.ZIP (many sites). To fully exploit the power
+of sed, one must understand "regular expressions." For this, see
+"Mastering Regular Expressions" by Jeffrey Friedl (O'Reilly, 1997).
+The manual ("man") pages on Unix systems may be helpful (try "man
+sed", "man regexp", or the subsection on regular expressions in "man
+ed"), but man pages are notoriously difficult. They are not written to
+teach sed use or regexps to first-time users, but as a reference text
+for those already acquainted with these tools.
+
+QUOTING SYNTAX: The preceding examples use single quotes ('...')
+instead of double quotes ("...") to enclose editing commands, since
+sed is typically used on a Unix platform. Single quotes prevent the
+Unix shell from intrepreting the dollar sign ($) and backquotes
+(`...`), which are expanded by the shell if they are enclosed in
+double quotes. Users of the "csh" shell and derivatives will also need
+to quote the exclamation mark (!) with the backslash (i.e., \!) to
+properly run the examples listed above, even within single quotes.
+Versions of sed written for DOS invariably require double quotes
+("...") instead of single quotes to enclose editing commands.
+
+USE OF '\t' IN SED SCRIPTS: For clarity in documentation, we have used
+the expression '\t' to indicate a tab character (0x09) in the scripts.
+However, most versions of sed do not recognize the '\t' abbreviation,
+so when typing these scripts from the command line, you should press
+the TAB key instead. '\t' is supported as a regular expression
+metacharacter in awk, perl, and HHsed, sedmod, and GNU sed v3.02.80.
+
+VERSIONS OF SED: Versions of sed do differ, and some slight syntax
+variation is to be expected. In particular, most do not support the
+use of labels (:name) or branch instructions (b,t) within editing
+commands, except at the end of those commands. We have used the syntax
+which will be portable to most users of sed, even though the popular
+GNU versions of sed allow a more succinct syntax. When the reader sees
+a fairly long command such as this:
+
+   sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d
+
+it is heartening to know that GNU sed will let you reduce it to:
+
+   sed '/AAA/b;/BBB/b;/CCC/b;d'      # or even
+   sed '/AAA\|BBB\|CCC/b;d'
+
+In addition, remember that while many versions of sed accept a command
+like "/one/ s/RE1/RE2/", some do NOT allow "/one/! s/RE1/RE2/", which
+contains space before the 's'. Omit the space when typing the command.
+
+OPTIMIZING FOR SPEED: If execution speed needs to be increased (due to
+large input files or slow processors or hard disks), substitution will
+be executed more quickly if the "find" expression is specified before
+giving the "s/.../.../" instruction. Thus:
+
+   sed 's/foo/bar/g' filename         # standard replace command
+   sed '/foo/ s/foo/bar/g' filename   # executes more quickly
+   sed '/foo/ s//bar/g' filename      # shorthand sed syntax
+
+On line selection or deletion in which you only need to output lines
+from the first part of the file, a "quit" command (q) in the script
+will drastically reduce processing time for large files. Thus:
+
+   sed -n '45,50p' filename           # print line nos. 45-50 of a file
+   sed -n '51q;45,50p' filename       # same, but executes much faster
+
+If you have any additional scripts to contribute or if you find errors
+in this document, please send e-mail to the compiler. Indicate the
+version of sed you used, the operating system it was compiled for, and
+the nature of the problem. Various scripts in this file were written
+or contributed by:
+
+ Al Aab <af137@freenet.toronto.on.ca>   # "seders" list moderator
+ Edgar Allen <era@sky.net>              # various
+ Yiorgos Adamopoulos <adamo@softlab.ece.ntua.gr>
+ Dale Dougherty <dale@songline.com>     # author of "sed & awk"
+ Carlos Duarte <cdua@algos.inesc.pt>    # author of "do it with sed"
+ Eric Pement <pemente@northpark.edu>    # author of this document
+ Ken Pizzini <ken@halcyon.com>          # author of GNU sed v3.02
+ S.G. Ravenhall <stew.ravenhall@totalise.co.uk> # great de-html script
+ Greg Ubben <gsu@romulus.ncsc.mil>      # many contributions & much help
+-------------------------------------------------------------------------
diff --git a/ap/app/busybox/src/editors/sed_summary.htm b/ap/app/busybox/src/editors/sed_summary.htm
new file mode 100644
index 0000000..34e72b0
--- /dev/null
+++ b/ap/app/busybox/src/editors/sed_summary.htm
@@ -0,0 +1,223 @@
+<html>
+
+<head><title>Command Summary for sed (sed & awk, Second Edition)</title>
+</head>
+
+<body>
+
+<h2>Command Summary for sed</h2>
+
+<dl>
+
+<dt><b>: </b> <b> :</b><em>label</em></dt>
+<dd>Label a line in the script for the transfer of control by
+<b>b</b> or <b>t</b>.
+<em>label</em> may contain up to seven characters.
+(The POSIX standard says that an implementation can allow longer
+labels if it wishes to. GNU sed allows labels to be of any length.)
+</p></dd>
+
+
+<dt><b>=</b> [<em>address</em>]<b>=</b></dt>
+<dd>Write to standard output the line number of addressed line.</p></dd>
+
+
+<dt><b>a</b> [<em>address</em>]<b>a\</b></dt>
+<dd><em>text</em></p>
+
+<p>Append <em>text</em>
+following each line matched by <em>address</em>.  If
+<em>text</em> goes over more than one line, newlines
+must be "hidden" by preceding them with a backslash.  The
+<em>text</em> will be terminated by the first
+newline that is not hidden in this way.  The
+<em>text</em> is not available in the pattern space
+and subsequent commands cannot be applied to it.  The results of this
+command are sent to standard output when the list of editing commands
+is finished, regardless of what happens to the current line in the
+pattern space.</p></dd>
+
+
+<dt><b>b</b> [<em>address1</em>[,<em>address2</em>]]<b>b</b>[<em>label</em>]</dt>
+<dd>Transfer control unconditionally (branch) to
+<b>:</b><em>label</em> elsewhere in
+script.  That is, the command following the
+<em>label</em> is the next command applied to the
+current line.  If no <em>label</em> is specified,
+control falls through to the end of the script, so no more commands
+are applied to the current line.</p></dd>
+
+
+<dt><b>c</b> [<em>address1</em>[,<em>address2</em>]]<b>c\</b></dt>
+<dd><em>text</em></p>
+
+<p>Replace (change) the lines selected by the address with
+<em>text</em>.  When a range of lines is specified,
+all lines as a group are replaced by a single copy of
+<em>text</em>.  The newline following each line of
+<em>text</em> must be escaped by a backslash, except
+the last line.  The contents of the pattern space are, in effect,
+deleted and no subsequent editing commands can be applied to it (or to
+<em>text</em>).</p></dd>
+
+
+<dt><b>d</b> [<em>address1</em>[,<em>address2</em>]]<b>d</b></dt>
+<dd>Delete line(s) from pattern space.  Thus, the line is not passed to standard
+output. A new line of input is read and editing resumes with first
+command in script.</p></dd>
+
+
+<dt><b>D</b> [<em>address1</em>[,<em>address2</em>]]<b>D</b></dt>
+<dd>Delete first part (up to embedded newline) of multiline pattern space created
+by <b>N</b> command and resume editing with first command in
+script.  If this command empties the pattern space, then a new line
+of input is read, as if the <b>d</b> command had been executed.</p></dd>
+
+
+<dt><b>g</b> [<em>address1</em>[,<em>address2</em>]]<b>g</b></dt>
+<dd>Copy (get) contents of hold space (see <b>h</b> or
+<b>H</b> command) into the pattern space, wiping out
+previous contents.</p></dd>
+
+
+<dt><b>G</b> [<em>address1</em>[,<em>address2</em>]]<b>G</b></dt>
+<dd>Append newline followed by contents of hold space (see
+<b>h</b> or <b>H</b> command) to contents of
+the pattern space.  If hold space is empty, a newline is still
+appended to the pattern space.</p></dd>
+
+
+<dt><b>h</b> [<em>address1</em>[,<em>address2</em>]]<b>h</b></dt>
+<dd>Copy pattern space into hold space, a special temporary buffer.
+Previous contents of hold space are wiped out.</p></dd>
+
+
+<dt><b>H</b> [<em>address1</em>[,<em>address2</em>]]<b>H</b></dt>
+<dd>Append newline and contents of pattern space to contents of the hold
+space.  Even if hold space is empty, this command still appends the
+newline first.</p></dd>
+
+
+<dt><b>i</b> [<em>address1</em>]<b>i\</b></dt>
+<dd><em>text</em></p>
+
+<p>Insert <em>text</em> before each line matched by
+<em>address</em>. (See <b>a</b> for
+details on <em>text</em>.)</p></dd>
+
+
+<dt><b>l</b> [<em>address1</em>[,<em>address2</em>]]<b>l</b></dt>
+<dd>List the contents of the pattern space, showing nonprinting characters
+as ASCII codes.  Long lines are wrapped.</p></dd>
+
+
+<dt><b>n</b> [<em>address1</em>[,<em>address2</em>]]<b>n</b></dt>
+<dd>Read next line of input into pattern space.  Current line is sent to
+standard output.  New line becomes current line and increments line
+counter.  Control passes to command following <b>n</b>
+instead of resuming at the top of the script.</p></dd>
+
+
+<dt><b>N</b> [<em>address1</em>[,<em>address2</em>]]<b>N</b></dt>
+<dd>Append next input line to contents of pattern space; the new line is
+separated from the previous contents of the pattern space by a newline.
+(This command is designed to allow pattern matches across two
+lines.  Using \n to match the embedded newline, you can match
+patterns across multiple lines.)</p></dd>
+
+
+<dt><b>p</b> [<em>address1</em>[,<em>address2</em>]]<b>p</b></dt>
+<dd>Print the addressed line(s).  Note that this can result in duplicate
+output unless default output is suppressed by using "#n" or
+the <span class="option">-n</span>
+
+command-line option.  Typically used before commands that change flow
+control (<b>d</b>, <b>n</b>,
+<b>b</b>) and might prevent the current line from being
+output.</p></dd>
+
+
+<dt><b>P</b> [<em>address1</em>[,<em>address2</em>]]<b>P</b></dt>
+<dd>Print first part (up to embedded newline) of multiline pattern space
+created by <b>N</b> command.  Same as <b>p</b>
+if <b>N</b> has not been applied to a line.</p></dd>
+
+
+<dt><b>q</b> [<em>address</em>]<b>q</b></dt>
+<dd>Quit when <em>address</em> is encountered.  The
+addressed line is first written to output (if default output is not
+suppressed), along with any text appended to it by previous
+<b>a</b> or <b>r</b> commands.</p></dd>
+
+
+<dt><b>r</b> [<em>address</em>]<b>r</b> <em>file</em></dt>
+<dd>Read contents of <em>file</em> and append after the
+contents of the pattern space.  Exactly one space must be put between
+<b>r</b> and the filename.</p></dd>
+
+
+<dt><b>s</b> [<em>address1</em>[,<em>address2</em>]]<b>s</b>/<em>pattern</em>/<em>replacement</em>/[<em>flags</em>]</dt>
+<dd>Substitute <em>replacement</em> for
+<em>pattern</em> on each addressed line.  If pattern
+addresses are used, the pattern <b>//</b> represents the
+last pattern address specified.  The following flags can be specified:</p>
+
+	<dl>
+
+	<dt><b>n</b></dt>
+	<dd>Replace <em>n</em>th instance of
+	/<em>pattern</em>/ on each addressed line.
+	<em>n</em> is any number in the range 1 to 512, and
+	the default is 1.</p></dd>
+
+	<dt><b>g</b></dt>
+	<dd>Replace all instances of /<em>pattern</em>/ on each
+	addressed line, not just the first instance.</p></dd>
+
+	<dt><b>I</b></dt>
+	<dd>Matching is case-insensitive.<p></p></dd>
+
+	<dt><b>p</b></dt>
+	<dd>Print the line if a successful substitution is done.  If several
+	successful substitutions are done, multiple copies of the line will be
+	printed.</p></dd>
+
+	<dt><b>w</b> <em>file</em></dt>
+	<dd>Write the line to <em>file</em> if a replacement
+	was done.  A maximum of 10 different <em>files</em> can be opened.</p></dd>
+
+	</dl>
+
+</dd>
+
+
+<dt><b>t</b> [<em>address1</em>[,<em>address2</em>]]<b>t </b>[<em>label</em>]</dt>
+<dd>Test if successful substitutions have been made on addressed lines,
+and if so, branch to line marked by :<em>label</em>.
+(See <b>b</b> and <b>:</b>.)  If label is not
+specified, control falls through to bottom of script.</p></dd>
+
+
+<dt><b>w</b> [<em>address1</em>[,<em>address2</em>]]<b>w</b> <em>file</em></dt>
+<dd>Append contents of pattern space to <em>file</em>.
+This action occurs when the command is encountered rather than when
+the pattern space is output.  Exactly one space must separate the
+<b>w</b> and the filename.  A maximum of 10 different
+files can be opened in a script.  This command will create the file if
+it does not exist; if the file exists, its contents will be
+overwritten each time the script is executed.  Multiple write commands
+that direct output to the same file append to the end of the file.</p></dd>
+
+
+<dt><b>x</b> [<em>address1</em>[,<em>address2</em>]]<b>x</b></dt>
+<dd>Exchange contents of the pattern space with the contents of the hold
+space.</p></dd>
+
+
+<dt><b>y</b> [<em>address1</em>[,<em>address2</em>]]<b>y</b>/<em>abc</em>/<em>xyz</em>/</dt>
+<dd>Transform each character by position in string
+<em>abc</em> to its equivalent in string
+<em>xyz</em>.</p></dd>
+
+
+</dl>
diff --git a/ap/app/busybox/src/editors/vi.c b/ap/app/busybox/src/editors/vi.c
new file mode 100644
index 0000000..5b5e2b0
--- /dev/null
+++ b/ap/app/busybox/src/editors/vi.c
@@ -0,0 +1,4098 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tiny vi.c: A small 'vi' clone
+ * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+/*
+ * Things To Do:
+ *	EXINIT
+ *	$HOME/.exrc  and  ./.exrc
+ *	add magic to search	/foo.*bar
+ *	add :help command
+ *	:map macros
+ *	if mark[] values were line numbers rather than pointers
+ *      it would be easier to change the mark when add/delete lines
+ *	More intelligence in refresh()
+ *	":r !cmd"  and  "!cmd"  to filter text through an external command
+ *	A true "undo" facility
+ *	An "ex" line oriented mode- maybe using "cmdedit"
+ */
+
+//config:config VI
+//config:	bool "vi"
+//config:	default y
+//config:	help
+//config:	  'vi' is a text editor. More specifically, it is the One True
+//config:	  text editor <grin>. It does, however, have a rather steep
+//config:	  learning curve. If you are not already comfortable with 'vi'
+//config:	  you may wish to use something else.
+//config:
+//config:config FEATURE_VI_MAX_LEN
+//config:	int "Maximum screen width in vi"
+//config:	range 256 16384
+//config:	default 4096
+//config:	depends on VI
+//config:	help
+//config:	  Contrary to what you may think, this is not eating much.
+//config:	  Make it smaller than 4k only if you are very limited on memory.
+//config:
+//config:config FEATURE_VI_8BIT
+//config:	bool "Allow vi to display 8-bit chars (otherwise shows dots)"
+//config:	default n
+//config:	depends on VI
+//config:	help
+//config:	  If your terminal can display characters with high bit set,
+//config:	  you may want to enable this. Note: vi is not Unicode-capable.
+//config:	  If your terminal combines several 8-bit bytes into one character
+//config:	  (as in Unicode mode), this will not work properly.
+//config:
+//config:config FEATURE_VI_COLON
+//config:	bool "Enable \":\" colon commands (no \"ex\" mode)"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Enable a limited set of colon commands for vi. This does not
+//config:	  provide an "ex" mode.
+//config:
+//config:config FEATURE_VI_YANKMARK
+//config:	bool "Enable yank/put commands and mark cmds"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  This will enable you to use yank and put, as well as mark in
+//config:	  busybox vi.
+//config:
+//config:config FEATURE_VI_SEARCH
+//config:	bool "Enable search and replace cmds"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Select this if you wish to be able to do search and replace in
+//config:	  busybox vi.
+//config:
+//config:config FEATURE_VI_REGEX_SEARCH
+//config:	bool "Enable regex in search and replace"
+//config:	default n   # Uses GNU regex, which may be unavailable. FIXME
+//config:	depends on FEATURE_VI_SEARCH
+//config:	help
+//config:	  Use extended regex search.
+//config:
+//config:config FEATURE_VI_USE_SIGNALS
+//config:	bool "Catch signals"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Selecting this option will make busybox vi signal aware. This will
+//config:	  make busybox vi support SIGWINCH to deal with Window Changes, catch
+//config:	  Ctrl-Z and Ctrl-C and alarms.
+//config:
+//config:config FEATURE_VI_DOT_CMD
+//config:	bool "Remember previous cmd and \".\" cmd"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Make busybox vi remember the last command and be able to repeat it.
+//config:
+//config:config FEATURE_VI_READONLY
+//config:	bool "Enable -R option and \"view\" mode"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Enable the read-only command line option, which allows the user to
+//config:	  open a file in read-only mode.
+//config:
+//config:config FEATURE_VI_SETOPTS
+//config:	bool "Enable set-able options, ai ic showmatch"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Enable the editor to set some (ai, ic, showmatch) options.
+//config:
+//config:config FEATURE_VI_SET
+//config:	bool "Support for :set"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Support for ":set".
+//config:
+//config:config FEATURE_VI_WIN_RESIZE
+//config:	bool "Handle window resize"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  Make busybox vi behave nicely with terminals that get resized.
+//config:
+//config:config FEATURE_VI_ASK_TERMINAL
+//config:	bool "Use 'tell me cursor position' ESC sequence to measure window"
+//config:	default y
+//config:	depends on VI
+//config:	help
+//config:	  If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
+//config:	  this option makes vi perform a last-ditch effort to find it:
+//config:	  position cursor to 999,999 and ask terminal to report real
+//config:	  cursor position using "ESC [ 6 n" escape sequence, then read stdin.
+//config:
+//config:	  This is not clean but helps a lot on serial lines and such.
+
+//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_VI) += vi.o
+
+//usage:#define vi_trivial_usage
+//usage:       "[OPTIONS] [FILE]..."
+//usage:#define vi_full_usage "\n\n"
+//usage:       "Edit FILE\n"
+//usage:	IF_FEATURE_VI_COLON(
+//usage:     "\n	-c CMD	Initial command to run ($EXINIT also available)"
+//usage:	)
+//usage:	IF_FEATURE_VI_READONLY(
+//usage:     "\n	-R	Read-only"
+//usage:	)
+//usage:     "\n	-H	List available features"
+
+#include "libbb.h"
+/* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
+#if ENABLE_FEATURE_VI_REGEX_SEARCH
+# include <regex.h>
+#endif
+
+/* the CRASHME code is unmaintained, and doesn't currently build */
+#define ENABLE_FEATURE_VI_CRASHME 0
+
+
+#if ENABLE_LOCALE_SUPPORT
+
+#if ENABLE_FEATURE_VI_8BIT
+//FIXME: this does not work properly for Unicode anyway
+# define Isprint(c) (isprint)(c)
+#else
+# define Isprint(c) isprint_asciionly(c)
+#endif
+
+#else
+
+/* 0x9b is Meta-ESC */
+#if ENABLE_FEATURE_VI_8BIT
+# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
+#else
+# define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
+#endif
+
+#endif
+
+
+enum {
+	MAX_TABSTOP = 32, // sanity limit
+	// User input len. Need not be extra big.
+	// Lines in file being edited *can* be bigger than this.
+	MAX_INPUT_LEN = 128,
+	// Sanity limits. We have only one buffer of this size.
+	MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
+	MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
+};
+
+/* VT102 ESC sequences.
+ * See "Xterm Control Sequences"
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ */
+/* Inverse/Normal text */
+#define ESC_BOLD_TEXT "\033[7m"
+#define ESC_NORM_TEXT "\033[0m"
+/* Bell */
+#define ESC_BELL "\007"
+/* Clear-to-end-of-line */
+#define ESC_CLEAR2EOL "\033[K"
+/* Clear-to-end-of-screen.
+ * (We use default param here.
+ * Full sequence is "ESC [ <num> J",
+ * <num> is 0/1/2 = "erase below/above/all".)
+ */
+#define ESC_CLEAR2EOS "\033[J"
+/* Cursor to given coordinate (1,1: top left) */
+#define ESC_SET_CURSOR_POS "\033[%u;%uH"
+//UNUSED
+///* Cursor up and down */
+//#define ESC_CURSOR_UP "\033[A"
+//#define ESC_CURSOR_DOWN "\n"
+
+#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
+// cmds modifying text[]
+// vda: removed "aAiIs" as they switch us into insert mode
+// and remembering input for replay after them makes no sense
+static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
+#endif
+
+enum {
+	YANKONLY = FALSE,
+	YANKDEL = TRUE,
+	FORWARD = 1,	// code depends on "1"  for array index
+	BACK = -1,	// code depends on "-1" for array index
+	LIMITED = 0,	// how much of text[] in char_search
+	FULL = 1,	// how much of text[] in char_search
+
+	S_BEFORE_WS = 1,	// used in skip_thing() for moving "dot"
+	S_TO_WS = 2,		// used in skip_thing() for moving "dot"
+	S_OVER_WS = 3,		// used in skip_thing() for moving "dot"
+	S_END_PUNCT = 4,	// used in skip_thing() for moving "dot"
+	S_END_ALNUM = 5,	// used in skip_thing() for moving "dot"
+};
+
+
+/* vi.c expects chars to be unsigned. */
+/* busybox build system provides that, but it's better */
+/* to audit and fix the source */
+
+struct globals {
+	/* many references - keep near the top of globals */
+	char *text, *end;       // pointers to the user data in memory
+	char *dot;              // where all the action takes place
+	int text_size;		// size of the allocated buffer
+
+	/* the rest */
+	smallint vi_setops;
+#define VI_AUTOINDENT 1
+#define VI_SHOWMATCH  2
+#define VI_IGNORECASE 4
+#define VI_ERR_METHOD 8
+#define autoindent (vi_setops & VI_AUTOINDENT)
+#define showmatch  (vi_setops & VI_SHOWMATCH )
+#define ignorecase (vi_setops & VI_IGNORECASE)
+/* indicate error with beep or flash */
+#define err_method (vi_setops & VI_ERR_METHOD)
+
+#if ENABLE_FEATURE_VI_READONLY
+	smallint readonly_mode;
+#define SET_READONLY_FILE(flags)        ((flags) |= 0x01)
+#define SET_READONLY_MODE(flags)        ((flags) |= 0x02)
+#define UNSET_READONLY_FILE(flags)      ((flags) &= 0xfe)
+#else
+#define SET_READONLY_FILE(flags)        ((void)0)
+#define SET_READONLY_MODE(flags)        ((void)0)
+#define UNSET_READONLY_FILE(flags)      ((void)0)
+#endif
+
+	smallint editing;        // >0 while we are editing a file
+	                         // [code audit says "can be 0, 1 or 2 only"]
+	smallint cmd_mode;       // 0=command  1=insert 2=replace
+	int file_modified;       // buffer contents changed (counter, not flag!)
+	int last_file_modified;  // = -1;
+	int save_argc;           // how many file names on cmd line
+	int cmdcnt;              // repetition count
+	unsigned rows, columns;	 // the terminal screen is this size
+#if ENABLE_FEATURE_VI_ASK_TERMINAL
+	int get_rowcol_error;
+#endif
+	int crow, ccol;          // cursor is on Crow x Ccol
+	int offset;              // chars scrolled off the screen to the left
+	int have_status_msg;     // is default edit status needed?
+	                         // [don't make smallint!]
+	int last_status_cksum;   // hash of current status line
+	char *current_filename;
+	char *screenbegin;       // index into text[], of top line on the screen
+	char *screen;            // pointer to the virtual screen buffer
+	int screensize;          //            and its size
+	int tabstop;
+	int last_forward_char;   // last char searched for with 'f' (int because of Unicode)
+	char erase_char;         // the users erase character
+	char last_input_char;    // last char read from user
+
+#if ENABLE_FEATURE_VI_DOT_CMD
+	smallint adding2q;	 // are we currently adding user input to q
+	int lmc_len;             // length of last_modifying_cmd
+	char *ioq, *ioq_start;   // pointer to string for get_one_char to "read"
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
+	int my_pid;
+#endif
+#if ENABLE_FEATURE_VI_SEARCH
+	char *last_search_pattern; // last pattern from a '/' or '?' search
+#endif
+
+	/* former statics */
+#if ENABLE_FEATURE_VI_YANKMARK
+	char *edit_file__cur_line;
+#endif
+	int refresh__old_offset;
+	int format_edit_status__tot;
+
+	/* a few references only */
+#if ENABLE_FEATURE_VI_YANKMARK
+	int YDreg, Ureg;        // default delete register and orig line for "U"
+	char *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
+	char *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
+	char *context_start, *context_end;
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+	sigjmp_buf restart;     // catch_sig()
+#endif
+	struct termios term_orig, term_vi; // remember what the cooked mode was
+#if ENABLE_FEATURE_VI_COLON
+	char *initial_cmds[3];  // currently 2 entries, NULL terminated
+#endif
+	// Should be just enough to hold a key sequence,
+	// but CRASHME mode uses it as generated command buffer too
+#if ENABLE_FEATURE_VI_CRASHME
+	char readbuffer[128];
+#else
+	char readbuffer[KEYCODE_BUFFER_SIZE];
+#endif
+#define STATUS_BUFFER_LEN  200
+	char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
+#if ENABLE_FEATURE_VI_DOT_CMD
+	char last_modifying_cmd[MAX_INPUT_LEN];	// last modifying cmd for "."
+#endif
+	char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
+
+	char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
+};
+#define G (*ptr_to_globals)
+#define text           (G.text          )
+#define text_size      (G.text_size     )
+#define end            (G.end           )
+#define dot            (G.dot           )
+#define reg            (G.reg           )
+
+#define vi_setops               (G.vi_setops          )
+#define editing                 (G.editing            )
+#define cmd_mode                (G.cmd_mode           )
+#define file_modified           (G.file_modified      )
+#define last_file_modified      (G.last_file_modified )
+#define save_argc               (G.save_argc          )
+#define cmdcnt                  (G.cmdcnt             )
+#define rows                    (G.rows               )
+#define columns                 (G.columns            )
+#define crow                    (G.crow               )
+#define ccol                    (G.ccol               )
+#define offset                  (G.offset             )
+#define status_buffer           (G.status_buffer      )
+#define have_status_msg         (G.have_status_msg    )
+#define last_status_cksum       (G.last_status_cksum  )
+#define current_filename        (G.current_filename   )
+#define screen                  (G.screen             )
+#define screensize              (G.screensize         )
+#define screenbegin             (G.screenbegin        )
+#define tabstop                 (G.tabstop            )
+#define last_forward_char       (G.last_forward_char  )
+#define erase_char              (G.erase_char         )
+#define last_input_char         (G.last_input_char    )
+#if ENABLE_FEATURE_VI_READONLY
+#define readonly_mode           (G.readonly_mode      )
+#else
+#define readonly_mode           0
+#endif
+#define adding2q                (G.adding2q           )
+#define lmc_len                 (G.lmc_len            )
+#define ioq                     (G.ioq                )
+#define ioq_start               (G.ioq_start          )
+#define my_pid                  (G.my_pid             )
+#define last_search_pattern     (G.last_search_pattern)
+
+#define edit_file__cur_line     (G.edit_file__cur_line)
+#define refresh__old_offset     (G.refresh__old_offset)
+#define format_edit_status__tot (G.format_edit_status__tot)
+
+#define YDreg          (G.YDreg         )
+#define Ureg           (G.Ureg          )
+#define mark           (G.mark          )
+#define context_start  (G.context_start )
+#define context_end    (G.context_end   )
+#define restart        (G.restart       )
+#define term_orig      (G.term_orig     )
+#define term_vi        (G.term_vi       )
+#define initial_cmds   (G.initial_cmds  )
+#define readbuffer     (G.readbuffer    )
+#define scr_out_buf    (G.scr_out_buf   )
+#define last_modifying_cmd  (G.last_modifying_cmd )
+#define get_input_line__buf (G.get_input_line__buf)
+
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+	last_file_modified = -1; \
+	/* "" but has space for 2 chars: */ \
+	IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
+} while (0)
+
+
+static int init_text_buffer(char *); // init from file or create new
+static void edit_file(char *);	// edit one file
+static void do_cmd(int);	// execute a command
+static int next_tabstop(int);
+static void sync_cursor(char *, int *, int *);	// synchronize the screen cursor to dot
+static char *begin_line(char *);	// return pointer to cur line B-o-l
+static char *end_line(char *);	// return pointer to cur line E-o-l
+static char *prev_line(char *);	// return pointer to prev line B-o-l
+static char *next_line(char *);	// return pointer to next line B-o-l
+static char *end_screen(void);	// get pointer to last char on screen
+static int count_lines(char *, char *);	// count line from start to stop
+static char *find_line(int);	// find begining of line #li
+static char *move_to_col(char *, int);	// move "p" to column l
+static void dot_left(void);	// move dot left- dont leave line
+static void dot_right(void);	// move dot right- dont leave line
+static void dot_begin(void);	// move dot to B-o-l
+static void dot_end(void);	// move dot to E-o-l
+static void dot_next(void);	// move dot to next line B-o-l
+static void dot_prev(void);	// move dot to prev line B-o-l
+static void dot_scroll(int, int);	// move the screen up or down
+static void dot_skip_over_ws(void);	// move dot pat WS
+static void dot_delete(void);	// delete the char at 'dot'
+static char *bound_dot(char *);	// make sure  text[0] <= P < "end"
+static char *new_screen(int, int);	// malloc virtual screen memory
+static char *char_insert(char *, char);	// insert the char c at 'p'
+// might reallocate text[]! use p += stupid_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t stupid_insert(char *, char);	// stupidly insert the char c at 'p'
+static int find_range(char **, char **, char);	// return pointers for an object
+static int st_test(char *, int, int, char *);	// helper for skip_thing()
+static char *skip_thing(char *, int, int, int);	// skip some object
+static char *find_pair(char *, char);	// find matching pair ()  []  {}
+static char *text_hole_delete(char *, char *);	// at "p", delete a 'size' byte hole
+// might reallocate text[]! use p += text_hole_make(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t text_hole_make(char *, int);	// at "p", make a 'size' byte hole
+static char *yank_delete(char *, char *, int, int);	// yank text[] into register then delete
+static void show_help(void);	// display some help info
+static void rawmode(void);	// set "raw" mode on tty
+static void cookmode(void);	// return to "cooked" mode on tty
+// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
+static int mysleep(int);
+static int readit(void);	// read (maybe cursor) key from stdin
+static int get_one_char(void);	// read 1 char from stdin
+static int file_size(const char *);   // what is the byte size of "fn"
+#if !ENABLE_FEATURE_VI_READONLY
+#define file_insert(fn, p, update_ro_status) file_insert(fn, p)
+#endif
+// file_insert might reallocate text[]!
+static int file_insert(const char *, char *, int);
+static int file_write(char *, char *, char *);
+static void place_cursor(int, int);
+static void screen_erase(void);
+static void clear_to_eol(void);
+static void clear_to_eos(void);
+static void go_bottom_and_clear_to_eol(void);
+static void standout_start(void);	// send "start reverse video" sequence
+static void standout_end(void);	// send "end reverse video" sequence
+static void flash(int);		// flash the terminal screen
+static void show_status_line(void);	// put a message on the bottom line
+static void status_line(const char *, ...);     // print to status buf
+static void status_line_bold(const char *, ...);
+static void not_implemented(const char *); // display "Not implemented" message
+static int format_edit_status(void);	// format file status on status line
+static void redraw(int);	// force a full screen refresh
+static char* format_line(char* /*, int*/);
+static void refresh(int);	// update the terminal from screen[]
+
+static void Indicate_Error(void);       // use flash or beep to indicate error
+#define indicate_error(c) Indicate_Error()
+static void Hit_Return(void);
+
+#if ENABLE_FEATURE_VI_SEARCH
+static char *char_search(char *, const char *, int, int);	// search for pattern starting at p
+#endif
+#if ENABLE_FEATURE_VI_COLON
+static char *get_one_address(char *, int *);	// get colon addr, if present
+static char *get_address(char *, int *, int *);	// get two colon addrs, if present
+static void colon(char *);	// execute the "colon" mode cmds
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int);	// catch window size changes
+static void suspend_sig(int);	// catch ctrl-Z
+static void catch_sig(int);     // catch ctrl-C and alarm time-outs
+#endif
+#if ENABLE_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(char);	// new queue for command
+static void end_cmd_q(void);	// stop saving input chars
+#else
+#define end_cmd_q() ((void)0)
+#endif
+#if ENABLE_FEATURE_VI_SETOPTS
+static void showmatching(char *);	// show the matching pair ()  []  {}
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
+// might reallocate text[]! use p += string_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t string_insert(char *, const char *);	// insert the string at 'p'
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK
+static char *text_yank(char *, char *, int);	// save copy of "p" into a register
+static char what_reg(void);		// what is letter of current YDreg
+static void check_context(char);	// remember context for '' command
+#endif
+#if ENABLE_FEATURE_VI_CRASHME
+static void crash_dummy();
+static void crash_test();
+static int crashme = 0;
+#endif
+
+
+static void write1(const char *out)
+{
+	fputs(out, stdout);
+}
+
+int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int vi_main(int argc, char **argv)
+{
+	int c;
+
+	INIT_G();
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
+	my_pid = getpid();
+#endif
+#if ENABLE_FEATURE_VI_CRASHME
+	srand((long) my_pid);
+#endif
+#ifdef NO_SUCH_APPLET_YET
+	/* If we aren't "vi", we are "view" */
+	if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
+		SET_READONLY_MODE(readonly_mode);
+	}
+#endif
+
+	// autoindent is not default in vim 7.3
+	vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
+	//  1-  process $HOME/.exrc file (not inplemented yet)
+	//  2-  process EXINIT variable from environment
+	//  3-  process command line args
+#if ENABLE_FEATURE_VI_COLON
+	{
+		char *p = getenv("EXINIT");
+		if (p && *p)
+			initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
+	}
+#endif
+	while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
+		switch (c) {
+#if ENABLE_FEATURE_VI_CRASHME
+		case 'C':
+			crashme = 1;
+			break;
+#endif
+#if ENABLE_FEATURE_VI_READONLY
+		case 'R':		// Read-only flag
+			SET_READONLY_MODE(readonly_mode);
+			break;
+#endif
+#if ENABLE_FEATURE_VI_COLON
+		case 'c':		// cmd line vi command
+			if (*optarg)
+				initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
+			break;
+#endif
+		case 'H':
+			show_help();
+			/* fall through */
+		default:
+			bb_show_usage();
+			return 1;
+		}
+	}
+
+	// The argv array can be used by the ":next"  and ":rewind" commands
+	argv += optind;
+	argc -= optind;
+
+	//----- This is the main file handling loop --------------
+	save_argc = argc;
+	optind = 0;
+	// "Save cursor, use alternate screen buffer, clear screen"
+	write1("\033[?1049h");
+	while (1) {
+		edit_file(argv[optind]); /* param might be NULL */
+		if (++optind >= argc)
+			break;
+	}
+	// "Use normal screen buffer, restore cursor"
+	write1("\033[?1049l");
+	//-----------------------------------------------------------
+
+	return 0;
+}
+
+/* read text from file or create an empty buf */
+/* will also update current_filename */
+static int init_text_buffer(char *fn)
+{
+	int rc;
+	int size = file_size(fn);	// file size. -1 means does not exist.
+
+	/* allocate/reallocate text buffer */
+	free(text);
+	text_size = size + 10240;
+	screenbegin = dot = end = text = xzalloc(text_size);
+
+	if (fn != current_filename) {
+		free(current_filename);
+		current_filename = xstrdup(fn);
+	}
+	if (size < 0) {
+		// file dont exist. Start empty buf with dummy line
+		char_insert(text, '\n');
+		rc = 0;
+	} else {
+		rc = file_insert(fn, text, 1);
+	}
+	file_modified = 0;
+	last_file_modified = -1;
+#if ENABLE_FEATURE_VI_YANKMARK
+	/* init the marks. */
+	memset(mark, 0, sizeof(mark));
+#endif
+	return rc;
+}
+
+#if ENABLE_FEATURE_VI_WIN_RESIZE
+static int query_screen_dimensions(void)
+{
+	int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
+	if (rows > MAX_SCR_ROWS)
+		rows = MAX_SCR_ROWS;
+	if (columns > MAX_SCR_COLS)
+		columns = MAX_SCR_COLS;
+	return err;
+}
+#else
+# define query_screen_dimensions() (0)
+#endif
+
+static void edit_file(char *fn)
+{
+#if ENABLE_FEATURE_VI_YANKMARK
+#define cur_line edit_file__cur_line
+#endif
+	int c;
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+	int sig;
+#endif
+
+	editing = 1;	// 0 = exit, 1 = one file, 2 = multiple files
+	rawmode();
+	rows = 24;
+	columns = 80;
+	IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
+#if ENABLE_FEATURE_VI_ASK_TERMINAL
+	if (G.get_rowcol_error /* TODO? && no input on stdin */) {
+		uint64_t k;
+		write1("\033[999;999H" "\033[6n");
+		fflush_all();
+		k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
+		if ((int32_t)k == KEYCODE_CURSOR_POS) {
+			uint32_t rc = (k >> 32);
+			columns = (rc & 0x7fff);
+			if (columns > MAX_SCR_COLS)
+				columns = MAX_SCR_COLS;
+			rows = ((rc >> 16) & 0x7fff);
+			if (rows > MAX_SCR_ROWS)
+				rows = MAX_SCR_ROWS;
+		}
+	}
+#endif
+	new_screen(rows, columns);	// get memory for virtual screen
+	init_text_buffer(fn);
+
+#if ENABLE_FEATURE_VI_YANKMARK
+	YDreg = 26;			// default Yank/Delete reg
+	Ureg = 27;			// hold orig line for "U" cmd
+	mark[26] = mark[27] = text;	// init "previous context"
+#endif
+
+	last_forward_char = last_input_char = '\0';
+	crow = 0;
+	ccol = 0;
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+	signal(SIGINT, catch_sig);
+	signal(SIGWINCH, winch_sig);
+	signal(SIGTSTP, suspend_sig);
+	sig = sigsetjmp(restart, 1);
+	if (sig != 0) {
+		screenbegin = dot = text;
+	}
+#endif
+
+	cmd_mode = 0;		// 0=command  1=insert  2='R'eplace
+	cmdcnt = 0;
+	tabstop = 8;
+	offset = 0;			// no horizontal offset
+	c = '\0';
+#if ENABLE_FEATURE_VI_DOT_CMD
+	free(ioq_start);
+	ioq = ioq_start = NULL;
+	lmc_len = 0;
+	adding2q = 0;
+#endif
+
+#if ENABLE_FEATURE_VI_COLON
+	{
+		char *p, *q;
+		int n = 0;
+
+		while ((p = initial_cmds[n]) != NULL) {
+			do {
+				q = p;
+				p = strchr(q, '\n');
+				if (p)
+					while (*p == '\n')
+						*p++ = '\0';
+				if (*q)
+					colon(q);
+			} while (p);
+			free(initial_cmds[n]);
+			initial_cmds[n] = NULL;
+			n++;
+		}
+	}
+#endif
+	redraw(FALSE);			// dont force every col re-draw
+	//------This is the main Vi cmd handling loop -----------------------
+	while (editing > 0) {
+#if ENABLE_FEATURE_VI_CRASHME
+		if (crashme > 0) {
+			if ((end - text) > 1) {
+				crash_dummy();	// generate a random command
+			} else {
+				crashme = 0;
+				string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n"); // insert the string
+				dot = text;
+				refresh(FALSE);
+			}
+		}
+#endif
+		last_input_char = c = get_one_char();	// get a cmd from user
+#if ENABLE_FEATURE_VI_YANKMARK
+		// save a copy of the current line- for the 'U" command
+		if (begin_line(dot) != cur_line) {
+			cur_line = begin_line(dot);
+			text_yank(begin_line(dot), end_line(dot), Ureg);
+		}
+#endif
+#if ENABLE_FEATURE_VI_DOT_CMD
+		// These are commands that change text[].
+		// Remember the input for the "." command
+		if (!adding2q && ioq_start == NULL
+		 && cmd_mode == 0 // command mode
+		 && c > '\0' // exclude NUL and non-ASCII chars
+		 && c < 0x7f // (Unicode and such)
+		 && strchr(modifying_cmds, c)
+		) {
+			start_new_cmd_q(c);
+		}
+#endif
+		do_cmd(c);		// execute the user command
+
+		// poll to see if there is input already waiting. if we are
+		// not able to display output fast enough to keep up, skip
+		// the display update until we catch up with input.
+		if (!readbuffer[0] && mysleep(0) == 0) {
+			// no input pending - so update output
+			refresh(FALSE);
+			show_status_line();
+		}
+#if ENABLE_FEATURE_VI_CRASHME
+		if (crashme > 0)
+			crash_test();	// test editor variables
+#endif
+	}
+	//-------------------------------------------------------------------
+
+	go_bottom_and_clear_to_eol();
+	cookmode();
+#undef cur_line
+}
+
+//----- The Colon commands -------------------------------------
+#if ENABLE_FEATURE_VI_COLON
+static char *get_one_address(char *p, int *addr)	// get colon addr, if present
+{
+	int st;
+	char *q;
+	IF_FEATURE_VI_YANKMARK(char c;)
+	IF_FEATURE_VI_SEARCH(char *pat;)
+
+	*addr = -1;			// assume no addr
+	if (*p == '.') {	// the current line
+		p++;
+		q = begin_line(dot);
+		*addr = count_lines(text, q);
+	}
+#if ENABLE_FEATURE_VI_YANKMARK
+	else if (*p == '\'') {	// is this a mark addr
+		p++;
+		c = tolower(*p);
+		p++;
+		if (c >= 'a' && c <= 'z') {
+			// we have a mark
+			c = c - 'a';
+			q = mark[(unsigned char) c];
+			if (q != NULL) {	// is mark valid
+				*addr = count_lines(text, q);
+			}
+		}
+	}
+#endif
+#if ENABLE_FEATURE_VI_SEARCH
+	else if (*p == '/') {	// a search pattern
+		q = strchrnul(++p, '/');
+		pat = xstrndup(p, q - p); // save copy of pattern
+		p = q;
+		if (*p == '/')
+			p++;
+		q = char_search(dot, pat, FORWARD, FULL);
+		if (q != NULL) {
+			*addr = count_lines(text, q);
+		}
+		free(pat);
+	}
+#endif
+	else if (*p == '$') {	// the last line in file
+		p++;
+		q = begin_line(end - 1);
+		*addr = count_lines(text, q);
+	} else if (isdigit(*p)) {	// specific line number
+		sscanf(p, "%d%n", addr, &st);
+		p += st;
+	} else {
+		// unrecognized address - assume -1
+		*addr = -1;
+	}
+	return p;
+}
+
+static char *get_address(char *p, int *b, int *e)	// get two colon addrs, if present
+{
+	//----- get the address' i.e., 1,3   'a,'b  -----
+	// get FIRST addr, if present
+	while (isblank(*p))
+		p++;				// skip over leading spaces
+	if (*p == '%') {			// alias for 1,$
+		p++;
+		*b = 1;
+		*e = count_lines(text, end-1);
+		goto ga0;
+	}
+	p = get_one_address(p, b);
+	while (isblank(*p))
+		p++;
+	if (*p == ',') {			// is there a address separator
+		p++;
+		while (isblank(*p))
+			p++;
+		// get SECOND addr, if present
+		p = get_one_address(p, e);
+	}
+ ga0:
+	while (isblank(*p))
+		p++;				// skip over trailing spaces
+	return p;
+}
+
+#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
+static void setops(const char *args, const char *opname, int flg_no,
+			const char *short_opname, int opt)
+{
+	const char *a = args + flg_no;
+	int l = strlen(opname) - 1; /* opname have + ' ' */
+
+	// maybe strncmp? we had tons of erroneous strncasecmp's...
+	if (strncasecmp(a, opname, l) == 0
+	 || strncasecmp(a, short_opname, 2) == 0
+	) {
+		if (flg_no)
+			vi_setops &= ~opt;
+		else
+			vi_setops |= opt;
+	}
+}
+#endif
+
+// buf must be no longer than MAX_INPUT_LEN!
+static void colon(char *buf)
+{
+	char c, *orig_buf, *buf1, *q, *r;
+	char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
+	int i, l, li, ch, b, e;
+	int useforce, forced = FALSE;
+
+	// :3154	// if (-e line 3154) goto it  else stay put
+	// :4,33w! foo	// write a portion of buffer to file "foo"
+	// :w		// write all of buffer to current file
+	// :q		// quit
+	// :q!		// quit- dont care about modified file
+	// :'a,'z!sort -u   // filter block through sort
+	// :'f		// goto mark "f"
+	// :'fl		// list literal the mark "f" line
+	// :.r bar	// read file "bar" into buffer before dot
+	// :/123/,/abc/d    // delete lines from "123" line to "abc" line
+	// :/xyz/	// goto the "xyz" line
+	// :s/find/replace/ // substitute pattern "find" with "replace"
+	// :!<cmd>	// run <cmd> then return
+	//
+
+	if (!buf[0])
+		goto ret;
+	if (*buf == ':')
+		buf++;			// move past the ':'
+
+	li = ch = i = 0;
+	b = e = -1;
+	q = text;			// assume 1,$ for the range
+	r = end - 1;
+	li = count_lines(text, end - 1);
+	fn = current_filename;
+
+	// look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
+	buf = get_address(buf, &b, &e);
+
+	// remember orig command line
+	orig_buf = buf;
+
+	// get the COMMAND into cmd[]
+	buf1 = cmd;
+	while (*buf != '\0') {
+		if (isspace(*buf))
+			break;
+		*buf1++ = *buf++;
+	}
+	*buf1 = '\0';
+	// get any ARGuments
+	while (isblank(*buf))
+		buf++;
+	strcpy(args, buf);
+	useforce = FALSE;
+	buf1 = last_char_is(cmd, '!');
+	if (buf1) {
+		useforce = TRUE;
+		*buf1 = '\0';   // get rid of !
+	}
+	if (b >= 0) {
+		// if there is only one addr, then the addr
+		// is the line number of the single line the
+		// user wants. So, reset the end
+		// pointer to point at end of the "b" line
+		q = find_line(b);	// what line is #b
+		r = end_line(q);
+		li = 1;
+	}
+	if (e >= 0) {
+		// we were given two addrs.  change the
+		// end pointer to the addr given by user.
+		r = find_line(e);	// what line is #e
+		r = end_line(r);
+		li = e - b + 1;
+	}
+	// ------------ now look for the command ------------
+	i = strlen(cmd);
+	if (i == 0) {		// :123CR goto line #123
+		if (b >= 0) {
+			dot = find_line(b);	// what line is #b
+			dot_skip_over_ws();
+		}
+	}
+#if ENABLE_FEATURE_ALLOW_EXEC
+	else if (cmd[0] == '!') {	// run a cmd
+		int retcode;
+		// :!ls   run the <cmd>
+		go_bottom_and_clear_to_eol();
+		cookmode();
+		retcode = system(orig_buf + 1);	// run the cmd
+		if (retcode)
+			printf("\nshell returned %i\n\n", retcode);
+		rawmode();
+		Hit_Return();			// let user see results
+	}
+#endif
+	else if (cmd[0] == '=' && !cmd[1]) {	// where is the address
+		if (b < 0) {	// no addr given- use defaults
+			b = e = count_lines(text, dot);
+		}
+		status_line("%d", b);
+	} else if (strncmp(cmd, "delete", i) == 0) {	// delete lines
+		if (b < 0) {	// no addr given- use defaults
+			q = begin_line(dot);	// assume .,. for the range
+			r = end_line(dot);
+		}
+		dot = yank_delete(q, r, 1, YANKDEL);	// save, then delete lines
+		dot_skip_over_ws();
+	} else if (strncmp(cmd, "edit", i) == 0) {	// Edit a file
+		// don't edit, if the current file has been modified
+		if (file_modified && !useforce) {
+			status_line_bold("No write since last change (:%s! overrides)", cmd);
+			goto ret;
+		}
+		if (args[0]) {
+			// the user supplied a file name
+			fn = args;
+		} else if (current_filename && current_filename[0]) {
+			// no user supplied name- use the current filename
+			// fn = current_filename;  was set by default
+		} else {
+			// no user file name, no current name- punt
+			status_line_bold("No current filename");
+			goto ret;
+		}
+
+		if (init_text_buffer(fn) < 0)
+			goto ret;
+
+#if ENABLE_FEATURE_VI_YANKMARK
+		if (Ureg >= 0 && Ureg < 28) {
+			free(reg[Ureg]);	//   free orig line reg- for 'U'
+			reg[Ureg] = NULL;
+		}
+		if (YDreg >= 0 && YDreg < 28) {
+			free(reg[YDreg]);	//   free default yank/delete register
+			reg[YDreg] = NULL;
+		}
+#endif
+		// how many lines in text[]?
+		li = count_lines(text, end - 1);
+		status_line("\"%s\"%s"
+			IF_FEATURE_VI_READONLY("%s")
+			" %dL, %dC", current_filename,
+			(file_size(fn) < 0 ? " [New file]" : ""),
+			IF_FEATURE_VI_READONLY(
+				((readonly_mode) ? " [Readonly]" : ""),
+			)
+			li, ch);
+	} else if (strncmp(cmd, "file", i) == 0) {	// what File is this
+		if (b != -1 || e != -1) {
+			status_line_bold("No address allowed on this command");
+			goto ret;
+		}
+		if (args[0]) {
+			// user wants a new filename
+			free(current_filename);
+			current_filename = xstrdup(args);
+		} else {
+			// user wants file status info
+			last_status_cksum = 0;	// force status update
+		}
+	} else if (strncmp(cmd, "features", i) == 0) {	// what features are available
+		// print out values of all features
+		go_bottom_and_clear_to_eol();
+		cookmode();
+		show_help();
+		rawmode();
+		Hit_Return();
+	} else if (strncmp(cmd, "list", i) == 0) {	// literal print line
+		if (b < 0) {	// no addr given- use defaults
+			q = begin_line(dot);	// assume .,. for the range
+			r = end_line(dot);
+		}
+		go_bottom_and_clear_to_eol();
+		puts("\r");
+		for (; q <= r; q++) {
+			int c_is_no_print;
+
+			c = *q;
+			c_is_no_print = (c & 0x80) && !Isprint(c);
+			if (c_is_no_print) {
+				c = '.';
+				standout_start();
+			}
+			if (c == '\n') {
+				write1("$\r");
+			} else if (c < ' ' || c == 127) {
+				bb_putchar('^');
+				if (c == 127)
+					c = '?';
+				else
+					c += '@';
+			}
+			bb_putchar(c);
+			if (c_is_no_print)
+				standout_end();
+		}
+		Hit_Return();
+	} else if (strncmp(cmd, "quit", i) == 0 // quit
+	        || strncmp(cmd, "next", i) == 0 // edit next file
+	        || strncmp(cmd, "prev", i) == 0 // edit previous file
+	) {
+		int n;
+		if (useforce) {
+			if (*cmd == 'q') {
+				// force end of argv list
+				optind = save_argc;
+			}
+			editing = 0;
+			goto ret;
+		}
+		// don't exit if the file been modified
+		if (file_modified) {
+			status_line_bold("No write since last change (:%s! overrides)", cmd);
+			goto ret;
+		}
+		// are there other file to edit
+		n = save_argc - optind - 1;
+		if (*cmd == 'q' && n > 0) {
+			status_line_bold("%d more file(s) to edit", n);
+			goto ret;
+		}
+		if (*cmd == 'n' && n <= 0) {
+			status_line_bold("No more files to edit");
+			goto ret;
+		}
+		if (*cmd == 'p') {
+			// are there previous files to edit
+			if (optind < 1) {
+				status_line_bold("No previous files to edit");
+				goto ret;
+			}
+			optind -= 2;
+		}
+		editing = 0;
+	} else if (strncmp(cmd, "read", i) == 0) {	// read file into text[]
+		fn = args;
+		if (!fn[0]) {
+			status_line_bold("No filename given");
+			goto ret;
+		}
+		if (b < 0) {	// no addr given- use defaults
+			q = begin_line(dot);	// assume "dot"
+		}
+		// read after current line- unless user said ":0r foo"
+		if (b != 0)
+			q = next_line(q);
+		{ // dance around potentially-reallocated text[]
+			uintptr_t ofs = q - text;
+			ch = file_insert(fn, q, 0);
+			q = text + ofs;
+		}
+		if (ch < 0)
+			goto ret;	// nothing was inserted
+		// how many lines in text[]?
+		li = count_lines(q, q + ch - 1);
+		status_line("\"%s\""
+			IF_FEATURE_VI_READONLY("%s")
+			" %dL, %dC", fn,
+			IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
+			li, ch);
+		if (ch > 0) {
+			// if the insert is before "dot" then we need to update
+			if (q <= dot)
+				dot += ch;
+			/*file_modified++; - done by file_insert */
+		}
+	} else if (strncmp(cmd, "rewind", i) == 0) {	// rewind cmd line args
+		if (file_modified && !useforce) {
+			status_line_bold("No write since last change (:%s! overrides)", cmd);
+		} else {
+			// reset the filenames to edit
+			optind = -1; /* start from 0th file */
+			editing = 0;
+		}
+#if ENABLE_FEATURE_VI_SET
+	} else if (strncmp(cmd, "set", i) == 0) {	// set or clear features
+#if ENABLE_FEATURE_VI_SETOPTS
+		char *argp;
+#endif
+		i = 0;			// offset into args
+		// only blank is regarded as args delimiter. What about tab '\t'?
+		if (!args[0] || strcasecmp(args, "all") == 0) {
+			// print out values of all options
+#if ENABLE_FEATURE_VI_SETOPTS
+			status_line_bold(
+				"%sautoindent "
+				"%sflash "
+				"%signorecase "
+				"%sshowmatch "
+				"tabstop=%u",
+				autoindent ? "" : "no",
+				err_method ? "" : "no",
+				ignorecase ? "" : "no",
+				showmatch ? "" : "no",
+				tabstop
+			);
+#endif
+			goto ret;
+		}
+#if ENABLE_FEATURE_VI_SETOPTS
+		argp = args;
+		while (*argp) {
+			if (strncmp(argp, "no", 2) == 0)
+				i = 2;		// ":set noautoindent"
+			setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
+			setops(argp, "flash "     , i, "fl", VI_ERR_METHOD);
+			setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
+			setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
+			if (strncmp(argp + i, "tabstop=", 8) == 0) {
+				int t = 0;
+				sscanf(argp + i+8, "%u", &t);
+				if (t > 0 && t <= MAX_TABSTOP)
+					tabstop = t;
+			}
+			argp = skip_non_whitespace(argp);
+			argp = skip_whitespace(argp);
+		}
+#endif /* FEATURE_VI_SETOPTS */
+#endif /* FEATURE_VI_SET */
+#if ENABLE_FEATURE_VI_SEARCH
+	} else if (cmd[0] == 's') {	// substitute a pattern with a replacement pattern
+		char *F, *R, *flags;
+		size_t len_F, len_R;
+		int gflag;		// global replace flag
+
+		// F points to the "find" pattern
+		// R points to the "replace" pattern
+		// replace the cmd line delimiters "/" with NULs
+		c = orig_buf[1];	// what is the delimiter
+		F = orig_buf + 2;	// start of "find"
+		R = strchr(F, c);	// middle delimiter
+		if (!R)
+			goto colon_s_fail;
+		len_F = R - F;
+		*R++ = '\0';	// terminate "find"
+		flags = strchr(R, c);
+		if (!flags)
+			goto colon_s_fail;
+		len_R = flags - R;
+		*flags++ = '\0';	// terminate "replace"
+		gflag = *flags;
+
+		q = begin_line(q);
+		if (b < 0) {	// maybe :s/foo/bar/
+			q = begin_line(dot);      // start with cur line
+			b = count_lines(text, q); // cur line number
+		}
+		if (e < 0)
+			e = b;		// maybe :.s/foo/bar/
+
+		for (i = b; i <= e; i++) {	// so, :20,23 s \0 find \0 replace \0
+			char *ls = q;		// orig line start
+			char *found;
+ vc4:
+			found = char_search(q, F, FORWARD, LIMITED);	// search cur line only for "find"
+			if (found) {
+				uintptr_t bias;
+				// we found the "find" pattern - delete it
+				text_hole_delete(found, found + len_F - 1);
+				// inset the "replace" patern
+				bias = string_insert(found, R);	// insert the string
+				found += bias;
+				ls += bias;
+				/*q += bias; - recalculated anyway */
+				// check for "global"  :s/foo/bar/g
+				if (gflag == 'g') {
+					if ((found + len_R) < end_line(ls)) {
+						q = found + len_R;
+						goto vc4;	// don't let q move past cur line
+					}
+				}
+			}
+			q = next_line(ls);
+		}
+#endif /* FEATURE_VI_SEARCH */
+	} else if (strncmp(cmd, "version", i) == 0) {  // show software version
+		status_line(BB_VER " " BB_BT);
+	} else if (strncmp(cmd, "write", i) == 0  // write text to file
+	        || strncmp(cmd, "wq", i) == 0
+	        || strncmp(cmd, "wn", i) == 0
+	        || (cmd[0] == 'x' && !cmd[1])
+	) {
+		// is there a file name to write to?
+		if (args[0]) {
+			fn = args;
+		}
+#if ENABLE_FEATURE_VI_READONLY
+		if (readonly_mode && !useforce) {
+			status_line_bold("\"%s\" File is read only", fn);
+			goto ret;
+		}
+#endif
+		// how many lines in text[]?
+		li = count_lines(q, r);
+		ch = r - q + 1;
+		// see if file exists- if not, its just a new file request
+		if (useforce) {
+			// if "fn" is not write-able, chmod u+w
+			// sprintf(syscmd, "chmod u+w %s", fn);
+			// system(syscmd);
+			forced = TRUE;
+		}
+		l = file_write(fn, q, r);
+		if (useforce && forced) {
+			// chmod u-w
+			// sprintf(syscmd, "chmod u-w %s", fn);
+			// system(syscmd);
+			forced = FALSE;
+		}
+		if (l < 0) {
+			if (l == -1)
+				status_line_bold("\"%s\" %s", fn, strerror(errno));
+		} else {
+			status_line("\"%s\" %dL, %dC", fn, li, l);
+			if (q == text && r == end - 1 && l == ch) {
+				file_modified = 0;
+				last_file_modified = -1;
+			}
+			if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
+			    || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
+			    )
+			 && l == ch
+			) {
+				editing = 0;
+			}
+		}
+#if ENABLE_FEATURE_VI_YANKMARK
+	} else if (strncmp(cmd, "yank", i) == 0) {	// yank lines
+		if (b < 0) {	// no addr given- use defaults
+			q = begin_line(dot);	// assume .,. for the range
+			r = end_line(dot);
+		}
+		text_yank(q, r, YDreg);
+		li = count_lines(q, r);
+		status_line("Yank %d lines (%d chars) into [%c]",
+				li, strlen(reg[YDreg]), what_reg());
+#endif
+	} else {
+		// cmd unknown
+		not_implemented(cmd);
+	}
+ ret:
+	dot = bound_dot(dot);	// make sure "dot" is valid
+	return;
+#if ENABLE_FEATURE_VI_SEARCH
+ colon_s_fail:
+	status_line(":s expression missing delimiters");
+#endif
+}
+
+#endif /* FEATURE_VI_COLON */
+
+static void Hit_Return(void)
+{
+	int c;
+
+	standout_start();
+	write1("[Hit return to continue]");
+	standout_end();
+	while ((c = get_one_char()) != '\n' && c != '\r')
+		continue;
+	redraw(TRUE);		// force redraw all
+}
+
+static int next_tabstop(int col)
+{
+	return col + ((tabstop - 1) - (col % tabstop));
+}
+
+//----- Synchronize the cursor to Dot --------------------------
+static NOINLINE void sync_cursor(char *d, int *row, int *col)
+{
+	char *beg_cur;	// begin and end of "d" line
+	char *tp;
+	int cnt, ro, co;
+
+	beg_cur = begin_line(d);	// first char of cur line
+
+	if (beg_cur < screenbegin) {
+		// "d" is before top line on screen
+		// how many lines do we have to move
+		cnt = count_lines(beg_cur, screenbegin);
+ sc1:
+		screenbegin = beg_cur;
+		if (cnt > (rows - 1) / 2) {
+			// we moved too many lines. put "dot" in middle of screen
+			for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
+				screenbegin = prev_line(screenbegin);
+			}
+		}
+	} else {
+		char *end_scr;	// begin and end of screen
+		end_scr = end_screen();	// last char of screen
+		if (beg_cur > end_scr) {
+			// "d" is after bottom line on screen
+			// how many lines do we have to move
+			cnt = count_lines(end_scr, beg_cur);
+			if (cnt > (rows - 1) / 2)
+				goto sc1;	// too many lines
+			for (ro = 0; ro < cnt - 1; ro++) {
+				// move screen begin the same amount
+				screenbegin = next_line(screenbegin);
+				// now, move the end of screen
+				end_scr = next_line(end_scr);
+				end_scr = end_line(end_scr);
+			}
+		}
+	}
+	// "d" is on screen- find out which row
+	tp = screenbegin;
+	for (ro = 0; ro < rows - 1; ro++) {	// drive "ro" to correct row
+		if (tp == beg_cur)
+			break;
+		tp = next_line(tp);
+	}
+
+	// find out what col "d" is on
+	co = 0;
+	while (tp < d) { // drive "co" to correct column
+		if (*tp == '\n') //vda || *tp == '\0')
+			break;
+		if (*tp == '\t') {
+			// handle tabs like real vi
+			if (d == tp && cmd_mode) {
+				break;
+			}
+			co = next_tabstop(co);
+		} else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
+			co++; // display as ^X, use 2 columns
+		}
+		co++;
+		tp++;
+	}
+
+	// "co" is the column where "dot" is.
+	// The screen has "columns" columns.
+	// The currently displayed columns are  0+offset -- columns+ofset
+	// |-------------------------------------------------------------|
+	//               ^ ^                                ^
+	//        offset | |------- columns ----------------|
+	//
+	// If "co" is already in this range then we do not have to adjust offset
+	//      but, we do have to subtract the "offset" bias from "co".
+	// If "co" is outside this range then we have to change "offset".
+	// If the first char of a line is a tab the cursor will try to stay
+	//  in column 7, but we have to set offset to 0.
+
+	if (co < 0 + offset) {
+		offset = co;
+	}
+	if (co >= columns + offset) {
+		offset = co - columns + 1;
+	}
+	// if the first char of the line is a tab, and "dot" is sitting on it
+	//  force offset to 0.
+	if (d == beg_cur && *d == '\t') {
+		offset = 0;
+	}
+	co -= offset;
+
+	*row = ro;
+	*col = co;
+}
+
+//----- Text Movement Routines ---------------------------------
+static char *begin_line(char *p) // return pointer to first char cur line
+{
+	if (p > text) {
+		p = memrchr(text, '\n', p - text);
+		if (!p)
+			return text;
+		return p + 1;
+	}
+	return p;
+}
+
+static char *end_line(char *p) // return pointer to NL of cur line
+{
+	if (p < end - 1) {
+		p = memchr(p, '\n', end - p - 1);
+		if (!p)
+			return end - 1;
+	}
+	return p;
+}
+
+static char *dollar_line(char *p) // return pointer to just before NL line
+{
+	p = end_line(p);
+	// Try to stay off of the Newline
+	if (*p == '\n' && (p - begin_line(p)) > 0)
+		p--;
+	return p;
+}
+
+static char *prev_line(char *p) // return pointer first char prev line
+{
+	p = begin_line(p);	// goto begining of cur line
+	if (p > text && p[-1] == '\n')
+		p--;			// step to prev line
+	p = begin_line(p);	// goto begining of prev line
+	return p;
+}
+
+static char *next_line(char *p) // return pointer first char next line
+{
+	p = end_line(p);
+	if (p < end - 1 && *p == '\n')
+		p++;			// step to next line
+	return p;
+}
+
+//----- Text Information Routines ------------------------------
+static char *end_screen(void)
+{
+	char *q;
+	int cnt;
+
+	// find new bottom line
+	q = screenbegin;
+	for (cnt = 0; cnt < rows - 2; cnt++)
+		q = next_line(q);
+	q = end_line(q);
+	return q;
+}
+
+// count line from start to stop
+static int count_lines(char *start, char *stop)
+{
+	char *q;
+	int cnt;
+
+	if (stop < start) { // start and stop are backwards- reverse them
+		q = start;
+		start = stop;
+		stop = q;
+	}
+	cnt = 0;
+	stop = end_line(stop);
+	while (start <= stop && start <= end - 1) {
+		start = end_line(start);
+		if (*start == '\n')
+			cnt++;
+		start++;
+	}
+	return cnt;
+}
+
+static char *find_line(int li)	// find begining of line #li
+{
+	char *q;
+
+	for (q = text; li > 1; li--) {
+		q = next_line(q);
+	}
+	return q;
+}
+
+//----- Dot Movement Routines ----------------------------------
+static void dot_left(void)
+{
+	if (dot > text && dot[-1] != '\n')
+		dot--;
+}
+
+static void dot_right(void)
+{
+	if (dot < end - 1 && *dot != '\n')
+		dot++;
+}
+
+static void dot_begin(void)
+{
+	dot = begin_line(dot);	// return pointer to first char cur line
+}
+
+static void dot_end(void)
+{
+	dot = end_line(dot);	// return pointer to last char cur line
+}
+
+static char *move_to_col(char *p, int l)
+{
+	int co;
+
+	p = begin_line(p);
+	co = 0;
+	while (co < l && p < end) {
+		if (*p == '\n') //vda || *p == '\0')
+			break;
+		if (*p == '\t') {
+			co = next_tabstop(co);
+		} else if (*p < ' ' || *p == 127) {
+			co++; // display as ^X, use 2 columns
+		}
+		co++;
+		p++;
+	}
+	return p;
+}
+
+static void dot_next(void)
+{
+	dot = next_line(dot);
+}
+
+static void dot_prev(void)
+{
+	dot = prev_line(dot);
+}
+
+static void dot_scroll(int cnt, int dir)
+{
+	char *q;
+
+	for (; cnt > 0; cnt--) {
+		if (dir < 0) {
+			// scroll Backwards
+			// ctrl-Y scroll up one line
+			screenbegin = prev_line(screenbegin);
+		} else {
+			// scroll Forwards
+			// ctrl-E scroll down one line
+			screenbegin = next_line(screenbegin);
+		}
+	}
+	// make sure "dot" stays on the screen so we dont scroll off
+	if (dot < screenbegin)
+		dot = screenbegin;
+	q = end_screen();	// find new bottom line
+	if (dot > q)
+		dot = begin_line(q);	// is dot is below bottom line?
+	dot_skip_over_ws();
+}
+
+static void dot_skip_over_ws(void)
+{
+	// skip WS
+	while (isspace(*dot) && *dot != '\n' && dot < end - 1)
+		dot++;
+}
+
+static void dot_delete(void)	// delete the char at 'dot'
+{
+	text_hole_delete(dot, dot);
+}
+
+static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
+{
+	if (p >= end && end > text) {
+		p = end - 1;
+		indicate_error('1');
+	}
+	if (p < text) {
+		p = text;
+		indicate_error('2');
+	}
+	return p;
+}
+
+//----- Helper Utility Routines --------------------------------
+
+//----------------------------------------------------------------
+//----- Char Routines --------------------------------------------
+/* Chars that are part of a word-
+ *    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+ * Chars that are Not part of a word (stoppers)
+ *    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
+ * Chars that are WhiteSpace
+ *    TAB NEWLINE VT FF RETURN SPACE
+ * DO NOT COUNT NEWLINE AS WHITESPACE
+ */
+
+static char *new_screen(int ro, int co)
+{
+	int li;
+
+	free(screen);
+	screensize = ro * co + 8;
+	screen = xmalloc(screensize);
+	// initialize the new screen. assume this will be a empty file.
+	screen_erase();
+	//   non-existent text[] lines start with a tilde (~).
+	for (li = 1; li < ro - 1; li++) {
+		screen[(li * co) + 0] = '~';
+	}
+	return screen;
+}
+
+#if ENABLE_FEATURE_VI_SEARCH
+
+# if ENABLE_FEATURE_VI_REGEX_SEARCH
+
+// search for pattern starting at p
+static char *char_search(char *p, const char *pat, int dir, int range)
+{
+	char *q;
+	struct re_pattern_buffer preg;
+	int i;
+	int size;
+
+	re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
+	preg.translate = 0;
+	preg.fastmap = 0;
+	preg.buffer = 0;
+	preg.allocated = 0;
+
+	// assume a LIMITED forward search
+	q = next_line(p);
+	q = end_line(q);
+	q = end - 1;
+	if (dir == BACK) {
+		q = prev_line(p);
+		q = text;
+	}
+	// count the number of chars to search over, forward or backward
+	size = q - p;
+	if (size < 0)
+		size = p - q;
+	// RANGE could be negative if we are searching backwards
+	range = q - p;
+
+	q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
+	if (q != 0) {
+		// The pattern was not compiled
+		status_line_bold("bad search pattern: \"%s\": %s", pat, q);
+		i = 0;			// return p if pattern not compiled
+		goto cs1;
+	}
+
+	q = p;
+	if (range < 0) {
+		q = p - size;
+		if (q < text)
+			q = text;
+	}
+	// search for the compiled pattern, preg, in p[]
+	// range < 0-  search backward
+	// range > 0-  search forward
+	// 0 < start < size
+	// re_search() < 0  not found or error
+	// re_search() > 0  index of found pattern
+	//            struct pattern    char     int    int    int     struct reg
+	// re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
+	i = re_search(&preg, q, size, 0, range, 0);
+	if (i == -1) {
+		p = 0;
+		i = 0;			// return NULL if pattern not found
+	}
+ cs1:
+	if (dir == FORWARD) {
+		p = p + i;
+	} else {
+		p = p - i;
+	}
+	return p;
+}
+
+# else
+
+#  if ENABLE_FEATURE_VI_SETOPTS
+static int mycmp(const char *s1, const char *s2, int len)
+{
+	if (ignorecase) {
+		return strncasecmp(s1, s2, len);
+	}
+	return strncmp(s1, s2, len);
+}
+#  else
+#   define mycmp strncmp
+#  endif
+
+static char *char_search(char *p, const char *pat, int dir, int range)
+{
+	char *start, *stop;
+	int len;
+
+	len = strlen(pat);
+	if (dir == FORWARD) {
+		stop = end - 1;	// assume range is p - end-1
+		if (range == LIMITED)
+			stop = next_line(p);	// range is to next line
+		for (start = p; start < stop; start++) {
+			if (mycmp(start, pat, len) == 0) {
+				return start;
+			}
+		}
+	} else if (dir == BACK) {
+		stop = text;	// assume range is text - p
+		if (range == LIMITED)
+			stop = prev_line(p);	// range is to prev line
+		for (start = p - len; start >= stop; start--) {
+			if (mycmp(start, pat, len) == 0) {
+				return start;
+			}
+		}
+	}
+	// pattern not found
+	return NULL;
+}
+
+# endif
+
+#endif /* FEATURE_VI_SEARCH */
+
+static char *char_insert(char *p, char c) // insert the char c at 'p'
+{
+	if (c == 22) {		// Is this an ctrl-V?
+		p += stupid_insert(p, '^');	// use ^ to indicate literal next
+		refresh(FALSE);	// show the ^
+		c = get_one_char();
+		*p = c;
+		p++;
+		file_modified++;
+	} else if (c == 27) {	// Is this an ESC?
+		cmd_mode = 0;
+		cmdcnt = 0;
+		end_cmd_q();	// stop adding to q
+		last_status_cksum = 0;	// force status update
+		if ((p[-1] != '\n') && (dot > text)) {
+			p--;
+		}
+	} else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
+		//     123456789
+		if ((p[-1] != '\n') && (dot>text)) {
+			p--;
+			p = text_hole_delete(p, p);	// shrink buffer 1 char
+		}
+	} else {
+#if ENABLE_FEATURE_VI_SETOPTS
+		// insert a char into text[]
+		char *sp;		// "save p"
+#endif
+
+		if (c == 13)
+			c = '\n';	// translate \r to \n
+#if ENABLE_FEATURE_VI_SETOPTS
+		sp = p;			// remember addr of insert
+#endif
+		p += 1 + stupid_insert(p, c);	// insert the char
+#if ENABLE_FEATURE_VI_SETOPTS
+		if (showmatch && strchr(")]}", *sp) != NULL) {
+			showmatching(sp);
+		}
+		if (autoindent && c == '\n') {	// auto indent the new line
+			char *q;
+			size_t len;
+			q = prev_line(p);	// use prev line as template
+			len = strspn(q, " \t"); // space or tab
+			if (len) {
+				uintptr_t bias;
+				bias = text_hole_make(p, len);
+				p += bias;
+				q += bias;
+				memcpy(p, q, len);
+				p += len;
+			}
+		}
+#endif
+	}
+	return p;
+}
+
+// might reallocate text[]! use p += stupid_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
+{
+	uintptr_t bias;
+	bias = text_hole_make(p, 1);
+	p += bias;
+	*p = c;
+	//file_modified++; - done by text_hole_make()
+	return bias;
+}
+
+static int find_range(char **start, char **stop, char c)
+{
+	char *save_dot, *p, *q, *t;
+	int cnt, multiline = 0;
+
+	save_dot = dot;
+	p = q = dot;
+
+	if (strchr("cdy><", c)) {
+		// these cmds operate on whole lines
+		p = q = begin_line(p);
+		for (cnt = 1; cnt < cmdcnt; cnt++) {
+			q = next_line(q);
+		}
+		q = end_line(q);
+	} else if (strchr("^%$0bBeEfth\b\177", c)) {
+		// These cmds operate on char positions
+		do_cmd(c);		// execute movement cmd
+		q = dot;
+	} else if (strchr("wW", c)) {
+		do_cmd(c);		// execute movement cmd
+		// if we are at the next word's first char
+		// step back one char
+		// but check the possibilities when it is true
+		if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
+				|| (ispunct(dot[-1]) && !ispunct(dot[0]))
+				|| (isalnum(dot[-1]) && !isalnum(dot[0]))))
+			dot--;		// move back off of next word
+		if (dot > text && *dot == '\n')
+			dot--;		// stay off NL
+		q = dot;
+	} else if (strchr("H-k{", c)) {
+		// these operate on multi-lines backwards
+		q = end_line(dot);	// find NL
+		do_cmd(c);		// execute movement cmd
+		dot_begin();
+		p = dot;
+	} else if (strchr("L+j}\r\n", c)) {
+		// these operate on multi-lines forwards
+		p = begin_line(dot);
+		do_cmd(c);		// execute movement cmd
+		dot_end();		// find NL
+		q = dot;
+	} else {
+		// nothing -- this causes any other values of c to
+		// represent the one-character range under the
+		// cursor.  this is correct for ' ' and 'l', but
+		// perhaps no others.
+		//
+	}
+	if (q < p) {
+		t = q;
+		q = p;
+		p = t;
+	}
+
+	// backward char movements don't include start position
+	if (q > p && strchr("^0bBh\b\177", c)) q--;
+
+	multiline = 0;
+	for (t = p; t <= q; t++) {
+		if (*t == '\n') {
+			multiline = 1;
+			break;
+		}
+	}
+
+	*start = p;
+	*stop = q;
+	dot = save_dot;
+	return multiline;
+}
+
+static int st_test(char *p, int type, int dir, char *tested)
+{
+	char c, c0, ci;
+	int test, inc;
+
+	inc = dir;
+	c = c0 = p[0];
+	ci = p[inc];
+	test = 0;
+
+	if (type == S_BEFORE_WS) {
+		c = ci;
+		test = (!isspace(c) || c == '\n');
+	}
+	if (type == S_TO_WS) {
+		c = c0;
+		test = (!isspace(c) || c == '\n');
+	}
+	if (type == S_OVER_WS) {
+		c = c0;
+		test = isspace(c);
+	}
+	if (type == S_END_PUNCT) {
+		c = ci;
+		test = ispunct(c);
+	}
+	if (type == S_END_ALNUM) {
+		c = ci;
+		test = (isalnum(c) || c == '_');
+	}
+	*tested = c;
+	return test;
+}
+
+static char *skip_thing(char *p, int linecnt, int dir, int type)
+{
+	char c;
+
+	while (st_test(p, type, dir, &c)) {
+		// make sure we limit search to correct number of lines
+		if (c == '\n' && --linecnt < 1)
+			break;
+		if (dir >= 0 && p >= end - 1)
+			break;
+		if (dir < 0 && p <= text)
+			break;
+		p += dir;		// move to next char
+	}
+	return p;
+}
+
+// find matching char of pair  ()  []  {}
+static char *find_pair(char *p, const char c)
+{
+	char match, *q;
+	int dir, level;
+
+	match = ')';
+	level = 1;
+	dir = 1;			// assume forward
+	switch (c) {
+	case '(': match = ')'; break;
+	case '[': match = ']'; break;
+	case '{': match = '}'; break;
+	case ')': match = '('; dir = -1; break;
+	case ']': match = '['; dir = -1; break;
+	case '}': match = '{'; dir = -1; break;
+	}
+	for (q = p + dir; text <= q && q < end; q += dir) {
+		// look for match, count levels of pairs  (( ))
+		if (*q == c)
+			level++;	// increase pair levels
+		if (*q == match)
+			level--;	// reduce pair level
+		if (level == 0)
+			break;		// found matching pair
+	}
+	if (level != 0)
+		q = NULL;		// indicate no match
+	return q;
+}
+
+#if ENABLE_FEATURE_VI_SETOPTS
+// show the matching char of a pair,  ()  []  {}
+static void showmatching(char *p)
+{
+	char *q, *save_dot;
+
+	// we found half of a pair
+	q = find_pair(p, *p);	// get loc of matching char
+	if (q == NULL) {
+		indicate_error('3');	// no matching char
+	} else {
+		// "q" now points to matching pair
+		save_dot = dot;	// remember where we are
+		dot = q;		// go to new loc
+		refresh(FALSE);	// let the user see it
+		mysleep(40);	// give user some time
+		dot = save_dot;	// go back to old loc
+		refresh(FALSE);
+	}
+}
+#endif /* FEATURE_VI_SETOPTS */
+
+// open a hole in text[]
+// might reallocate text[]! use p += text_hole_make(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t text_hole_make(char *p, int size)	// at "p", make a 'size' byte hole
+{
+	uintptr_t bias = 0;
+
+	if (size <= 0)
+		return bias;
+	end += size;		// adjust the new END
+	if (end >= (text + text_size)) {
+		char *new_text;
+		text_size += end - (text + text_size) + 10240;
+		new_text = xrealloc(text, text_size);
+		bias = (new_text - text);
+		screenbegin += bias;
+		dot         += bias;
+		end         += bias;
+		p           += bias;
+#if ENABLE_FEATURE_VI_YANKMARK
+		{
+			int i;
+			for (i = 0; i < ARRAY_SIZE(mark); i++)
+				if (mark[i])
+					mark[i] += bias;
+		}
+#endif
+		text = new_text;
+	}
+	memmove(p + size, p, end - size - p);
+	memset(p, ' ', size);	// clear new hole
+	file_modified++;
+	return bias;
+}
+
+//  close a hole in text[]
+static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
+{
+	char *src, *dest;
+	int cnt, hole_size;
+
+	// move forwards, from beginning
+	// assume p <= q
+	src = q + 1;
+	dest = p;
+	if (q < p) {		// they are backward- swap them
+		src = p + 1;
+		dest = q;
+	}
+	hole_size = q - p + 1;
+	cnt = end - src;
+	if (src < text || src > end)
+		goto thd0;
+	if (dest < text || dest >= end)
+		goto thd0;
+	if (src >= end)
+		goto thd_atend;	// just delete the end of the buffer
+	memmove(dest, src, cnt);
+ thd_atend:
+	end = end - hole_size;	// adjust the new END
+	if (dest >= end)
+		dest = end - 1;	// make sure dest in below end-1
+	if (end <= text)
+		dest = end = text;	// keep pointers valid
+	file_modified++;
+ thd0:
+	return dest;
+}
+
+// copy text into register, then delete text.
+// if dist <= 0, do not include, or go past, a NewLine
+//
+static char *yank_delete(char *start, char *stop, int dist, int yf)
+{
+	char *p;
+
+	// make sure start <= stop
+	if (start > stop) {
+		// they are backwards, reverse them
+		p = start;
+		start = stop;
+		stop = p;
+	}
+	if (dist <= 0) {
+		// we cannot cross NL boundaries
+		p = start;
+		if (*p == '\n')
+			return p;
+		// dont go past a NewLine
+		for (; p + 1 <= stop; p++) {
+			if (p[1] == '\n') {
+				stop = p;	// "stop" just before NewLine
+				break;
+			}
+		}
+	}
+	p = start;
+#if ENABLE_FEATURE_VI_YANKMARK
+	text_yank(start, stop, YDreg);
+#endif
+	if (yf == YANKDEL) {
+		p = text_hole_delete(start, stop);
+	}					// delete lines
+	return p;
+}
+
+static void show_help(void)
+{
+	puts("These features are available:"
+#if ENABLE_FEATURE_VI_SEARCH
+	"\n\tPattern searches with / and ?"
+#endif
+#if ENABLE_FEATURE_VI_DOT_CMD
+	"\n\tLast command repeat with ."
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK
+	"\n\tLine marking with 'x"
+	"\n\tNamed buffers with \"x"
+#endif
+#if ENABLE_FEATURE_VI_READONLY
+	//not implemented: "\n\tReadonly if vi is called as \"view\""
+	//redundant: usage text says this too: "\n\tReadonly with -R command line arg"
+#endif
+#if ENABLE_FEATURE_VI_SET
+	"\n\tSome colon mode commands with :"
+#endif
+#if ENABLE_FEATURE_VI_SETOPTS
+	"\n\tSettable options with \":set\""
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+	"\n\tSignal catching- ^C"
+	"\n\tJob suspend and resume with ^Z"
+#endif
+#if ENABLE_FEATURE_VI_WIN_RESIZE
+	"\n\tAdapt to window re-sizes"
+#endif
+	);
+}
+
+#if ENABLE_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(char c)
+{
+	// get buffer for new cmd
+	// if there is a current cmd count put it in the buffer first
+	if (cmdcnt > 0) {
+		lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
+	} else { // just save char c onto queue
+		last_modifying_cmd[0] = c;
+		lmc_len = 1;
+	}
+	adding2q = 1;
+}
+
+static void end_cmd_q(void)
+{
+#if ENABLE_FEATURE_VI_YANKMARK
+	YDreg = 26;			// go back to default Yank/Delete reg
+#endif
+	adding2q = 0;
+}
+#endif /* FEATURE_VI_DOT_CMD */
+
+#if ENABLE_FEATURE_VI_YANKMARK \
+ || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
+ || ENABLE_FEATURE_VI_CRASHME
+// might reallocate text[]! use p += string_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
+{
+	uintptr_t bias;
+	int i;
+
+	i = strlen(s);
+	bias = text_hole_make(p, i);
+	p += bias;
+	memcpy(p, s, i);
+#if ENABLE_FEATURE_VI_YANKMARK
+	{
+		int cnt;
+		for (cnt = 0; *s != '\0'; s++) {
+			if (*s == '\n')
+				cnt++;
+		}
+		status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
+	}
+#endif
+	return bias;
+}
+#endif
+
+#if ENABLE_FEATURE_VI_YANKMARK
+static char *text_yank(char *p, char *q, int dest)	// copy text into a register
+{
+	int cnt = q - p;
+	if (cnt < 0) {		// they are backwards- reverse them
+		p = q;
+		cnt = -cnt;
+	}
+	free(reg[dest]);	//  if already a yank register, free it
+	reg[dest] = xstrndup(p, cnt + 1);
+	return p;
+}
+
+static char what_reg(void)
+{
+	char c;
+
+	c = 'D';			// default to D-reg
+	if (0 <= YDreg && YDreg <= 25)
+		c = 'a' + (char) YDreg;
+	if (YDreg == 26)
+		c = 'D';
+	if (YDreg == 27)
+		c = 'U';
+	return c;
+}
+
+static void check_context(char cmd)
+{
+	// A context is defined to be "modifying text"
+	// Any modifying command establishes a new context.
+
+	if (dot < context_start || dot > context_end) {
+		if (strchr(modifying_cmds, cmd) != NULL) {
+			// we are trying to modify text[]- make this the current context
+			mark[27] = mark[26];	// move cur to prev
+			mark[26] = dot;	// move local to cur
+			context_start = prev_line(prev_line(dot));
+			context_end = next_line(next_line(dot));
+			//loiter= start_loiter= now;
+		}
+	}
+}
+
+static char *swap_context(char *p) // goto new context for '' command make this the current context
+{
+	char *tmp;
+
+	// the current context is in mark[26]
+	// the previous context is in mark[27]
+	// only swap context if other context is valid
+	if (text <= mark[27] && mark[27] <= end - 1) {
+		tmp = mark[27];
+		mark[27] = mark[26];
+		mark[26] = tmp;
+		p = mark[26];	// where we are going- previous context
+		context_start = prev_line(prev_line(prev_line(p)));
+		context_end = next_line(next_line(next_line(p)));
+	}
+	return p;
+}
+#endif /* FEATURE_VI_YANKMARK */
+
+//----- Set terminal attributes --------------------------------
+static void rawmode(void)
+{
+	tcgetattr(0, &term_orig);
+	term_vi = term_orig;
+	term_vi.c_lflag &= (~ICANON & ~ECHO);	// leave ISIG on - allow intr's
+	term_vi.c_iflag &= (~IXON & ~ICRNL);
+	term_vi.c_oflag &= (~ONLCR);
+	term_vi.c_cc[VMIN] = 1;
+	term_vi.c_cc[VTIME] = 0;
+	erase_char = term_vi.c_cc[VERASE];
+	tcsetattr_stdin_TCSANOW(&term_vi);
+}
+
+static void cookmode(void)
+{
+	fflush_all();
+	tcsetattr_stdin_TCSANOW(&term_orig);
+}
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+//----- Come here when we get a window resize signal ---------
+static void winch_sig(int sig UNUSED_PARAM)
+{
+	int save_errno = errno;
+	// FIXME: do it in main loop!!!
+	signal(SIGWINCH, winch_sig);
+	query_screen_dimensions();
+	new_screen(rows, columns);	// get memory for virtual screen
+	redraw(TRUE);		// re-draw the screen
+	errno = save_errno;
+}
+
+//----- Come here when we get a continue signal -------------------
+static void cont_sig(int sig UNUSED_PARAM)
+{
+	int save_errno = errno;
+	rawmode(); // terminal to "raw"
+	last_status_cksum = 0; // force status update
+	redraw(TRUE); // re-draw the screen
+
+	signal(SIGTSTP, suspend_sig);
+	signal(SIGCONT, SIG_DFL);
+	//kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
+	errno = save_errno;
+}
+
+//----- Come here when we get a Suspend signal -------------------
+static void suspend_sig(int sig UNUSED_PARAM)
+{
+	int save_errno = errno;
+	go_bottom_and_clear_to_eol();
+	cookmode(); // terminal to "cooked"
+
+	signal(SIGCONT, cont_sig);
+	signal(SIGTSTP, SIG_DFL);
+	kill(my_pid, SIGTSTP);
+	errno = save_errno;
+}
+
+//----- Come here when we get a signal ---------------------------
+static void catch_sig(int sig)
+{
+	signal(SIGINT, catch_sig);
+	siglongjmp(restart, sig);
+}
+#endif /* FEATURE_VI_USE_SIGNALS */
+
+static int mysleep(int hund)	// sleep for 'hund' 1/100 seconds or stdin ready
+{
+	struct pollfd pfd[1];
+
+	pfd[0].fd = STDIN_FILENO;
+	pfd[0].events = POLLIN;
+	return safe_poll(pfd, 1, hund*10) > 0;
+}
+
+//----- IO Routines --------------------------------------------
+static int readit(void) // read (maybe cursor) key from stdin
+{
+	int c;
+
+	fflush_all();
+	c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
+	if (c == -1) { // EOF/error
+		go_bottom_and_clear_to_eol();
+		cookmode(); // terminal to "cooked"
+		bb_error_msg_and_die("can't read user input");
+	}
+	return c;
+}
+
+//----- IO Routines --------------------------------------------
+static int get_one_char(void)
+{
+	int c;
+
+#if ENABLE_FEATURE_VI_DOT_CMD
+	if (!adding2q) {
+		// we are not adding to the q.
+		// but, we may be reading from a q
+		if (ioq == 0) {
+			// there is no current q, read from STDIN
+			c = readit();	// get the users input
+		} else {
+			// there is a queue to get chars from first
+			// careful with correct sign expansion!
+			c = (unsigned char)*ioq++;
+			if (c == '\0') {
+				// the end of the q, read from STDIN
+				free(ioq_start);
+				ioq_start = ioq = 0;
+				c = readit();	// get the users input
+			}
+		}
+	} else {
+		// adding STDIN chars to q
+		c = readit();	// get the users input
+		if (lmc_len >= MAX_INPUT_LEN - 1) {
+			status_line_bold("last_modifying_cmd overrun");
+		} else {
+			// add new char to q
+			last_modifying_cmd[lmc_len++] = c;
+		}
+	}
+#else
+	c = readit();		// get the users input
+#endif /* FEATURE_VI_DOT_CMD */
+	return c;
+}
+
+// Get input line (uses "status line" area)
+static char *get_input_line(const char *prompt)
+{
+	// char [MAX_INPUT_LEN]
+#define buf get_input_line__buf
+
+	int c;
+	int i;
+
+	strcpy(buf, prompt);
+	last_status_cksum = 0;	// force status update
+	go_bottom_and_clear_to_eol();
+	write1(prompt);      // write out the :, /, or ? prompt
+
+	i = strlen(buf);
+	while (i < MAX_INPUT_LEN) {
+		c = get_one_char();
+		if (c == '\n' || c == '\r' || c == 27)
+			break;		// this is end of input
+		if (c == erase_char || c == 8 || c == 127) {
+			// user wants to erase prev char
+			buf[--i] = '\0';
+			write1("\b \b"); // erase char on screen
+			if (i <= 0) // user backs up before b-o-l, exit
+				break;
+		} else if (c > 0 && c < 256) { // exclude Unicode
+			// (TODO: need to handle Unicode)
+			buf[i] = c;
+			buf[++i] = '\0';
+			bb_putchar(c);
+		}
+	}
+	refresh(FALSE);
+	return buf;
+#undef buf
+}
+
+static int file_size(const char *fn) // what is the byte size of "fn"
+{
+	struct stat st_buf;
+	int cnt;
+
+	cnt = -1;
+	if (fn && stat(fn, &st_buf) == 0)	// see if file exists
+		cnt = (int) st_buf.st_size;
+	return cnt;
+}
+
+// might reallocate text[]!
+static int file_insert(const char *fn, char *p, int update_ro_status)
+{
+	int cnt = -1;
+	int fd, size;
+	struct stat statbuf;
+
+	/* Validate file */
+	if (stat(fn, &statbuf) < 0) {
+		status_line_bold("\"%s\" %s", fn, strerror(errno));
+		goto fi0;
+	}
+	if (!S_ISREG(statbuf.st_mode)) {
+		// This is not a regular file
+		status_line_bold("\"%s\" Not a regular file", fn);
+		goto fi0;
+	}
+	if (p < text || p > end) {
+		status_line_bold("Trying to insert file outside of memory");
+		goto fi0;
+	}
+
+	// read file to buffer
+	fd = open(fn, O_RDONLY);
+	if (fd < 0) {
+		status_line_bold("\"%s\" %s", fn, strerror(errno));
+		goto fi0;
+	}
+	size = statbuf.st_size;
+	p += text_hole_make(p, size);
+	cnt = safe_read(fd, p, size);
+	if (cnt < 0) {
+		status_line_bold("\"%s\" %s", fn, strerror(errno));
+		p = text_hole_delete(p, p + size - 1);	// un-do buffer insert
+	} else if (cnt < size) {
+		// There was a partial read, shrink unused space text[]
+		p = text_hole_delete(p + cnt, p + (size - cnt) - 1);	// un-do buffer insert
+		status_line_bold("can't read all of file \"%s\"", fn);
+	}
+	if (cnt >= size)
+		file_modified++;
+	close(fd);
+ fi0:
+#if ENABLE_FEATURE_VI_READONLY
+	if (update_ro_status
+	 && ((access(fn, W_OK) < 0) ||
+		/* root will always have access()
+		 * so we check fileperms too */
+		!(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
+	    )
+	) {
+		SET_READONLY_FILE(readonly_mode);
+	}
+#endif
+	return cnt;
+}
+
+static int file_write(char *fn, char *first, char *last)
+{
+	int fd, cnt, charcnt;
+
+	if (fn == 0) {
+		status_line_bold("No current filename");
+		return -2;
+	}
+	/* By popular request we do not open file with O_TRUNC,
+	 * but instead ftruncate() it _after_ successful write.
+	 * Might reduce amount of data lost on power fail etc.
+	 */
+	fd = open(fn, (O_WRONLY | O_CREAT), 0666);
+	if (fd < 0)
+		return -1;
+	cnt = last - first + 1;
+	charcnt = full_write(fd, first, cnt);
+	ftruncate(fd, charcnt);
+	if (charcnt == cnt) {
+		// good write
+		//file_modified = FALSE;
+	} else {
+		charcnt = 0;
+	}
+	close(fd);
+	return charcnt;
+}
+
+//----- Terminal Drawing ---------------------------------------
+// The terminal is made up of 'rows' line of 'columns' columns.
+// classically this would be 24 x 80.
+//  screen coordinates
+//  0,0     ...     0,79
+//  1,0     ...     1,79
+//  .       ...     .
+//  .       ...     .
+//  22,0    ...     22,79
+//  23,0    ...     23,79   <- status line
+
+//----- Move the cursor to row x col (count from 0, not 1) -------
+static void place_cursor(int row, int col)
+{
+	char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
+
+	if (row < 0) row = 0;
+	if (row >= rows) row = rows - 1;
+	if (col < 0) col = 0;
+	if (col >= columns) col = columns - 1;
+
+	sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
+	write1(cm1);
+}
+
+//----- Erase from cursor to end of line -----------------------
+static void clear_to_eol(void)
+{
+	write1(ESC_CLEAR2EOL);
+}
+
+static void go_bottom_and_clear_to_eol(void)
+{
+	place_cursor(rows - 1, 0);
+	clear_to_eol();
+}
+
+//----- Erase from cursor to end of screen -----------------------
+static void clear_to_eos(void)
+{
+	write1(ESC_CLEAR2EOS);
+}
+
+//----- Start standout mode ------------------------------------
+static void standout_start(void)
+{
+	write1(ESC_BOLD_TEXT);
+}
+
+//----- End standout mode --------------------------------------
+static void standout_end(void)
+{
+	write1(ESC_NORM_TEXT);
+}
+
+//----- Flash the screen  --------------------------------------
+static void flash(int h)
+{
+	standout_start();
+	redraw(TRUE);
+	mysleep(h);
+	standout_end();
+	redraw(TRUE);
+}
+
+static void Indicate_Error(void)
+{
+#if ENABLE_FEATURE_VI_CRASHME
+	if (crashme > 0)
+		return;			// generate a random command
+#endif
+	if (!err_method) {
+		write1(ESC_BELL);
+	} else {
+		flash(10);
+	}
+}
+
+//----- Screen[] Routines --------------------------------------
+//----- Erase the Screen[] memory ------------------------------
+static void screen_erase(void)
+{
+	memset(screen, ' ', screensize);	// clear new screen
+}
+
+static int bufsum(char *buf, int count)
+{
+	int sum = 0;
+	char *e = buf + count;
+
+	while (buf < e)
+		sum += (unsigned char) *buf++;
+	return sum;
+}
+
+//----- Draw the status line at bottom of the screen -------------
+static void show_status_line(void)
+{
+	int cnt = 0, cksum = 0;
+
+	// either we already have an error or status message, or we
+	// create one.
+	if (!have_status_msg) {
+		cnt = format_edit_status();
+		cksum = bufsum(status_buffer, cnt);
+	}
+	if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
+		last_status_cksum = cksum;		// remember if we have seen this line
+		go_bottom_and_clear_to_eol();
+		write1(status_buffer);
+		if (have_status_msg) {
+			if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
+					(columns - 1) ) {
+				have_status_msg = 0;
+				Hit_Return();
+			}
+			have_status_msg = 0;
+		}
+		place_cursor(crow, ccol);  // put cursor back in correct place
+	}
+	fflush_all();
+}
+
+//----- format the status buffer, the bottom line of screen ------
+// format status buffer, with STANDOUT mode
+static void status_line_bold(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	strcpy(status_buffer, ESC_BOLD_TEXT);
+	vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
+	strcat(status_buffer, ESC_NORM_TEXT);
+	va_end(args);
+
+	have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
+}
+
+// format status buffer
+static void status_line(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	vsprintf(status_buffer, format, args);
+	va_end(args);
+
+	have_status_msg = 1;
+}
+
+// copy s to buf, convert unprintable
+static void print_literal(char *buf, const char *s)
+{
+	char *d;
+	unsigned char c;
+
+	buf[0] = '\0';
+	if (!s[0])
+		s = "(NULL)";
+
+	d = buf;
+	for (; *s; s++) {
+		int c_is_no_print;
+
+		c = *s;
+		c_is_no_print = (c & 0x80) && !Isprint(c);
+		if (c_is_no_print) {
+			strcpy(d, ESC_NORM_TEXT);
+			d += sizeof(ESC_NORM_TEXT)-1;
+			c = '.';
+		}
+		if (c < ' ' || c == 0x7f) {
+			*d++ = '^';
+			c |= '@'; /* 0x40 */
+			if (c == 0x7f)
+				c = '?';
+		}
+		*d++ = c;
+		*d = '\0';
+		if (c_is_no_print) {
+			strcpy(d, ESC_BOLD_TEXT);
+			d += sizeof(ESC_BOLD_TEXT)-1;
+		}
+		if (*s == '\n') {
+			*d++ = '$';
+			*d = '\0';
+		}
+		if (d - buf > MAX_INPUT_LEN - 10) // paranoia
+			break;
+	}
+}
+
+static void not_implemented(const char *s)
+{
+	char buf[MAX_INPUT_LEN];
+
+	print_literal(buf, s);
+	status_line_bold("\'%s\' is not implemented", buf);
+}
+
+// show file status on status line
+static int format_edit_status(void)
+{
+	static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
+
+#define tot format_edit_status__tot
+
+	int cur, percent, ret, trunc_at;
+
+	// file_modified is now a counter rather than a flag.  this
+	// helps reduce the amount of line counting we need to do.
+	// (this will cause a mis-reporting of modified status
+	// once every MAXINT editing operations.)
+
+	// it would be nice to do a similar optimization here -- if
+	// we haven't done a motion that could have changed which line
+	// we're on, then we shouldn't have to do this count_lines()
+	cur = count_lines(text, dot);
+
+	// reduce counting -- the total lines can't have
+	// changed if we haven't done any edits.
+	if (file_modified != last_file_modified) {
+		tot = cur + count_lines(dot, end - 1) - 1;
+		last_file_modified = file_modified;
+	}
+
+	//    current line         percent
+	//   -------------    ~~ ----------
+	//    total lines            100
+	if (tot > 0) {
+		percent = (100 * cur) / tot;
+	} else {
+		cur = tot = 0;
+		percent = 100;
+	}
+
+	trunc_at = columns < STATUS_BUFFER_LEN-1 ?
+		columns : STATUS_BUFFER_LEN-1;
+
+	ret = snprintf(status_buffer, trunc_at+1,
+#if ENABLE_FEATURE_VI_READONLY
+		"%c %s%s%s %d/%d %d%%",
+#else
+		"%c %s%s %d/%d %d%%",
+#endif
+		cmd_mode_indicator[cmd_mode & 3],
+		(current_filename != NULL ? current_filename : "No file"),
+#if ENABLE_FEATURE_VI_READONLY
+		(readonly_mode ? " [Readonly]" : ""),
+#endif
+		(file_modified ? " [Modified]" : ""),
+		cur, tot, percent);
+
+	if (ret >= 0 && ret < trunc_at)
+		return ret;  /* it all fit */
+
+	return trunc_at;  /* had to truncate */
+#undef tot
+}
+
+//----- Force refresh of all Lines -----------------------------
+static void redraw(int full_screen)
+{
+	place_cursor(0, 0);
+	clear_to_eos();
+	screen_erase();		// erase the internal screen buffer
+	last_status_cksum = 0;	// force status update
+	refresh(full_screen);	// this will redraw the entire display
+	show_status_line();
+}
+
+//----- Format a text[] line into a buffer ---------------------
+static char* format_line(char *src /*, int li*/)
+{
+	unsigned char c;
+	int co;
+	int ofs = offset;
+	char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
+
+	c = '~'; // char in col 0 in non-existent lines is '~'
+	co = 0;
+	while (co < columns + tabstop) {
+		// have we gone past the end?
+		if (src < end) {
+			c = *src++;
+			if (c == '\n')
+				break;
+			if ((c & 0x80) && !Isprint(c)) {
+				c = '.';
+			}
+			if (c < ' ' || c == 0x7f) {
+				if (c == '\t') {
+					c = ' ';
+					//      co %    8     !=     7
+					while ((co % tabstop) != (tabstop - 1)) {
+						dest[co++] = c;
+					}
+				} else {
+					dest[co++] = '^';
+					if (c == 0x7f)
+						c = '?';
+					else
+						c += '@'; // Ctrl-X -> 'X'
+				}
+			}
+		}
+		dest[co++] = c;
+		// discard scrolled-off-to-the-left portion,
+		// in tabstop-sized pieces
+		if (ofs >= tabstop && co >= tabstop) {
+			memmove(dest, dest + tabstop, co);
+			co -= tabstop;
+			ofs -= tabstop;
+		}
+		if (src >= end)
+			break;
+	}
+	// check "short line, gigantic offset" case
+	if (co < ofs)
+		ofs = co;
+	// discard last scrolled off part
+	co -= ofs;
+	dest += ofs;
+	// fill the rest with spaces
+	if (co < columns)
+		memset(&dest[co], ' ', columns - co);
+	return dest;
+}
+
+//----- Refresh the changed screen lines -----------------------
+// Copy the source line from text[] into the buffer and note
+// if the current screenline is different from the new buffer.
+// If they differ then that line needs redrawing on the terminal.
+//
+static void refresh(int full_screen)
+{
+#define old_offset refresh__old_offset
+
+	int li, changed;
+	char *tp, *sp;		// pointer into text[] and screen[]
+
+	if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
+		unsigned c = columns, r = rows;
+		query_screen_dimensions();
+		full_screen |= (c - columns) | (r - rows);
+	}
+	sync_cursor(dot, &crow, &ccol);	// where cursor will be (on "dot")
+	tp = screenbegin;	// index into text[] of top line
+
+	// compare text[] to screen[] and mark screen[] lines that need updating
+	for (li = 0; li < rows - 1; li++) {
+		int cs, ce;				// column start & end
+		char *out_buf;
+		// format current text line
+		out_buf = format_line(tp /*, li*/);
+
+		// skip to the end of the current text[] line
+		if (tp < end) {
+			char *t = memchr(tp, '\n', end - tp);
+			if (!t) t = end - 1;
+			tp = t + 1;
+		}
+
+		// see if there are any changes between vitual screen and out_buf
+		changed = FALSE;	// assume no change
+		cs = 0;
+		ce = columns - 1;
+		sp = &screen[li * columns];	// start of screen line
+		if (full_screen) {
+			// force re-draw of every single column from 0 - columns-1
+			goto re0;
+		}
+		// compare newly formatted buffer with virtual screen
+		// look forward for first difference between buf and screen
+		for (; cs <= ce; cs++) {
+			if (out_buf[cs] != sp[cs]) {
+				changed = TRUE;	// mark for redraw
+				break;
+			}
+		}
+
+		// look backward for last difference between out_buf and screen
+		for (; ce >= cs; ce--) {
+			if (out_buf[ce] != sp[ce]) {
+				changed = TRUE;	// mark for redraw
+				break;
+			}
+		}
+		// now, cs is index of first diff, and ce is index of last diff
+
+		// if horz offset has changed, force a redraw
+		if (offset != old_offset) {
+ re0:
+			changed = TRUE;
+		}
+
+		// make a sanity check of columns indexes
+		if (cs < 0) cs = 0;
+		if (ce > columns - 1) ce = columns - 1;
+		if (cs > ce) { cs = 0; ce = columns - 1; }
+		// is there a change between vitual screen and out_buf
+		if (changed) {
+			// copy changed part of buffer to virtual screen
+			memcpy(sp+cs, out_buf+cs, ce-cs+1);
+			place_cursor(li, cs);
+			// write line out to terminal
+			fwrite(&sp[cs], ce - cs + 1, 1, stdout);
+		}
+	}
+
+	place_cursor(crow, ccol);
+
+	old_offset = offset;
+#undef old_offset
+}
+
+//---------------------------------------------------------------------
+//----- the Ascii Chart -----------------------------------------------
+//
+//  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
+//  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
+//  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
+//  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
+//  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
+//  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
+//  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
+//  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
+//  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
+//  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
+//  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
+//  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
+//  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
+//  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
+//  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
+//  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
+//---------------------------------------------------------------------
+
+//----- Execute a Vi Command -----------------------------------
+static void do_cmd(int c)
+{
+	char *p, *q, *save_dot;
+	char buf[12];
+	int dir;
+	int cnt, i, j;
+	int c1;
+
+//	c1 = c; // quiet the compiler
+//	cnt = yf = 0; // quiet the compiler
+//	p = q = save_dot = buf; // quiet the compiler
+	memset(buf, '\0', sizeof(buf));
+
+	show_status_line();
+
+	/* if this is a cursor key, skip these checks */
+	switch (c) {
+		case KEYCODE_UP:
+		case KEYCODE_DOWN:
+		case KEYCODE_LEFT:
+		case KEYCODE_RIGHT:
+		case KEYCODE_HOME:
+		case KEYCODE_END:
+		case KEYCODE_PAGEUP:
+		case KEYCODE_PAGEDOWN:
+		case KEYCODE_DELETE:
+			goto key_cmd_mode;
+	}
+
+	if (cmd_mode == 2) {
+		//  flip-flop Insert/Replace mode
+		if (c == KEYCODE_INSERT)
+			goto dc_i;
+		// we are 'R'eplacing the current *dot with new char
+		if (*dot == '\n') {
+			// don't Replace past E-o-l
+			cmd_mode = 1;	// convert to insert
+		} else {
+			if (1 <= c || Isprint(c)) {
+				if (c != 27)
+					dot = yank_delete(dot, dot, 0, YANKDEL);	// delete char
+				dot = char_insert(dot, c);	// insert new char
+			}
+			goto dc1;
+		}
+	}
+	if (cmd_mode == 1) {
+		//  hitting "Insert" twice means "R" replace mode
+		if (c == KEYCODE_INSERT) goto dc5;
+		// insert the char c at "dot"
+		if (1 <= c || Isprint(c)) {
+			dot = char_insert(dot, c);
+		}
+		goto dc1;
+	}
+
+ key_cmd_mode:
+	switch (c) {
+		//case 0x01:	// soh
+		//case 0x09:	// ht
+		//case 0x0b:	// vt
+		//case 0x0e:	// so
+		//case 0x0f:	// si
+		//case 0x10:	// dle
+		//case 0x11:	// dc1
+		//case 0x13:	// dc3
+#if ENABLE_FEATURE_VI_CRASHME
+	case 0x14:			// dc4  ctrl-T
+		crashme = (crashme == 0) ? 1 : 0;
+		break;
+#endif
+		//case 0x16:	// syn
+		//case 0x17:	// etb
+		//case 0x18:	// can
+		//case 0x1c:	// fs
+		//case 0x1d:	// gs
+		//case 0x1e:	// rs
+		//case 0x1f:	// us
+		//case '!':	// !-
+		//case '#':	// #-
+		//case '&':	// &-
+		//case '(':	// (-
+		//case ')':	// )-
+		//case '*':	// *-
+		//case '=':	// =-
+		//case '@':	// @-
+		//case 'F':	// F-
+		//case 'K':	// K-
+		//case 'Q':	// Q-
+		//case 'S':	// S-
+		//case 'T':	// T-
+		//case 'V':	// V-
+		//case '[':	// [-
+		//case '\\':	// \-
+		//case ']':	// ]-
+		//case '_':	// _-
+		//case '`':	// `-
+		//case 'u':	// u- FIXME- there is no undo
+		//case 'v':	// v-
+	default:			// unrecognized command
+		buf[0] = c;
+		buf[1] = '\0';
+		not_implemented(buf);
+		end_cmd_q();	// stop adding to q
+	case 0x00:			// nul- ignore
+		break;
+	case 2:			// ctrl-B  scroll up   full screen
+	case KEYCODE_PAGEUP:	// Cursor Key Page Up
+		dot_scroll(rows - 2, -1);
+		break;
+	case 4:			// ctrl-D  scroll down half screen
+		dot_scroll((rows - 2) / 2, 1);
+		break;
+	case 5:			// ctrl-E  scroll down one line
+		dot_scroll(1, 1);
+		break;
+	case 6:			// ctrl-F  scroll down full screen
+	case KEYCODE_PAGEDOWN:	// Cursor Key Page Down
+		dot_scroll(rows - 2, 1);
+		break;
+	case 7:			// ctrl-G  show current status
+		last_status_cksum = 0;	// force status update
+		break;
+	case 'h':			// h- move left
+	case KEYCODE_LEFT:	// cursor key Left
+	case 8:		// ctrl-H- move left    (This may be ERASE char)
+	case 0x7f:	// DEL- move left   (This may be ERASE char)
+		do {
+			dot_left();
+		} while (--cmdcnt > 0);
+		break;
+	case 10:			// Newline ^J
+	case 'j':			// j- goto next line, same col
+	case KEYCODE_DOWN:	// cursor key Down
+		do {
+			dot_next();		// go to next B-o-l
+			// try stay in same col
+			dot = move_to_col(dot, ccol + offset);
+		} while (--cmdcnt > 0);
+		break;
+	case 12:			// ctrl-L  force redraw whole screen
+	case 18:			// ctrl-R  force redraw
+		place_cursor(0, 0);
+		clear_to_eos();
+		//mysleep(10); // why???
+		screen_erase();	// erase the internal screen buffer
+		last_status_cksum = 0;	// force status update
+		refresh(TRUE);	// this will redraw the entire display
+		break;
+	case 13:			// Carriage Return ^M
+	case '+':			// +- goto next line
+		do {
+			dot_next();
+			dot_skip_over_ws();
+		} while (--cmdcnt > 0);
+		break;
+	case 21:			// ctrl-U  scroll up   half screen
+		dot_scroll((rows - 2) / 2, -1);
+		break;
+	case 25:			// ctrl-Y  scroll up one line
+		dot_scroll(1, -1);
+		break;
+	case 27:			// esc
+		if (cmd_mode == 0)
+			indicate_error(c);
+		cmd_mode = 0;	// stop insrting
+		end_cmd_q();
+		last_status_cksum = 0;	// force status update
+		break;
+	case ' ':			// move right
+	case 'l':			// move right
+	case KEYCODE_RIGHT:	// Cursor Key Right
+		do {
+			dot_right();
+		} while (--cmdcnt > 0);
+		break;
+#if ENABLE_FEATURE_VI_YANKMARK
+	case '"':			// "- name a register to use for Delete/Yank
+		c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
+		if ((unsigned)c1 <= 25) { // a-z?
+			YDreg = c1;
+		} else {
+			indicate_error(c);
+		}
+		break;
+	case '\'':			// '- goto a specific mark
+		c1 = (get_one_char() | 0x20) - 'a';
+		if ((unsigned)c1 <= 25) { // a-z?
+			// get the b-o-l
+			q = mark[c1];
+			if (text <= q && q < end) {
+				dot = q;
+				dot_begin();	// go to B-o-l
+				dot_skip_over_ws();
+			}
+		} else if (c1 == '\'') {	// goto previous context
+			dot = swap_context(dot);	// swap current and previous context
+			dot_begin();	// go to B-o-l
+			dot_skip_over_ws();
+		} else {
+			indicate_error(c);
+		}
+		break;
+	case 'm':			// m- Mark a line
+		// this is really stupid.  If there are any inserts or deletes
+		// between text[0] and dot then this mark will not point to the
+		// correct location! It could be off by many lines!
+		// Well..., at least its quick and dirty.
+		c1 = (get_one_char() | 0x20) - 'a';
+		if ((unsigned)c1 <= 25) { // a-z?
+			// remember the line
+			mark[c1] = dot;
+		} else {
+			indicate_error(c);
+		}
+		break;
+	case 'P':			// P- Put register before
+	case 'p':			// p- put register after
+		p = reg[YDreg];
+		if (p == NULL) {
+			status_line_bold("Nothing in register %c", what_reg());
+			break;
+		}
+		// are we putting whole lines or strings
+		if (strchr(p, '\n') != NULL) {
+			if (c == 'P') {
+				dot_begin();	// putting lines- Put above
+			}
+			if (c == 'p') {
+				// are we putting after very last line?
+				if (end_line(dot) == (end - 1)) {
+					dot = end;	// force dot to end of text[]
+				} else {
+					dot_next();	// next line, then put before
+				}
+			}
+		} else {
+			if (c == 'p')
+				dot_right();	// move to right, can move to NL
+		}
+		string_insert(dot, p);	// insert the string
+		end_cmd_q();	// stop adding to q
+		break;
+	case 'U':			// U- Undo; replace current line with original version
+		if (reg[Ureg] != NULL) {
+			p = begin_line(dot);
+			q = end_line(dot);
+			p = text_hole_delete(p, q);	// delete cur line
+			p += string_insert(p, reg[Ureg]);	// insert orig line
+			dot = p;
+			dot_skip_over_ws();
+		}
+		break;
+#endif /* FEATURE_VI_YANKMARK */
+	case '$':			// $- goto end of line
+	case KEYCODE_END:		// Cursor Key End
+		for (;;) {
+			dot = end_line(dot);
+			if (--cmdcnt <= 0)
+				break;
+			dot_next();
+		}
+		break;
+	case '%':			// %- find matching char of pair () [] {}
+		for (q = dot; q < end && *q != '\n'; q++) {
+			if (strchr("()[]{}", *q) != NULL) {
+				// we found half of a pair
+				p = find_pair(q, *q);
+				if (p == NULL) {
+					indicate_error(c);
+				} else {
+					dot = p;
+				}
+				break;
+			}
+		}
+		if (*q == '\n')
+			indicate_error(c);
+		break;
+	case 'f':			// f- forward to a user specified char
+		last_forward_char = get_one_char();	// get the search char
+		//
+		// dont separate these two commands. 'f' depends on ';'
+		//
+		//**** fall through to ... ';'
+	case ';':			// ;- look at rest of line for last forward char
+		do {
+			if (last_forward_char == 0)
+				break;
+			q = dot + 1;
+			while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
+				q++;
+			}
+			if (*q == last_forward_char)
+				dot = q;
+		} while (--cmdcnt > 0);
+		break;
+	case ',':           // repeat latest 'f' in opposite direction
+		if (last_forward_char == 0)
+			break;
+		do {
+			q = dot - 1;
+			while (q >= text && *q != '\n' && *q != last_forward_char) {
+				q--;
+			}
+			if (q >= text && *q == last_forward_char)
+				dot = q;
+		} while (--cmdcnt > 0);
+		break;
+
+	case '-':			// -- goto prev line
+		do {
+			dot_prev();
+			dot_skip_over_ws();
+		} while (--cmdcnt > 0);
+		break;
+#if ENABLE_FEATURE_VI_DOT_CMD
+	case '.':			// .- repeat the last modifying command
+		// Stuff the last_modifying_cmd back into stdin
+		// and let it be re-executed.
+		if (lmc_len > 0) {
+			last_modifying_cmd[lmc_len] = 0;
+			ioq = ioq_start = xstrdup(last_modifying_cmd);
+		}
+		break;
+#endif
+#if ENABLE_FEATURE_VI_SEARCH
+	case '?':			// /- search for a pattern
+	case '/':			// /- search for a pattern
+		buf[0] = c;
+		buf[1] = '\0';
+		q = get_input_line(buf);	// get input line- use "status line"
+		if (q[0] && !q[1]) {
+			if (last_search_pattern[0])
+				last_search_pattern[0] = c;
+			goto dc3; // if no pat re-use old pat
+		}
+		if (q[0]) {       // strlen(q) > 1: new pat- save it and find
+			// there is a new pat
+			free(last_search_pattern);
+			last_search_pattern = xstrdup(q);
+			goto dc3;	// now find the pattern
+		}
+		// user changed mind and erased the "/"-  do nothing
+		break;
+	case 'N':			// N- backward search for last pattern
+		dir = BACK;		// assume BACKWARD search
+		p = dot - 1;
+		if (last_search_pattern[0] == '?') {
+			dir = FORWARD;
+			p = dot + 1;
+		}
+		goto dc4;		// now search for pattern
+		break;
+	case 'n':			// n- repeat search for last pattern
+		// search rest of text[] starting at next char
+		// if search fails return orignal "p" not the "p+1" address
+		do {
+			const char *msg;
+ dc3:
+			dir = FORWARD;	// assume FORWARD search
+			p = dot + 1;
+			if (last_search_pattern[0] == '?') {
+				dir = BACK;
+				p = dot - 1;
+			}
+ dc4:
+			q = char_search(p, last_search_pattern + 1, dir, FULL);
+			if (q != NULL) {
+				dot = q;	// good search, update "dot"
+				msg = NULL;
+				goto dc2;
+			}
+			// no pattern found between "dot" and "end"- continue at top
+			p = text;
+			if (dir == BACK) {
+				p = end - 1;
+			}
+			q = char_search(p, last_search_pattern + 1, dir, FULL);
+			if (q != NULL) {	// found something
+				dot = q;	// found new pattern- goto it
+				msg = "search hit BOTTOM, continuing at TOP";
+				if (dir == BACK) {
+					msg = "search hit TOP, continuing at BOTTOM";
+				}
+			} else {
+				msg = "Pattern not found";
+			}
+ dc2:
+			if (msg)
+				status_line_bold("%s", msg);
+		} while (--cmdcnt > 0);
+		break;
+	case '{':			// {- move backward paragraph
+		q = char_search(dot, "\n\n", BACK, FULL);
+		if (q != NULL) {	// found blank line
+			dot = next_line(q);	// move to next blank line
+		}
+		break;
+	case '}':			// }- move forward paragraph
+		q = char_search(dot, "\n\n", FORWARD, FULL);
+		if (q != NULL) {	// found blank line
+			dot = next_line(q);	// move to next blank line
+		}
+		break;
+#endif /* FEATURE_VI_SEARCH */
+	case '0':			// 0- goto begining of line
+	case '1':			// 1-
+	case '2':			// 2-
+	case '3':			// 3-
+	case '4':			// 4-
+	case '5':			// 5-
+	case '6':			// 6-
+	case '7':			// 7-
+	case '8':			// 8-
+	case '9':			// 9-
+		if (c == '0' && cmdcnt < 1) {
+			dot_begin();	// this was a standalone zero
+		} else {
+			cmdcnt = cmdcnt * 10 + (c - '0');	// this 0 is part of a number
+		}
+		break;
+	case ':':			// :- the colon mode commands
+		p = get_input_line(":");	// get input line- use "status line"
+#if ENABLE_FEATURE_VI_COLON
+		colon(p);		// execute the command
+#else
+		if (*p == ':')
+			p++;				// move past the ':'
+		cnt = strlen(p);
+		if (cnt <= 0)
+			break;
+		if (strncmp(p, "quit", cnt) == 0
+		 || strncmp(p, "q!", cnt) == 0   // delete lines
+		) {
+			if (file_modified && p[1] != '!') {
+				status_line_bold("No write since last change (:%s! overrides)", p);
+			} else {
+				editing = 0;
+			}
+		} else if (strncmp(p, "write", cnt) == 0
+		        || strncmp(p, "wq", cnt) == 0
+		        || strncmp(p, "wn", cnt) == 0
+		        || (p[0] == 'x' && !p[1])
+		) {
+			cnt = file_write(current_filename, text, end - 1);
+			if (cnt < 0) {
+				if (cnt == -1)
+					status_line_bold("Write error: %s", strerror(errno));
+			} else {
+				file_modified = 0;
+				last_file_modified = -1;
+				status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
+				if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
+				 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
+				) {
+					editing = 0;
+				}
+			}
+		} else if (strncmp(p, "file", cnt) == 0) {
+			last_status_cksum = 0;	// force status update
+		} else if (sscanf(p, "%d", &j) > 0) {
+			dot = find_line(j);		// go to line # j
+			dot_skip_over_ws();
+		} else {		// unrecognized cmd
+			not_implemented(p);
+		}
+#endif /* !FEATURE_VI_COLON */
+		break;
+	case '<':			// <- Left  shift something
+	case '>':			// >- Right shift something
+		cnt = count_lines(text, dot);	// remember what line we are on
+		c1 = get_one_char();	// get the type of thing to delete
+		find_range(&p, &q, c1);
+		yank_delete(p, q, 1, YANKONLY);	// save copy before change
+		p = begin_line(p);
+		q = end_line(q);
+		i = count_lines(p, q);	// # of lines we are shifting
+		for ( ; i > 0; i--, p = next_line(p)) {
+			if (c == '<') {
+				// shift left- remove tab or 8 spaces
+				if (*p == '\t') {
+					// shrink buffer 1 char
+					text_hole_delete(p, p);
+				} else if (*p == ' ') {
+					// we should be calculating columns, not just SPACE
+					for (j = 0; *p == ' ' && j < tabstop; j++) {
+						text_hole_delete(p, p);
+					}
+				}
+			} else if (c == '>') {
+				// shift right -- add tab or 8 spaces
+				char_insert(p, '\t');
+			}
+		}
+		dot = find_line(cnt);	// what line were we on
+		dot_skip_over_ws();
+		end_cmd_q();	// stop adding to q
+		break;
+	case 'A':			// A- append at e-o-l
+		dot_end();		// go to e-o-l
+		//**** fall through to ... 'a'
+	case 'a':			// a- append after current char
+		if (*dot != '\n')
+			dot++;
+		goto dc_i;
+		break;
+	case 'B':			// B- back a blank-delimited Word
+	case 'E':			// E- end of a blank-delimited word
+	case 'W':			// W- forward a blank-delimited word
+		dir = FORWARD;
+		if (c == 'B')
+			dir = BACK;
+		do {
+			if (c == 'W' || isspace(dot[dir])) {
+				dot = skip_thing(dot, 1, dir, S_TO_WS);
+				dot = skip_thing(dot, 2, dir, S_OVER_WS);
+			}
+			if (c != 'W')
+				dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
+		} while (--cmdcnt > 0);
+		break;
+	case 'C':			// C- Change to e-o-l
+	case 'D':			// D- delete to e-o-l
+		save_dot = dot;
+		dot = dollar_line(dot);	// move to before NL
+		// copy text into a register and delete
+		dot = yank_delete(save_dot, dot, 0, YANKDEL);	// delete to e-o-l
+		if (c == 'C')
+			goto dc_i;	// start inserting
+#if ENABLE_FEATURE_VI_DOT_CMD
+		if (c == 'D')
+			end_cmd_q();	// stop adding to q
+#endif
+		break;
+	case 'g': // 'gg' goto a line number (vim) (default: very first line)
+		c1 = get_one_char();
+		if (c1 != 'g') {
+			buf[0] = 'g';
+			buf[1] = c1; // TODO: if Unicode?
+			buf[2] = '\0';
+			not_implemented(buf);
+			break;
+		}
+		if (cmdcnt == 0)
+			cmdcnt = 1;
+		/* fall through */
+	case 'G':		// G- goto to a line number (default= E-O-F)
+		dot = end - 1;				// assume E-O-F
+		if (cmdcnt > 0) {
+			dot = find_line(cmdcnt);	// what line is #cmdcnt
+		}
+		dot_skip_over_ws();
+		break;
+	case 'H':			// H- goto top line on screen
+		dot = screenbegin;
+		if (cmdcnt > (rows - 1)) {
+			cmdcnt = (rows - 1);
+		}
+		if (--cmdcnt > 0) {
+			do_cmd('+');
+		}
+		dot_skip_over_ws();
+		break;
+	case 'I':			// I- insert before first non-blank
+		dot_begin();	// 0
+		dot_skip_over_ws();
+		//**** fall through to ... 'i'
+	case 'i':			// i- insert before current char
+	case KEYCODE_INSERT:	// Cursor Key Insert
+ dc_i:
+		cmd_mode = 1;	// start inserting
+		break;
+	case 'J':			// J- join current and next lines together
+		do {
+			dot_end();		// move to NL
+			if (dot < end - 1) {	// make sure not last char in text[]
+				*dot++ = ' ';	// replace NL with space
+				file_modified++;
+				while (isblank(*dot)) {	// delete leading WS
+					dot_delete();
+				}
+			}
+		} while (--cmdcnt > 0);
+		end_cmd_q();	// stop adding to q
+		break;
+	case 'L':			// L- goto bottom line on screen
+		dot = end_screen();
+		if (cmdcnt > (rows - 1)) {
+			cmdcnt = (rows - 1);
+		}
+		if (--cmdcnt > 0) {
+			do_cmd('-');
+		}
+		dot_begin();
+		dot_skip_over_ws();
+		break;
+	case 'M':			// M- goto middle line on screen
+		dot = screenbegin;
+		for (cnt = 0; cnt < (rows-1) / 2; cnt++)
+			dot = next_line(dot);
+		break;
+	case 'O':			// O- open a empty line above
+		//    0i\n ESC -i
+		p = begin_line(dot);
+		if (p[-1] == '\n') {
+			dot_prev();
+	case 'o':			// o- open a empty line below; Yes, I know it is in the middle of the "if (..."
+			dot_end();
+			dot = char_insert(dot, '\n');
+		} else {
+			dot_begin();	// 0
+			dot = char_insert(dot, '\n');	// i\n ESC
+			dot_prev();	// -
+		}
+		goto dc_i;
+		break;
+	case 'R':			// R- continuous Replace char
+ dc5:
+		cmd_mode = 2;
+		break;
+	case KEYCODE_DELETE:
+		c = 'x';
+		// fall through
+	case 'X':			// X- delete char before dot
+	case 'x':			// x- delete the current char
+	case 's':			// s- substitute the current char
+		dir = 0;
+		if (c == 'X')
+			dir = -1;
+		do {
+			if (dot[dir] != '\n') {
+				if (c == 'X')
+					dot--;	// delete prev char
+				dot = yank_delete(dot, dot, 0, YANKDEL);	// delete char
+			}
+		} while (--cmdcnt > 0);
+		end_cmd_q();	// stop adding to q
+		if (c == 's')
+			goto dc_i;	// start inserting
+		break;
+	case 'Z':			// Z- if modified, {write}; exit
+		// ZZ means to save file (if necessary), then exit
+		c1 = get_one_char();
+		if (c1 != 'Z') {
+			indicate_error(c);
+			break;
+		}
+		if (file_modified) {
+			if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
+				status_line_bold("\"%s\" File is read only", current_filename);
+				break;
+			}
+			cnt = file_write(current_filename, text, end - 1);
+			if (cnt < 0) {
+				if (cnt == -1)
+					status_line_bold("Write error: %s", strerror(errno));
+			} else if (cnt == (end - 1 - text + 1)) {
+				editing = 0;
+			}
+		} else {
+			editing = 0;
+		}
+		break;
+	case '^':			// ^- move to first non-blank on line
+		dot_begin();
+		dot_skip_over_ws();
+		break;
+	case 'b':			// b- back a word
+	case 'e':			// e- end of word
+		dir = FORWARD;
+		if (c == 'b')
+			dir = BACK;
+		do {
+			if ((dot + dir) < text || (dot + dir) > end - 1)
+				break;
+			dot += dir;
+			if (isspace(*dot)) {
+				dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
+			}
+			if (isalnum(*dot) || *dot == '_') {
+				dot = skip_thing(dot, 1, dir, S_END_ALNUM);
+			} else if (ispunct(*dot)) {
+				dot = skip_thing(dot, 1, dir, S_END_PUNCT);
+			}
+		} while (--cmdcnt > 0);
+		break;
+	case 'c':			// c- change something
+	case 'd':			// d- delete something
+#if ENABLE_FEATURE_VI_YANKMARK
+	case 'y':			// y- yank   something
+	case 'Y':			// Y- Yank a line
+#endif
+	{
+		int yf, ml, whole = 0;
+		yf = YANKDEL;	// assume either "c" or "d"
+#if ENABLE_FEATURE_VI_YANKMARK
+		if (c == 'y' || c == 'Y')
+			yf = YANKONLY;
+#endif
+		c1 = 'y';
+		if (c != 'Y')
+			c1 = get_one_char();	// get the type of thing to delete
+		// determine range, and whether it spans lines
+		ml = find_range(&p, &q, c1);
+		if (c1 == 27) {	// ESC- user changed mind and wants out
+			c = c1 = 27;	// Escape- do nothing
+		} else if (strchr("wW", c1)) {
+			if (c == 'c') {
+				// don't include trailing WS as part of word
+				while (isblank(*q)) {
+					if (q <= text || q[-1] == '\n')
+						break;
+					q--;
+				}
+			}
+			dot = yank_delete(p, q, ml, yf);	// delete word
+		} else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
+			// partial line copy text into a register and delete
+			dot = yank_delete(p, q, ml, yf);	// delete word
+		} else if (strchr("cdykjHL+-{}\r\n", c1)) {
+			// whole line copy text into a register and delete
+			dot = yank_delete(p, q, ml, yf);	// delete lines
+			whole = 1;
+		} else {
+			// could not recognize object
+			c = c1 = 27;	// error-
+			ml = 0;
+			indicate_error(c);
+		}
+		if (ml && whole) {
+			if (c == 'c') {
+				dot = char_insert(dot, '\n');
+				// on the last line of file don't move to prev line
+				if (whole && dot != (end-1)) {
+					dot_prev();
+				}
+			} else if (c == 'd') {
+				dot_begin();
+				dot_skip_over_ws();
+			}
+		}
+		if (c1 != 27) {
+			// if CHANGING, not deleting, start inserting after the delete
+			if (c == 'c') {
+				strcpy(buf, "Change");
+				goto dc_i;	// start inserting
+			}
+			if (c == 'd') {
+				strcpy(buf, "Delete");
+			}
+#if ENABLE_FEATURE_VI_YANKMARK
+			if (c == 'y' || c == 'Y') {
+				strcpy(buf, "Yank");
+			}
+			p = reg[YDreg];
+			q = p + strlen(p);
+			for (cnt = 0; p <= q; p++) {
+				if (*p == '\n')
+					cnt++;
+			}
+			status_line("%s %d lines (%d chars) using [%c]",
+				buf, cnt, strlen(reg[YDreg]), what_reg());
+#endif
+			end_cmd_q();	// stop adding to q
+		}
+		break;
+	}
+	case 'k':			// k- goto prev line, same col
+	case KEYCODE_UP:		// cursor key Up
+		do {
+			dot_prev();
+			dot = move_to_col(dot, ccol + offset);	// try stay in same col
+		} while (--cmdcnt > 0);
+		break;
+	case 'r':			// r- replace the current char with user input
+		c1 = get_one_char();	// get the replacement char
+		if (*dot != '\n') {
+			*dot = c1;
+			file_modified++;
+		}
+		end_cmd_q();	// stop adding to q
+		break;
+	case 't':			// t- move to char prior to next x
+		last_forward_char = get_one_char();
+		do_cmd(';');
+		if (*dot == last_forward_char)
+			dot_left();
+		last_forward_char = 0;
+		break;
+	case 'w':			// w- forward a word
+		do {
+			if (isalnum(*dot) || *dot == '_') {	// we are on ALNUM
+				dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
+			} else if (ispunct(*dot)) {	// we are on PUNCT
+				dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
+			}
+			if (dot < end - 1)
+				dot++;		// move over word
+			if (isspace(*dot)) {
+				dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
+			}
+		} while (--cmdcnt > 0);
+		break;
+	case 'z':			// z-
+		c1 = get_one_char();	// get the replacement char
+		cnt = 0;
+		if (c1 == '.')
+			cnt = (rows - 2) / 2;	// put dot at center
+		if (c1 == '-')
+			cnt = rows - 2;	// put dot at bottom
+		screenbegin = begin_line(dot);	// start dot at top
+		dot_scroll(cnt, -1);
+		break;
+	case '|':			// |- move to column "cmdcnt"
+		dot = move_to_col(dot, cmdcnt - 1);	// try to move to column
+		break;
+	case '~':			// ~- flip the case of letters   a-z -> A-Z
+		do {
+			if (islower(*dot)) {
+				*dot = toupper(*dot);
+				file_modified++;
+			} else if (isupper(*dot)) {
+				*dot = tolower(*dot);
+				file_modified++;
+			}
+			dot_right();
+		} while (--cmdcnt > 0);
+		end_cmd_q();	// stop adding to q
+		break;
+		//----- The Cursor and Function Keys -----------------------------
+	case KEYCODE_HOME:	// Cursor Key Home
+		dot_begin();
+		break;
+		// The Fn keys could point to do_macro which could translate them
+#if 0
+	case KEYCODE_FUN1:	// Function Key F1
+	case KEYCODE_FUN2:	// Function Key F2
+	case KEYCODE_FUN3:	// Function Key F3
+	case KEYCODE_FUN4:	// Function Key F4
+	case KEYCODE_FUN5:	// Function Key F5
+	case KEYCODE_FUN6:	// Function Key F6
+	case KEYCODE_FUN7:	// Function Key F7
+	case KEYCODE_FUN8:	// Function Key F8
+	case KEYCODE_FUN9:	// Function Key F9
+	case KEYCODE_FUN10:	// Function Key F10
+	case KEYCODE_FUN11:	// Function Key F11
+	case KEYCODE_FUN12:	// Function Key F12
+		break;
+#endif
+	}
+
+ dc1:
+	// if text[] just became empty, add back an empty line
+	if (end == text) {
+		char_insert(text, '\n');	// start empty buf with dummy line
+		dot = text;
+	}
+	// it is OK for dot to exactly equal to end, otherwise check dot validity
+	if (dot != end) {
+		dot = bound_dot(dot);	// make sure "dot" is valid
+	}
+#if ENABLE_FEATURE_VI_YANKMARK
+	check_context(c);	// update the current context
+#endif
+
+	if (!isdigit(c))
+		cmdcnt = 0;		// cmd was not a number, reset cmdcnt
+	cnt = dot - begin_line(dot);
+	// Try to stay off of the Newline
+	if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
+		dot--;
+}
+
+/* NB!  the CRASHME code is unmaintained, and doesn't currently build */
+#if ENABLE_FEATURE_VI_CRASHME
+static int totalcmds = 0;
+static int Mp = 85;             // Movement command Probability
+static int Np = 90;             // Non-movement command Probability
+static int Dp = 96;             // Delete command Probability
+static int Ip = 97;             // Insert command Probability
+static int Yp = 98;             // Yank command Probability
+static int Pp = 99;             // Put command Probability
+static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
+static const char chars[20] = "\t012345 abcdABCD-=.$";
+static const char *const words[20] = {
+	"this", "is", "a", "test",
+	"broadcast", "the", "emergency", "of",
+	"system", "quick", "brown", "fox",
+	"jumped", "over", "lazy", "dogs",
+	"back", "January", "Febuary", "March"
+};
+static const char *const lines[20] = {
+	"You should have received a copy of the GNU General Public License\n",
+	"char c, cm, *cmd, *cmd1;\n",
+	"generate a command by percentages\n",
+	"Numbers may be typed as a prefix to some commands.\n",
+	"Quit, discarding changes!\n",
+	"Forced write, if permission originally not valid.\n",
+	"In general, any ex or ed command (such as substitute or delete).\n",
+	"I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+	"Please get w/ me and I will go over it with you.\n",
+	"The following is a list of scheduled, committed changes.\n",
+	"1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+	"Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+	"Any question about transactions please contact Sterling Huxley.\n",
+	"I will try to get back to you by Friday, December 31.\n",
+	"This Change will be implemented on Friday.\n",
+	"Let me know if you have problems accessing this;\n",
+	"Sterling Huxley recently added you to the access list.\n",
+	"Would you like to go to lunch?\n",
+	"The last command will be automatically run.\n",
+	"This is too much english for a computer geek.\n",
+};
+static char *multilines[20] = {
+	"You should have received a copy of the GNU General Public License\n",
+	"char c, cm, *cmd, *cmd1;\n",
+	"generate a command by percentages\n",
+	"Numbers may be typed as a prefix to some commands.\n",
+	"Quit, discarding changes!\n",
+	"Forced write, if permission originally not valid.\n",
+	"In general, any ex or ed command (such as substitute or delete).\n",
+	"I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+	"Please get w/ me and I will go over it with you.\n",
+	"The following is a list of scheduled, committed changes.\n",
+	"1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+	"Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+	"Any question about transactions please contact Sterling Huxley.\n",
+	"I will try to get back to you by Friday, December 31.\n",
+	"This Change will be implemented on Friday.\n",
+	"Let me know if you have problems accessing this;\n",
+	"Sterling Huxley recently added you to the access list.\n",
+	"Would you like to go to lunch?\n",
+	"The last command will be automatically run.\n",
+	"This is too much english for a computer geek.\n",
+};
+
+// create a random command to execute
+static void crash_dummy()
+{
+	static int sleeptime;   // how long to pause between commands
+	char c, cm, *cmd, *cmd1;
+	int i, cnt, thing, rbi, startrbi, percent;
+
+	// "dot" movement commands
+	cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
+
+	// is there already a command running?
+	if (readbuffer[0] > 0)
+		goto cd1;
+ cd0:
+	readbuffer[0] = 'X';
+	startrbi = rbi = 1;
+	sleeptime = 0;          // how long to pause between commands
+	memset(readbuffer, '\0', sizeof(readbuffer));
+	// generate a command by percentages
+	percent = (int) lrand48() % 100;        // get a number from 0-99
+	if (percent < Mp) {     //  Movement commands
+		// available commands
+		cmd = cmd1;
+		M++;
+	} else if (percent < Np) {      //  non-movement commands
+		cmd = "mz<>\'\"";       // available commands
+		N++;
+	} else if (percent < Dp) {      //  Delete commands
+		cmd = "dx";             // available commands
+		D++;
+	} else if (percent < Ip) {      //  Inset commands
+		cmd = "iIaAsrJ";        // available commands
+		I++;
+	} else if (percent < Yp) {      //  Yank commands
+		cmd = "yY";             // available commands
+		Y++;
+	} else if (percent < Pp) {      //  Put commands
+		cmd = "pP";             // available commands
+		P++;
+	} else {
+		// We do not know how to handle this command, try again
+		U++;
+		goto cd0;
+	}
+	// randomly pick one of the available cmds from "cmd[]"
+	i = (int) lrand48() % strlen(cmd);
+	cm = cmd[i];
+	if (strchr(":\024", cm))
+		goto cd0;               // dont allow colon or ctrl-T commands
+	readbuffer[rbi++] = cm; // put cmd into input buffer
+
+	// now we have the command-
+	// there are 1, 2, and multi char commands
+	// find out which and generate the rest of command as necessary
+	if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
+		cmd1 = " \n\r0$^-+wWeEbBhjklHL";
+		if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
+			cmd1 = "abcdefghijklmnopqrstuvwxyz";
+		}
+		thing = (int) lrand48() % strlen(cmd1); // pick a movement command
+		c = cmd1[thing];
+		readbuffer[rbi++] = c;  // add movement to input buffer
+	}
+	if (strchr("iIaAsc", cm)) {     // multi-char commands
+		if (cm == 'c') {
+			// change some thing
+			thing = (int) lrand48() % strlen(cmd1); // pick a movement command
+			c = cmd1[thing];
+			readbuffer[rbi++] = c;  // add movement to input buffer
+		}
+		thing = (int) lrand48() % 4;    // what thing to insert
+		cnt = (int) lrand48() % 10;     // how many to insert
+		for (i = 0; i < cnt; i++) {
+			if (thing == 0) {       // insert chars
+				readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
+			} else if (thing == 1) {        // insert words
+				strcat(readbuffer, words[(int) lrand48() % 20]);
+				strcat(readbuffer, " ");
+				sleeptime = 0;  // how fast to type
+			} else if (thing == 2) {        // insert lines
+				strcat(readbuffer, lines[(int) lrand48() % 20]);
+				sleeptime = 0;  // how fast to type
+			} else {        // insert multi-lines
+				strcat(readbuffer, multilines[(int) lrand48() % 20]);
+				sleeptime = 0;  // how fast to type
+			}
+		}
+		strcat(readbuffer, "\033");
+	}
+	readbuffer[0] = strlen(readbuffer + 1);
+ cd1:
+	totalcmds++;
+	if (sleeptime > 0)
+		mysleep(sleeptime);      // sleep 1/100 sec
+}
+
+// test to see if there are any errors
+static void crash_test()
+{
+	static time_t oldtim;
+
+	time_t tim;
+	char d[2], msg[80];
+
+	msg[0] = '\0';
+	if (end < text) {
+		strcat(msg, "end<text ");
+	}
+	if (end > textend) {
+		strcat(msg, "end>textend ");
+	}
+	if (dot < text) {
+		strcat(msg, "dot<text ");
+	}
+	if (dot > end) {
+		strcat(msg, "dot>end ");
+	}
+	if (screenbegin < text) {
+		strcat(msg, "screenbegin<text ");
+	}
+	if (screenbegin > end - 1) {
+		strcat(msg, "screenbegin>end-1 ");
+	}
+
+	if (msg[0]) {
+		printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
+			totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
+		fflush_all();
+		while (safe_read(STDIN_FILENO, d, 1) > 0) {
+			if (d[0] == '\n' || d[0] == '\r')
+				break;
+		}
+	}
+	tim = time(NULL);
+	if (tim >= (oldtim + 3)) {
+		sprintf(status_buffer,
+				"Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
+				totalcmds, M, N, I, D, Y, P, U, end - text + 1);
+		oldtim = tim;
+	}
+}
+#endif