summaryrefslogtreecommitdiff
path: root/busybox/editors
diff options
context:
space:
mode:
authornobody <nobody@localhost>2004-10-13 09:42:10 +0000
committernobody <nobody@localhost>2004-10-13 09:42:10 +0000
commit8c59a0bf0e9e2d87b0ff273ea3f0bf05bbbf6373 (patch)
tree1826706cd4fd009fcd14f4f8021005ec8ec0fa59 /busybox/editors
downloadbusybox-w32-8c59a0bf0e9e2d87b0ff273ea3f0bf05bbbf6373.tar.gz
busybox-w32-8c59a0bf0e9e2d87b0ff273ea3f0bf05bbbf6373.tar.bz2
busybox-w32-8c59a0bf0e9e2d87b0ff273ea3f0bf05bbbf6373.zip
This commit was manufactured by cvs2svn to create tag 'busybox_1_00'.
Diffstat (limited to 'busybox/editors')
-rw-r--r--busybox/editors/Config.in123
-rw-r--r--busybox/editors/Makefile32
-rw-r--r--busybox/editors/Makefile.in48
-rw-r--r--busybox/editors/awk.c2764
-rw-r--r--busybox/editors/patch.c290
-rw-r--r--busybox/editors/sed.c1220
-rw-r--r--busybox/editors/vi.c3983
7 files changed, 8460 insertions, 0 deletions
diff --git a/busybox/editors/Config.in b/busybox/editors/Config.in
new file mode 100644
index 000000000..bb0285976
--- /dev/null
+++ b/busybox/editors/Config.in
@@ -0,0 +1,123 @@
1#
2# For a description of the syntax of this configuration file,
3# see scripts/kbuild/config-language.txt.
4#
5
6menu "Editors"
7
8config CONFIG_AWK
9 bool "awk"
10 default n
11 help
12 Awk is used as a pattern scanning and processing language. This is
13 the BusyBox implementation of that programming language.
14
15config CONFIG_FEATURE_AWK_MATH
16 bool " Enable math functions (requires libm)"
17 default y
18 depends on CONFIG_AWK
19 help
20 Enable math functions of the Awk programming language.
21 NOTE: This will require libm to be present for linking.
22
23config CONFIG_PATCH
24 bool "patch"
25 default n
26 help
27 Apply a unified diff formatted patch.
28
29config CONFIG_SED
30 bool "sed"
31 default n
32 help
33 sed is used to perform text transformations on a file
34 or input from a pipeline.
35
36config CONFIG_VI
37 bool "vi"
38 default n
39 help
40 'vi' is a text editor. More specifically, it is the One True
41 text editor <grin>. It does, however, have a rather steep
42 learning curve. If you are not already comfortable with 'vi'
43 you may wish to use something else.
44
45config CONFIG_FEATURE_VI_COLON
46 bool " Enable \":\" colon commands (no \"ex\" mode)"
47 default y
48 depends on CONFIG_VI
49 help
50 Enable a limited set of colon commands for vi. This does not
51 provide an "ex" mode.
52
53config CONFIG_FEATURE_VI_YANKMARK
54 bool " Enable yank/put commands and mark cmds"
55 default y
56 depends on CONFIG_VI
57 help
58 This will enable you to use yank and put, as well as mark in
59 busybox vi.
60
61config CONFIG_FEATURE_VI_SEARCH
62 bool " Enable search and replace cmds"
63 default y
64 depends on CONFIG_VI
65 help
66 Select this if you wish to be able to do search and replace in
67 busybox vi.
68
69config CONFIG_FEATURE_VI_USE_SIGNALS
70 bool " Catch signals"
71 default y
72 depends on CONFIG_VI
73 help
74 Selecting this option will make busybox vi signal aware. This will
75 make busybox vi support SIGWINCH to deal with Window Changes, catch
76 Ctrl-Z and Ctrl-C and alarms.
77
78config CONFIG_FEATURE_VI_DOT_CMD
79 bool " Remember previous cmd and \".\" cmd"
80 default y
81 depends on CONFIG_VI
82 help
83 Make busybox vi remember the last command and be able to repeat it.
84
85config CONFIG_FEATURE_VI_READONLY
86 bool " Enable -R option and \"view\" mode"
87 default y
88 depends on CONFIG_VI
89 help
90 Enable the read-only command line option, which allows the user to
91 open a file in read-only mode.
92
93config CONFIG_FEATURE_VI_SETOPTS
94 bool " Enable set-able options, ai ic showmatch"
95 default y
96 depends on CONFIG_VI
97 help
98 Enable the editor to set some (ai, ic, showmatch) options.
99
100config CONFIG_FEATURE_VI_SET
101 bool " Support for :set"
102 default y
103 depends on CONFIG_VI
104 help
105 Support for ":set".
106
107config CONFIG_FEATURE_VI_WIN_RESIZE
108 bool " Handle window resize"
109 default y
110 depends on CONFIG_VI
111 help
112 Make busybox vi behave nicely with terminals that get resized.
113
114config CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
115 bool " Optimize cursor movement"
116 default y
117 depends on CONFIG_VI
118 help
119 This will make the cursor movement faster, but requires more memory
120 and it makes the applet a tiny bit larger.
121
122endmenu
123
diff --git a/busybox/editors/Makefile b/busybox/editors/Makefile
new file mode 100644
index 000000000..e6c114781
--- /dev/null
+++ b/busybox/editors/Makefile
@@ -0,0 +1,32 @@
1# Makefile for busybox
2#
3# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19
20top_srcdir=..
21top_builddir=..
22srcdir=$(top_srcdir)/editors
23EDITOR_DIR:=./
24include $(top_builddir)/Rules.mak
25include $(top_builddir)/.config
26include $(srcdir)/Makefile.in
27all: $(libraries-y)
28-include $(top_builddir)/.depend
29
30clean:
31 rm -f *.o *.a $(AR_TARGET)
32
diff --git a/busybox/editors/Makefile.in b/busybox/editors/Makefile.in
new file mode 100644
index 000000000..571e05568
--- /dev/null
+++ b/busybox/editors/Makefile.in
@@ -0,0 +1,48 @@
1# Makefile for busybox
2#
3# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19
20EDITOR_AR:=editors.a
21ifndef $(EDITOR_DIR)
22EDITOR_DIR:=$(top_builddir)/editors/
23endif
24srcdir=$(top_srcdir)/editors
25
26EDITOR-y:=
27EDITOR-$(CONFIG_AWK) += awk.o
28EDITOR-$(CONFIG_PATCH) += patch.o
29EDITOR-$(CONFIG_SED) += sed.o
30EDITOR-$(CONFIG_VI) += vi.o
31EDITOR_SRC:= $(EDITOR-y)
32EDITOR_OBJ:= $(patsubst %.c,$(EDITOR_DIR)%.o, $(EDITOR_SRC))
33
34libraries-y+=$(EDITOR_DIR)$(EDITOR_AR)
35
36needlibm-y:=
37needlibm-$(CONFIG_FEATURE_AWK_MATH) := y
38
39ifeq ($(needlibm-y),y)
40 LIBRARIES += -lm
41endif
42
43$(EDITOR_DIR)$(EDITOR_AR): $(patsubst %,$(EDITOR_DIR)%, $(EDITOR-y))
44 $(AR) -ro $@ $(patsubst %,$(EDITOR_DIR)%, $(EDITOR-y))
45
46$(EDITOR_DIR)%.o: $(srcdir)/%.c
47 $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<
48
diff --git a/busybox/editors/awk.c b/busybox/editors/awk.c
new file mode 100644
index 000000000..c1cb2a2e2
--- /dev/null
+++ b/busybox/editors/awk.c
@@ -0,0 +1,2764 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * awk implementation for busybox
4 *
5 * Copyright (C) 2002 by Dmitry Zakharov <dmit@crp.bank.gov.ua>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <unistd.h>
26#include <errno.h>
27#include <string.h>
28#include <time.h>
29#include <math.h>
30#include <ctype.h>
31#include <getopt.h>
32#include <regex.h>
33
34#include "busybox.h"
35
36
37#define MAXVARFMT 240
38#define MINNVBLOCK 64
39
40/* variable flags */
41#define VF_NUMBER 0x0001 /* 1 = primary type is number */
42#define VF_ARRAY 0x0002 /* 1 = it's an array */
43
44#define VF_CACHED 0x0100 /* 1 = num/str value has cached str/num eq */
45#define VF_USER 0x0200 /* 1 = user input (may be numeric string) */
46#define VF_SPECIAL 0x0400 /* 1 = requires extra handling when changed */
47#define VF_WALK 0x0800 /* 1 = variable has alloc'd x.walker list */
48#define VF_FSTR 0x1000 /* 1 = string points to fstring buffer */
49#define VF_CHILD 0x2000 /* 1 = function arg; x.parent points to source */
50#define VF_DIRTY 0x4000 /* 1 = variable was set explicitly */
51
52/* these flags are static, don't change them when value is changed */
53#define VF_DONTTOUCH (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY)
54
55/* Variable */
56typedef struct var_s {
57 unsigned short type; /* flags */
58 double number;
59 char *string;
60 union {
61 int aidx; /* func arg index (on compilation stage) */
62 struct xhash_s *array; /* array ptr */
63 struct var_s *parent; /* for func args, ptr to actual parameter */
64 char **walker; /* list of array elements (for..in) */
65 } x;
66} var;
67
68/* Node chain (pattern-action chain, BEGIN, END, function bodies) */
69typedef struct chain_s {
70 struct node_s *first;
71 struct node_s *last;
72 char *programname;
73} chain;
74
75/* Function */
76typedef struct func_s {
77 unsigned short nargs;
78 struct chain_s body;
79} func;
80
81/* I/O stream */
82typedef struct rstream_s {
83 FILE *F;
84 char *buffer;
85 int adv;
86 int size;
87 int pos;
88 unsigned short is_pipe;
89} rstream;
90
91typedef struct hash_item_s {
92 union {
93 struct var_s v; /* variable/array hash */
94 struct rstream_s rs; /* redirect streams hash */
95 struct func_s f; /* functions hash */
96 } data;
97 struct hash_item_s *next; /* next in chain */
98 char name[1]; /* really it's longer */
99} hash_item;
100
101typedef struct xhash_s {
102 unsigned int nel; /* num of elements */
103 unsigned int csize; /* current hash size */
104 unsigned int nprime; /* next hash size in PRIMES[] */
105 unsigned int glen; /* summary length of item names */
106 struct hash_item_s **items;
107} xhash;
108
109/* Tree node */
110typedef struct node_s {
111 unsigned long info;
112 unsigned short lineno;
113 union {
114 struct node_s *n;
115 var *v;
116 int i;
117 char *s;
118 regex_t *re;
119 } l;
120 union {
121 struct node_s *n;
122 regex_t *ire;
123 func *f;
124 int argno;
125 } r;
126 union {
127 struct node_s *n;
128 } a;
129} node;
130
131/* Block of temporary variables */
132typedef struct nvblock_s {
133 int size;
134 var *pos;
135 struct nvblock_s *prev;
136 struct nvblock_s *next;
137 var nv[0];
138} nvblock;
139
140typedef struct tsplitter_s {
141 node n;
142 regex_t re[2];
143} tsplitter;
144
145/* simple token classes */
146/* Order and hex values are very important!!! See next_token() */
147#define TC_SEQSTART 1 /* ( */
148#define TC_SEQTERM (1 << 1) /* ) */
149#define TC_REGEXP (1 << 2) /* /.../ */
150#define TC_OUTRDR (1 << 3) /* | > >> */
151#define TC_UOPPOST (1 << 4) /* unary postfix operator */
152#define TC_UOPPRE1 (1 << 5) /* unary prefix operator */
153#define TC_BINOPX (1 << 6) /* two-opnd operator */
154#define TC_IN (1 << 7)
155#define TC_COMMA (1 << 8)
156#define TC_PIPE (1 << 9) /* input redirection pipe */
157#define TC_UOPPRE2 (1 << 10) /* unary prefix operator */
158#define TC_ARRTERM (1 << 11) /* ] */
159#define TC_GRPSTART (1 << 12) /* { */
160#define TC_GRPTERM (1 << 13) /* } */
161#define TC_SEMICOL (1 << 14)
162#define TC_NEWLINE (1 << 15)
163#define TC_STATX (1 << 16) /* ctl statement (for, next...) */
164#define TC_WHILE (1 << 17)
165#define TC_ELSE (1 << 18)
166#define TC_BUILTIN (1 << 19)
167#define TC_GETLINE (1 << 20)
168#define TC_FUNCDECL (1 << 21) /* `function' `func' */
169#define TC_BEGIN (1 << 22)
170#define TC_END (1 << 23)
171#define TC_EOF (1 << 24)
172#define TC_VARIABLE (1 << 25)
173#define TC_ARRAY (1 << 26)
174#define TC_FUNCTION (1 << 27)
175#define TC_STRING (1 << 28)
176#define TC_NUMBER (1 << 29)
177
178#define TC_UOPPRE (TC_UOPPRE1 | TC_UOPPRE2)
179
180/* combined token classes */
181#define TC_BINOP (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
182#define TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
183#define TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION | \
184 TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER)
185
186#define TC_STATEMNT (TC_STATX | TC_WHILE)
187#define TC_OPTERM (TC_SEMICOL | TC_NEWLINE)
188
189/* word tokens, cannot mean something else if not expected */
190#define TC_WORD (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN | \
191 TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END)
192
193/* discard newlines after these */
194#define TC_NOTERM (TC_COMMA | TC_GRPSTART | TC_GRPTERM | \
195 TC_BINOP | TC_OPTERM)
196
197/* what can expression begin with */
198#define TC_OPSEQ (TC_OPERAND | TC_UOPPRE | TC_REGEXP)
199/* what can group begin with */
200#define TC_GRPSEQ (TC_OPSEQ | TC_OPTERM | TC_STATEMNT | TC_GRPSTART)
201
202/* if previous token class is CONCAT1 and next is CONCAT2, concatenation */
203/* operator is inserted between them */
204#define TC_CONCAT1 (TC_VARIABLE | TC_ARRTERM | TC_SEQTERM | \
205 TC_STRING | TC_NUMBER | TC_UOPPOST)
206#define TC_CONCAT2 (TC_OPERAND | TC_UOPPRE)
207
208#define OF_RES1 0x010000
209#define OF_RES2 0x020000
210#define OF_STR1 0x040000
211#define OF_STR2 0x080000
212#define OF_NUM1 0x100000
213#define OF_CHECKED 0x200000
214
215/* combined operator flags */
216#define xx 0
217#define xV OF_RES2
218#define xS (OF_RES2 | OF_STR2)
219#define Vx OF_RES1
220#define VV (OF_RES1 | OF_RES2)
221#define Nx (OF_RES1 | OF_NUM1)
222#define NV (OF_RES1 | OF_NUM1 | OF_RES2)
223#define Sx (OF_RES1 | OF_STR1)
224#define SV (OF_RES1 | OF_STR1 | OF_RES2)
225#define SS (OF_RES1 | OF_STR1 | OF_RES2 | OF_STR2)
226
227#define OPCLSMASK 0xFF00
228#define OPNMASK 0x007F
229
230/* operator priority is a highest byte (even: r->l, odd: l->r grouping)
231 * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1,
232 * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string
233 */
234#define P(x) (x << 24)
235#define PRIMASK 0x7F000000
236#define PRIMASK2 0x7E000000
237
238/* Operation classes */
239
240#define SHIFT_TIL_THIS 0x0600
241#define RECUR_FROM_THIS 0x1000
242
243enum {
244 OC_DELETE=0x0100, OC_EXEC=0x0200, OC_NEWSOURCE=0x0300,
245 OC_PRINT=0x0400, OC_PRINTF=0x0500, OC_WALKINIT=0x0600,
246
247 OC_BR=0x0700, OC_BREAK=0x0800, OC_CONTINUE=0x0900,
248 OC_EXIT=0x0a00, OC_NEXT=0x0b00, OC_NEXTFILE=0x0c00,
249 OC_TEST=0x0d00, OC_WALKNEXT=0x0e00,
250
251 OC_BINARY=0x1000, OC_BUILTIN=0x1100, OC_COLON=0x1200,
252 OC_COMMA=0x1300, OC_COMPARE=0x1400, OC_CONCAT=0x1500,
253 OC_FBLTIN=0x1600, OC_FIELD=0x1700, OC_FNARG=0x1800,
254 OC_FUNC=0x1900, OC_GETLINE=0x1a00, OC_IN=0x1b00,
255 OC_LAND=0x1c00, OC_LOR=0x1d00, OC_MATCH=0x1e00,
256 OC_MOVE=0x1f00, OC_PGETLINE=0x2000, OC_REGEXP=0x2100,
257 OC_REPLACE=0x2200, OC_RETURN=0x2300, OC_SPRINTF=0x2400,
258 OC_TERNARY=0x2500, OC_UNARY=0x2600, OC_VAR=0x2700,
259 OC_DONE=0x2800,
260
261 ST_IF=0x3000, ST_DO=0x3100, ST_FOR=0x3200,
262 ST_WHILE=0x3300
263};
264
265/* simple builtins */
266enum {
267 F_in=0, F_rn, F_co, F_ex, F_lg, F_si, F_sq, F_sr,
268 F_ti, F_le, F_sy, F_ff, F_cl
269};
270
271/* builtins */
272enum {
273 B_a2=0, B_ix, B_ma, B_sp, B_ss, B_ti, B_lo, B_up,
274 B_ge, B_gs, B_su
275};
276
277/* tokens and their corresponding info values */
278
279#define NTC "\377" /* switch to next token class (tc<<1) */
280#define NTCC '\377'
281
282#define OC_B OC_BUILTIN
283
284static char * const tokenlist =
285 "\1(" NTC
286 "\1)" NTC
287 "\1/" NTC /* REGEXP */
288 "\2>>" "\1>" "\1|" NTC /* OUTRDR */
289 "\2++" "\2--" NTC /* UOPPOST */
290 "\2++" "\2--" "\1$" NTC /* UOPPRE1 */
291 "\2==" "\1=" "\2+=" "\2-=" /* BINOPX */
292 "\2*=" "\2/=" "\2%=" "\2^="
293 "\1+" "\1-" "\3**=" "\2**"
294 "\1/" "\1%" "\1^" "\1*"
295 "\2!=" "\2>=" "\2<=" "\1>"
296 "\1<" "\2!~" "\1~" "\2&&"
297 "\2||" "\1?" "\1:" NTC
298 "\2in" NTC
299 "\1," NTC
300 "\1|" NTC
301 "\1+" "\1-" "\1!" NTC /* UOPPRE2 */
302 "\1]" NTC
303 "\1{" NTC
304 "\1}" NTC
305 "\1;" NTC
306 "\1\n" NTC
307 "\2if" "\2do" "\3for" "\5break" /* STATX */
308 "\10continue" "\6delete" "\5print"
309 "\6printf" "\4next" "\10nextfile"
310 "\6return" "\4exit" NTC
311 "\5while" NTC
312 "\4else" NTC
313
314 "\5close" "\6system" "\6fflush" "\5atan2" /* BUILTIN */
315 "\3cos" "\3exp" "\3int" "\3log"
316 "\4rand" "\3sin" "\4sqrt" "\5srand"
317 "\6gensub" "\4gsub" "\5index" "\6length"
318 "\5match" "\5split" "\7sprintf" "\3sub"
319 "\6substr" "\7systime" "\10strftime"
320 "\7tolower" "\7toupper" NTC
321 "\7getline" NTC
322 "\4func" "\10function" NTC
323 "\5BEGIN" NTC
324 "\3END" "\0"
325 ;
326
327static unsigned long tokeninfo[] = {
328
329 0,
330 0,
331 OC_REGEXP,
332 xS|'a', xS|'w', xS|'|',
333 OC_UNARY|xV|P(9)|'p', OC_UNARY|xV|P(9)|'m',
334 OC_UNARY|xV|P(9)|'P', OC_UNARY|xV|P(9)|'M',
335 OC_FIELD|xV|P(5),
336 OC_COMPARE|VV|P(39)|5, OC_MOVE|VV|P(74),
337 OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-',
338 OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/',
339 OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&',
340 OC_BINARY|NV|P(29)|'+', OC_BINARY|NV|P(29)|'-',
341 OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&',
342 OC_BINARY|NV|P(25)|'/', OC_BINARY|NV|P(25)|'%',
343 OC_BINARY|NV|P(15)|'&', OC_BINARY|NV|P(25)|'*',
344 OC_COMPARE|VV|P(39)|4, OC_COMPARE|VV|P(39)|3,
345 OC_COMPARE|VV|P(39)|0, OC_COMPARE|VV|P(39)|1,
346 OC_COMPARE|VV|P(39)|2, OC_MATCH|Sx|P(45)|'!',
347 OC_MATCH|Sx|P(45)|'~', OC_LAND|Vx|P(55),
348 OC_LOR|Vx|P(59), OC_TERNARY|Vx|P(64)|'?',
349 OC_COLON|xx|P(67)|':',
350 OC_IN|SV|P(49),
351 OC_COMMA|SS|P(80),
352 OC_PGETLINE|SV|P(37),
353 OC_UNARY|xV|P(19)|'+', OC_UNARY|xV|P(19)|'-',
354 OC_UNARY|xV|P(19)|'!',
355 0,
356 0,
357 0,
358 0,
359 0,
360 ST_IF, ST_DO, ST_FOR, OC_BREAK,
361 OC_CONTINUE, OC_DELETE|Vx, OC_PRINT,
362 OC_PRINTF, OC_NEXT, OC_NEXTFILE,
363 OC_RETURN|Vx, OC_EXIT|Nx,
364 ST_WHILE,
365 0,
366
367 OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83),
368 OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg,
369 OC_FBLTIN|F_rn, OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr,
370 OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le,
371 OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF, OC_B|B_su|P(0xb6),
372 OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti, OC_B|B_ti|P(0x0b),
373 OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49),
374 OC_GETLINE|SV|P(0),
375 0, 0,
376 0,
377 0
378};
379
380/* internal variable names and their initial values */
381/* asterisk marks SPECIAL vars; $ is just no-named Field0 */
382enum {
383 CONVFMT=0, OFMT, FS, OFS,
384 ORS, RS, RT, FILENAME,
385 SUBSEP, ARGIND, ARGC, ARGV,
386 ERRNO, FNR,
387 NR, NF, IGNORECASE,
388 ENVIRON, F0, _intvarcount_
389};
390
391static char * vNames =
392 "CONVFMT\0" "OFMT\0" "FS\0*" "OFS\0"
393 "ORS\0" "RS\0*" "RT\0" "FILENAME\0"
394 "SUBSEP\0" "ARGIND\0" "ARGC\0" "ARGV\0"
395 "ERRNO\0" "FNR\0"
396 "NR\0" "NF\0*" "IGNORECASE\0*"
397 "ENVIRON\0" "$\0*" "\0";
398
399static char * vValues =
400 "%.6g\0" "%.6g\0" " \0" " \0"
401 "\n\0" "\n\0" "\0" "\0"
402 "\034\0"
403 "\377";
404
405/* hash size may grow to these values */
406#define FIRST_PRIME 61;
407static const unsigned int PRIMES[] = { 251, 1021, 4093, 16381, 65521 };
408static const unsigned int NPRIMES = sizeof(PRIMES) / sizeof(unsigned int);
409
410/* globals */
411
412extern char **environ;
413
414static var * V[_intvarcount_];
415static chain beginseq, mainseq, endseq, *seq;
416static int nextrec, nextfile;
417static node *break_ptr, *continue_ptr;
418static rstream *iF;
419static xhash *vhash, *ahash, *fdhash, *fnhash;
420static char *programname;
421static short lineno;
422static int is_f0_split;
423static int nfields = 0;
424static var *Fields = NULL;
425static tsplitter fsplitter, rsplitter;
426static nvblock *cb = NULL;
427static char *pos;
428static char *buf;
429static int icase = FALSE;
430static int exiting = FALSE;
431
432static struct {
433 unsigned long tclass;
434 unsigned long info;
435 char *string;
436 double number;
437 short lineno;
438 int rollback;
439} t;
440
441/* function prototypes */
442extern void xregcomp(regex_t *preg, const char *regex, int cflags);
443static void handle_special(var *);
444static node *parse_expr(unsigned long);
445static void chain_group(void);
446static var *evaluate(node *, var *);
447static rstream *next_input_file(void);
448static int fmt_num(char *, int, char *, double, int);
449static int awk_exit(int);
450
451/* ---- error handling ---- */
452
453static const char EMSG_INTERNAL_ERROR[] = "Internal error";
454static const char EMSG_UNEXP_EOS[] = "Unexpected end of string";
455static const char EMSG_UNEXP_TOKEN[] = "Unexpected token";
456static const char EMSG_DIV_BY_ZERO[] = "Division by zero";
457static const char EMSG_INV_FMT[] = "Invalid format specifier";
458static const char EMSG_TOO_FEW_ARGS[] = "Too few arguments for builtin";
459static const char EMSG_NOT_ARRAY[] = "Not an array";
460static const char EMSG_POSSIBLE_ERROR[] = "Possible syntax error";
461static const char EMSG_UNDEF_FUNC[] = "Call to undefined function";
462#ifndef CONFIG_FEATURE_AWK_MATH
463static const char EMSG_NO_MATH[] = "Math support is not compiled in";
464#endif
465
466static void syntax_error(const char * const message)
467{
468 bb_error_msg("%s:%i: %s", programname, lineno, message);
469 exit(1);
470}
471
472#define runtime_error(x) syntax_error(x)
473
474
475/* ---- hash stuff ---- */
476
477static unsigned int hashidx(char *name) {
478
479 register unsigned int idx=0;
480
481 while (*name) idx = *name++ + (idx << 6) - idx;
482 return idx;
483}
484
485/* create new hash */
486static xhash *hash_init(void) {
487
488 xhash *newhash;
489
490 newhash = (xhash *)xcalloc(1, sizeof(xhash));
491 newhash->csize = FIRST_PRIME;
492 newhash->items = (hash_item **)xcalloc(newhash->csize, sizeof(hash_item *));
493
494 return newhash;
495}
496
497/* find item in hash, return ptr to data, NULL if not found */
498static void *hash_search(xhash *hash, char *name) {
499
500 hash_item *hi;
501
502 hi = hash->items [ hashidx(name) % hash->csize ];
503 while (hi) {
504 if (strcmp(hi->name, name) == 0)
505 return &(hi->data);
506 hi = hi->next;
507 }
508 return NULL;
509}
510
511/* grow hash if it becomes too big */
512static void hash_rebuild(xhash *hash) {
513
514 unsigned int newsize, i, idx;
515 hash_item **newitems, *hi, *thi;
516
517 if (hash->nprime == NPRIMES)
518 return;
519
520 newsize = PRIMES[hash->nprime++];
521 newitems = (hash_item **)xcalloc(newsize, sizeof(hash_item *));
522
523 for (i=0; i<hash->csize; i++) {
524 hi = hash->items[i];
525 while (hi) {
526 thi = hi;
527 hi = thi->next;
528 idx = hashidx(thi->name) % newsize;
529 thi->next = newitems[idx];
530 newitems[idx] = thi;
531 }
532 }
533
534 free(hash->items);
535 hash->csize = newsize;
536 hash->items = newitems;
537}
538
539/* find item in hash, add it if necessary. Return ptr to data */
540static void *hash_find(xhash *hash, char *name) {
541
542 hash_item *hi;
543 unsigned int idx;
544 int l;
545
546 hi = hash_search(hash, name);
547 if (! hi) {
548 if (++hash->nel / hash->csize > 10)
549 hash_rebuild(hash);
550
551 l = bb_strlen(name) + 1;
552 hi = xcalloc(sizeof(hash_item) + l, 1);
553 memcpy(hi->name, name, l);
554
555 idx = hashidx(name) % hash->csize;
556 hi->next = hash->items[idx];
557 hash->items[idx] = hi;
558 hash->glen += l;
559 }
560 return &(hi->data);
561}
562
563#define findvar(hash, name) (var *) hash_find ( (hash) , (name) )
564#define newvar(name) (var *) hash_find ( vhash , (name) )
565#define newfile(name) (rstream *) hash_find ( fdhash , (name) )
566#define newfunc(name) (func *) hash_find ( fnhash , (name) )
567
568static void hash_remove(xhash *hash, char *name) {
569
570 hash_item *hi, **phi;
571
572 phi = &(hash->items[ hashidx(name) % hash->csize ]);
573 while (*phi) {
574 hi = *phi;
575 if (strcmp(hi->name, name) == 0) {
576 hash->glen -= (bb_strlen(name) + 1);
577 hash->nel--;
578 *phi = hi->next;
579 free(hi);
580 break;
581 }
582 phi = &(hi->next);
583 }
584}
585
586/* ------ some useful functions ------ */
587
588static void skip_spaces(char **s) {
589
590 register char *p = *s;
591
592 while(*p == ' ' || *p == '\t' ||
593 (*p == '\\' && *(p+1) == '\n' && (++p, ++t.lineno))) {
594 p++;
595 }
596 *s = p;
597}
598
599static char *nextword(char **s) {
600
601 register char *p = *s;
602
603 while (*(*s)++) ;
604
605 return p;
606}
607
608static char nextchar(char **s) {
609
610 register char c, *pps;
611
612 c = *((*s)++);
613 pps = *s;
614 if (c == '\\') c = bb_process_escape_sequence((const char**)s);
615 if (c == '\\' && *s == pps) c = *((*s)++);
616 return c;
617}
618
619static inline int isalnum_(int c) {
620
621 return (isalnum(c) || c == '_');
622}
623
624static FILE *afopen(const char *path, const char *mode) {
625
626 return (*path == '-' && *(path+1) == '\0') ? stdin : bb_xfopen(path, mode);
627}
628
629/* -------- working with variables (set/get/copy/etc) -------- */
630
631static xhash *iamarray(var *v) {
632
633 var *a = v;
634
635 while (a->type & VF_CHILD)
636 a = a->x.parent;
637
638 if (! (a->type & VF_ARRAY)) {
639 a->type |= VF_ARRAY;
640 a->x.array = hash_init();
641 }
642 return a->x.array;
643}
644
645static void clear_array(xhash *array) {
646
647 unsigned int i;
648 hash_item *hi, *thi;
649
650 for (i=0; i<array->csize; i++) {
651 hi = array->items[i];
652 while (hi) {
653 thi = hi;
654 hi = hi->next;
655 free(thi->data.v.string);
656 free(thi);
657 }
658 array->items[i] = NULL;
659 }
660 array->glen = array->nel = 0;
661}
662
663/* clear a variable */
664static var *clrvar(var *v) {
665
666 if (!(v->type & VF_FSTR))
667 free(v->string);
668
669 v->type &= VF_DONTTOUCH;
670 v->type |= VF_DIRTY;
671 v->string = NULL;
672 return v;
673}
674
675/* assign string value to variable */
676static var *setvar_p(var *v, char *value) {
677
678 clrvar(v);
679 v->string = value;
680 handle_special(v);
681
682 return v;
683}
684
685/* same as setvar_p but make a copy of string */
686static var *setvar_s(var *v, char *value) {
687
688 return setvar_p(v, (value && *value) ? bb_xstrdup(value) : NULL);
689}
690
691/* same as setvar_s but set USER flag */
692static var *setvar_u(var *v, char *value) {
693
694 setvar_s(v, value);
695 v->type |= VF_USER;
696 return v;
697}
698
699/* set array element to user string */
700static void setari_u(var *a, int idx, char *s) {
701
702 register var *v;
703 static char sidx[12];
704
705 sprintf(sidx, "%d", idx);
706 v = findvar(iamarray(a), sidx);
707 setvar_u(v, s);
708}
709
710/* assign numeric value to variable */
711static var *setvar_i(var *v, double value) {
712
713 clrvar(v);
714 v->type |= VF_NUMBER;
715 v->number = value;
716 handle_special(v);
717 return v;
718}
719
720static char *getvar_s(var *v) {
721
722 /* if v is numeric and has no cached string, convert it to string */
723 if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) {
724 fmt_num(buf, MAXVARFMT, getvar_s(V[CONVFMT]), v->number, TRUE);
725 v->string = bb_xstrdup(buf);
726 v->type |= VF_CACHED;
727 }
728 return (v->string == NULL) ? "" : v->string;
729}
730
731static double getvar_i(var *v) {
732
733 char *s;
734
735 if ((v->type & (VF_NUMBER | VF_CACHED)) == 0) {
736 v->number = 0;
737 s = v->string;
738 if (s && *s) {
739 v->number = strtod(s, &s);
740 if (v->type & VF_USER) {
741 skip_spaces(&s);
742 if (*s != '\0')
743 v->type &= ~VF_USER;
744 }
745 } else {
746 v->type &= ~VF_USER;
747 }
748 v->type |= VF_CACHED;
749 }
750 return v->number;
751}
752
753static var *copyvar(var *dest, var *src) {
754
755 if (dest != src) {
756 clrvar(dest);
757 dest->type |= (src->type & ~VF_DONTTOUCH);
758 dest->number = src->number;
759 if (src->string)
760 dest->string = bb_xstrdup(src->string);
761 }
762 handle_special(dest);
763 return dest;
764}
765
766static var *incvar(var *v) {
767
768 return setvar_i(v, getvar_i(v)+1.);
769}
770
771/* return true if v is number or numeric string */
772static int is_numeric(var *v) {
773
774 getvar_i(v);
775 return ((v->type ^ VF_DIRTY) & (VF_NUMBER | VF_USER | VF_DIRTY));
776}
777
778/* return 1 when value of v corresponds to true, 0 otherwise */
779static int istrue(var *v) {
780
781 if (is_numeric(v))
782 return (v->number == 0) ? 0 : 1;
783 else
784 return (v->string && *(v->string)) ? 1 : 0;
785}
786
787/* temporary variables allocator. Last allocated should be first freed */
788static var *nvalloc(int n) {
789
790 nvblock *pb = NULL;
791 var *v, *r;
792 int size;
793
794 while (cb) {
795 pb = cb;
796 if ((cb->pos - cb->nv) + n <= cb->size) break;
797 cb = cb->next;
798 }
799
800 if (! cb) {
801 size = (n <= MINNVBLOCK) ? MINNVBLOCK : n;
802 cb = (nvblock *)xmalloc(sizeof(nvblock) + size * sizeof(var));
803 cb->size = size;
804 cb->pos = cb->nv;
805 cb->prev = pb;
806 cb->next = NULL;
807 if (pb) pb->next = cb;
808 }
809
810 v = r = cb->pos;
811 cb->pos += n;
812
813 while (v < cb->pos) {
814 v->type = 0;
815 v->string = NULL;
816 v++;
817 }
818
819 return r;
820}
821
822static void nvfree(var *v) {
823
824 var *p;
825
826 if (v < cb->nv || v >= cb->pos)
827 runtime_error(EMSG_INTERNAL_ERROR);
828
829 for (p=v; p<cb->pos; p++) {
830 if ((p->type & (VF_ARRAY|VF_CHILD)) == VF_ARRAY) {
831 clear_array(iamarray(p));
832 free(p->x.array->items);
833 free(p->x.array);
834 }
835 if (p->type & VF_WALK)
836 free(p->x.walker);
837
838 clrvar(p);
839 }
840
841 cb->pos = v;
842 while (cb->prev && cb->pos == cb->nv) {
843 cb = cb->prev;
844 }
845}
846
847/* ------- awk program text parsing ------- */
848
849/* Parse next token pointed by global pos, place results into global t.
850 * If token isn't expected, give away. Return token class
851 */
852static unsigned long next_token(unsigned long expected) {
853
854 char *p, *pp, *s;
855 char *tl;
856 unsigned long tc, *ti;
857 int l;
858 static int concat_inserted = FALSE;
859 static unsigned long save_tclass, save_info;
860 static unsigned long ltclass = TC_OPTERM;
861
862 if (t.rollback) {
863
864 t.rollback = FALSE;
865
866 } else if (concat_inserted) {
867
868 concat_inserted = FALSE;
869 t.tclass = save_tclass;
870 t.info = save_info;
871
872 } else {
873
874 p = pos;
875
876 readnext:
877 skip_spaces(&p);
878 lineno = t.lineno;
879 if (*p == '#')
880 while (*p != '\n' && *p != '\0') p++;
881
882 if (*p == '\n')
883 t.lineno++;
884
885 if (*p == '\0') {
886 tc = TC_EOF;
887
888 } else if (*p == '\"') {
889 /* it's a string */
890 t.string = s = ++p;
891 while (*p != '\"') {
892 if (*p == '\0' || *p == '\n')
893 syntax_error(EMSG_UNEXP_EOS);
894 *(s++) = nextchar(&p);
895 }
896 p++;
897 *s = '\0';
898 tc = TC_STRING;
899
900 } else if ((expected & TC_REGEXP) && *p == '/') {
901 /* it's regexp */
902 t.string = s = ++p;
903 while (*p != '/') {
904 if (*p == '\0' || *p == '\n')
905 syntax_error(EMSG_UNEXP_EOS);
906 if ((*s++ = *p++) == '\\') {
907 pp = p;
908 *(s-1) = bb_process_escape_sequence((const char **)&p);
909 if (*pp == '\\') *s++ = '\\';
910 if (p == pp) *s++ = *p++;
911 }
912 }
913 p++;
914 *s = '\0';
915 tc = TC_REGEXP;
916
917 } else if (*p == '.' || isdigit(*p)) {
918 /* it's a number */
919 t.number = strtod(p, &p);
920 if (*p == '.')
921 syntax_error(EMSG_UNEXP_TOKEN);
922 tc = TC_NUMBER;
923
924 } else {
925 /* search for something known */
926 tl = tokenlist;
927 tc = 0x00000001;
928 ti = tokeninfo;
929 while (*tl) {
930 l = *(tl++);
931 if (l == NTCC) {
932 tc <<= 1;
933 continue;
934 }
935 /* if token class is expected, token
936 * matches and it's not a longer word,
937 * then this is what we are looking for
938 */
939 if ((tc & (expected | TC_WORD | TC_NEWLINE)) &&
940 *tl == *p && strncmp(p, tl, l) == 0 &&
941 !((tc & TC_WORD) && isalnum_(*(p + l)))) {
942 t.info = *ti;
943 p += l;
944 break;
945 }
946 ti++;
947 tl += l;
948 }
949
950 if (! *tl) {
951 /* it's a name (var/array/function),
952 * otherwise it's something wrong
953 */
954 if (! isalnum_(*p))
955 syntax_error(EMSG_UNEXP_TOKEN);
956
957 t.string = --p;
958 while(isalnum_(*(++p))) {
959 *(p-1) = *p;
960 }
961 *(p-1) = '\0';
962 tc = TC_VARIABLE;
963 if (*p == '(') {
964 tc = TC_FUNCTION;
965 } else {
966 skip_spaces(&p);
967 if (*p == '[') {
968 p++;
969 tc = TC_ARRAY;
970 }
971 }
972 }
973 }
974 pos = p;
975
976 /* skipping newlines in some cases */
977 if ((ltclass & TC_NOTERM) && (tc & TC_NEWLINE))
978 goto readnext;
979
980 /* insert concatenation operator when needed */
981 if ((ltclass&TC_CONCAT1) && (tc&TC_CONCAT2) && (expected&TC_BINOP)) {
982 concat_inserted = TRUE;
983 save_tclass = tc;
984 save_info = t.info;
985 tc = TC_BINOP;
986 t.info = OC_CONCAT | SS | P(35);
987 }
988
989 t.tclass = tc;
990 }
991 ltclass = t.tclass;
992
993 /* Are we ready for this? */
994 if (! (ltclass & expected))
995 syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ?
996 EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN);
997
998 return ltclass;
999}
1000
1001static void rollback_token(void) { t.rollback = TRUE; }
1002
1003static node *new_node(unsigned long info) {
1004
1005 register node *n;
1006
1007 n = (node *)xcalloc(sizeof(node), 1);
1008 n->info = info;
1009 n->lineno = lineno;
1010 return n;
1011}
1012
1013static node *mk_re_node(char *s, node *n, regex_t *re) {
1014
1015 n->info = OC_REGEXP;
1016 n->l.re = re;
1017 n->r.ire = re + 1;
1018 xregcomp(re, s, REG_EXTENDED);
1019 xregcomp(re+1, s, REG_EXTENDED | REG_ICASE);
1020
1021 return n;
1022}
1023
1024static node *condition(void) {
1025
1026 next_token(TC_SEQSTART);
1027 return parse_expr(TC_SEQTERM);
1028}
1029
1030/* parse expression terminated by given argument, return ptr
1031 * to built subtree. Terminator is eaten by parse_expr */
1032static node *parse_expr(unsigned long iexp) {
1033
1034 node sn;
1035 node *cn = &sn;
1036 node *vn, *glptr;
1037 unsigned long tc, xtc;
1038 var *v;
1039
1040 sn.info = PRIMASK;
1041 sn.r.n = glptr = NULL;
1042 xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp;
1043
1044 while (! ((tc = next_token(xtc)) & iexp)) {
1045 if (glptr && (t.info == (OC_COMPARE|VV|P(39)|2))) {
1046 /* input redirection (<) attached to glptr node */
1047 cn = glptr->l.n = new_node(OC_CONCAT|SS|P(37));
1048 cn->a.n = glptr;
1049 xtc = TC_OPERAND | TC_UOPPRE;
1050 glptr = NULL;
1051
1052 } else if (tc & (TC_BINOP | TC_UOPPOST)) {
1053 /* for binary and postfix-unary operators, jump back over
1054 * previous operators with higher priority */
1055 vn = cn;
1056 while ( ((t.info & PRIMASK) > (vn->a.n->info & PRIMASK2)) ||
1057 ((t.info == vn->info) && ((t.info & OPCLSMASK) == OC_COLON)) )
1058 vn = vn->a.n;
1059 if ((t.info & OPCLSMASK) == OC_TERNARY)
1060 t.info += P(6);
1061 cn = vn->a.n->r.n = new_node(t.info);
1062 cn->a.n = vn->a.n;
1063 if (tc & TC_BINOP) {
1064 cn->l.n = vn;
1065 xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
1066 if ((t.info & OPCLSMASK) == OC_PGETLINE) {
1067 /* it's a pipe */
1068 next_token(TC_GETLINE);
1069 /* give maximum priority to this pipe */
1070 cn->info &= ~PRIMASK;
1071 xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
1072 }
1073 } else {
1074 cn->r.n = vn;
1075 xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
1076 }
1077 vn->a.n = cn;
1078
1079 } else {
1080 /* for operands and prefix-unary operators, attach them
1081 * to last node */
1082 vn = cn;
1083 cn = vn->r.n = new_node(t.info);
1084 cn->a.n = vn;
1085 xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
1086 if (tc & (TC_OPERAND | TC_REGEXP)) {
1087 xtc = TC_UOPPRE | TC_BINOP | TC_OPERAND | iexp;
1088 /* one should be very careful with switch on tclass -
1089 * only simple tclasses should be used! */
1090 switch (tc) {
1091 case TC_VARIABLE:
1092 case TC_ARRAY:
1093 cn->info = OC_VAR;
1094 if ((v = hash_search(ahash, t.string)) != NULL) {
1095 cn->info = OC_FNARG;
1096 cn->l.i = v->x.aidx;
1097 } else {
1098 cn->l.v = newvar(t.string);
1099 }
1100 if (tc & TC_ARRAY) {
1101 cn->info |= xS;
1102 cn->r.n = parse_expr(TC_ARRTERM);
1103 }
1104 xtc = TC_UOPPOST | TC_UOPPRE | TC_BINOP | TC_OPERAND | iexp;
1105 break;
1106
1107 case TC_NUMBER:
1108 case TC_STRING:
1109 cn->info = OC_VAR;
1110 v = cn->l.v = xcalloc(sizeof(var), 1);
1111 if (tc & TC_NUMBER)
1112 setvar_i(v, t.number);
1113 else
1114 setvar_s(v, t.string);
1115 break;
1116
1117 case TC_REGEXP:
1118 mk_re_node(t.string, cn,
1119 (regex_t *)xcalloc(sizeof(regex_t),2));
1120 break;
1121
1122 case TC_FUNCTION:
1123 cn->info = OC_FUNC;
1124 cn->r.f = newfunc(t.string);
1125 cn->l.n = condition();
1126 break;
1127
1128 case TC_SEQSTART:
1129 cn = vn->r.n = parse_expr(TC_SEQTERM);
1130 cn->a.n = vn;
1131 break;
1132
1133 case TC_GETLINE:
1134 glptr = cn;
1135 xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
1136 break;
1137
1138 case TC_BUILTIN:
1139 cn->l.n = condition();
1140 break;
1141 }
1142 }
1143 }
1144 }
1145 return sn.r.n;
1146}
1147
1148/* add node to chain. Return ptr to alloc'd node */
1149static node *chain_node(unsigned long info) {
1150
1151 register node *n;
1152
1153 if (! seq->first)
1154 seq->first = seq->last = new_node(0);
1155
1156 if (seq->programname != programname) {
1157 seq->programname = programname;
1158 n = chain_node(OC_NEWSOURCE);
1159 n->l.s = bb_xstrdup(programname);
1160 }
1161
1162 n = seq->last;
1163 n->info = info;
1164 seq->last = n->a.n = new_node(OC_DONE);
1165
1166 return n;
1167}
1168
1169static void chain_expr(unsigned long info) {
1170
1171 node *n;
1172
1173 n = chain_node(info);
1174 n->l.n = parse_expr(TC_OPTERM | TC_GRPTERM);
1175 if (t.tclass & TC_GRPTERM)
1176 rollback_token();
1177}
1178
1179static node *chain_loop(node *nn) {
1180
1181 node *n, *n2, *save_brk, *save_cont;
1182
1183 save_brk = break_ptr;
1184 save_cont = continue_ptr;
1185
1186 n = chain_node(OC_BR | Vx);
1187 continue_ptr = new_node(OC_EXEC);
1188 break_ptr = new_node(OC_EXEC);
1189 chain_group();
1190 n2 = chain_node(OC_EXEC | Vx);
1191 n2->l.n = nn;
1192 n2->a.n = n;
1193 continue_ptr->a.n = n2;
1194 break_ptr->a.n = n->r.n = seq->last;
1195
1196 continue_ptr = save_cont;
1197 break_ptr = save_brk;
1198
1199 return n;
1200}
1201
1202/* parse group and attach it to chain */
1203static void chain_group(void) {
1204
1205 unsigned long c;
1206 node *n, *n2, *n3;
1207
1208 do {
1209 c = next_token(TC_GRPSEQ);
1210 } while (c & TC_NEWLINE);
1211
1212 if (c & TC_GRPSTART) {
1213 while(next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) {
1214 if (t.tclass & TC_NEWLINE) continue;
1215 rollback_token();
1216 chain_group();
1217 }
1218 } else if (c & (TC_OPSEQ | TC_OPTERM)) {
1219 rollback_token();
1220 chain_expr(OC_EXEC | Vx);
1221 } else { /* TC_STATEMNT */
1222 switch (t.info & OPCLSMASK) {
1223 case ST_IF:
1224 n = chain_node(OC_BR | Vx);
1225 n->l.n = condition();
1226 chain_group();
1227 n2 = chain_node(OC_EXEC);
1228 n->r.n = seq->last;
1229 if (next_token(TC_GRPSEQ | TC_GRPTERM | TC_ELSE)==TC_ELSE) {
1230 chain_group();
1231 n2->a.n = seq->last;
1232 } else {
1233 rollback_token();
1234 }
1235 break;
1236
1237 case ST_WHILE:
1238 n2 = condition();
1239 n = chain_loop(NULL);
1240 n->l.n = n2;
1241 break;
1242
1243 case ST_DO:
1244 n2 = chain_node(OC_EXEC);
1245 n = chain_loop(NULL);
1246 n2->a.n = n->a.n;
1247 next_token(TC_WHILE);
1248 n->l.n = condition();
1249 break;
1250
1251 case ST_FOR:
1252 next_token(TC_SEQSTART);
1253 n2 = parse_expr(TC_SEMICOL | TC_SEQTERM);
1254 if (t.tclass & TC_SEQTERM) { /* for-in */
1255 if ((n2->info & OPCLSMASK) != OC_IN)
1256 syntax_error(EMSG_UNEXP_TOKEN);
1257 n = chain_node(OC_WALKINIT | VV);
1258 n->l.n = n2->l.n;
1259 n->r.n = n2->r.n;
1260 n = chain_loop(NULL);
1261 n->info = OC_WALKNEXT | Vx;
1262 n->l.n = n2->l.n;
1263 } else { /* for(;;) */
1264 n = chain_node(OC_EXEC | Vx);
1265 n->l.n = n2;
1266 n2 = parse_expr(TC_SEMICOL);
1267 n3 = parse_expr(TC_SEQTERM);
1268 n = chain_loop(n3);
1269 n->l.n = n2;
1270 if (! n2)
1271 n->info = OC_EXEC;
1272 }
1273 break;
1274
1275 case OC_PRINT:
1276 case OC_PRINTF:
1277 n = chain_node(t.info);
1278 n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM);
1279 if (t.tclass & TC_OUTRDR) {
1280 n->info |= t.info;
1281 n->r.n = parse_expr(TC_OPTERM | TC_GRPTERM);
1282 }
1283 if (t.tclass & TC_GRPTERM)
1284 rollback_token();
1285 break;
1286
1287 case OC_BREAK:
1288 n = chain_node(OC_EXEC);
1289 n->a.n = break_ptr;
1290 break;
1291
1292 case OC_CONTINUE:
1293 n = chain_node(OC_EXEC);
1294 n->a.n = continue_ptr;
1295 break;
1296
1297 /* delete, next, nextfile, return, exit */
1298 default:
1299 chain_expr(t.info);
1300
1301 }
1302 }
1303}
1304
1305static void parse_program(char *p) {
1306
1307 unsigned long tclass;
1308 node *cn;
1309 func *f;
1310 var *v;
1311
1312 pos = p;
1313 t.lineno = 1;
1314 while((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART |
1315 TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) {
1316
1317 if (tclass & TC_OPTERM)
1318 continue;
1319
1320 seq = &mainseq;
1321 if (tclass & TC_BEGIN) {
1322 seq = &beginseq;
1323 chain_group();
1324
1325 } else if (tclass & TC_END) {
1326 seq = &endseq;
1327 chain_group();
1328
1329 } else if (tclass & TC_FUNCDECL) {
1330 next_token(TC_FUNCTION);
1331 pos++;
1332 f = newfunc(t.string);
1333 f->body.first = NULL;
1334 f->nargs = 0;
1335 while(next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) {
1336 v = findvar(ahash, t.string);
1337 v->x.aidx = (f->nargs)++;
1338
1339 if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
1340 break;
1341 }
1342 seq = &(f->body);
1343 chain_group();
1344 clear_array(ahash);
1345
1346 } else if (tclass & TC_OPSEQ) {
1347 rollback_token();
1348 cn = chain_node(OC_TEST);
1349 cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART);
1350 if (t.tclass & TC_GRPSTART) {
1351 rollback_token();
1352 chain_group();
1353 } else {
1354 chain_node(OC_PRINT);
1355 }
1356 cn->r.n = mainseq.last;
1357
1358 } else /* if (tclass & TC_GRPSTART) */ {
1359 rollback_token();
1360 chain_group();
1361 }
1362 }
1363}
1364
1365
1366/* -------- program execution part -------- */
1367
1368static node *mk_splitter(char *s, tsplitter *spl) {
1369
1370 register regex_t *re, *ire;
1371 node *n;
1372
1373 re = &spl->re[0];
1374 ire = &spl->re[1];
1375 n = &spl->n;
1376 if ((n->info && OPCLSMASK) == OC_REGEXP) {
1377 regfree(re);
1378 regfree(ire);
1379 }
1380 if (bb_strlen(s) > 1) {
1381 mk_re_node(s, n, re);
1382 } else {
1383 n->info = (unsigned long) *s;
1384 }
1385
1386 return n;
1387}
1388
1389/* use node as a regular expression. Supplied with node ptr and regex_t
1390 * storage space. Return ptr to regex (if result points to preg, it should
1391 * be later regfree'd manually
1392 */
1393static regex_t *as_regex(node *op, regex_t *preg) {
1394
1395 var *v;
1396 char *s;
1397
1398 if ((op->info & OPCLSMASK) == OC_REGEXP) {
1399 return icase ? op->r.ire : op->l.re;
1400 } else {
1401 v = nvalloc(1);
1402 s = getvar_s(evaluate(op, v));
1403 xregcomp(preg, s, icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED);
1404 nvfree(v);
1405 return preg;
1406 }
1407}
1408
1409/* gradually increasing buffer */
1410static void qrealloc(char **b, int n, int *size) {
1411
1412 if (! *b || n >= *size)
1413 *b = xrealloc(*b, *size = n + (n>>1) + 80);
1414}
1415
1416/* resize field storage space */
1417static void fsrealloc(int size) {
1418
1419 static int maxfields = 0;
1420 int i;
1421
1422 if (size >= maxfields) {
1423 i = maxfields;
1424 maxfields = size + 16;
1425 Fields = (var *)xrealloc(Fields, maxfields * sizeof(var));
1426 for (; i<maxfields; i++) {
1427 Fields[i].type = VF_SPECIAL;
1428 Fields[i].string = NULL;
1429 }
1430 }
1431
1432 if (size < nfields) {
1433 for (i=size; i<nfields; i++) {
1434 clrvar(Fields+i);
1435 }
1436 }
1437 nfields = size;
1438}
1439
1440static int awk_split(char *s, node *spl, char **slist) {
1441
1442 int l, n=0;
1443 char c[4];
1444 char *s1;
1445 regmatch_t pmatch[2];
1446
1447 /* in worst case, each char would be a separate field */
1448 *slist = s1 = bb_xstrndup(s, bb_strlen(s) * 2 + 3);
1449
1450 c[0] = c[1] = (char)spl->info;
1451 c[2] = c[3] = '\0';
1452 if (*getvar_s(V[RS]) == '\0') c[2] = '\n';
1453
1454 if ((spl->info & OPCLSMASK) == OC_REGEXP) { /* regex split */
1455 while (*s) {
1456 l = strcspn(s, c+2);
1457 if (regexec(icase ? spl->r.ire : spl->l.re, s, 1, pmatch, 0) == 0 &&
1458 pmatch[0].rm_so <= l) {
1459 l = pmatch[0].rm_so;
1460 if (pmatch[0].rm_eo == 0) { l++; pmatch[0].rm_eo++; }
1461 } else {
1462 pmatch[0].rm_eo = l;
1463 if (*(s+l)) pmatch[0].rm_eo++;
1464 }
1465
1466 memcpy(s1, s, l);
1467 *(s1+l) = '\0';
1468 nextword(&s1);
1469 s += pmatch[0].rm_eo;
1470 n++;
1471 }
1472 } else if (c[0] == '\0') { /* null split */
1473 while(*s) {
1474 *(s1++) = *(s++);
1475 *(s1++) = '\0';
1476 n++;
1477 }
1478 } else if (c[0] != ' ') { /* single-character split */
1479 if (icase) {
1480 c[0] = toupper(c[0]);
1481 c[1] = tolower(c[1]);
1482 }
1483 if (*s1) n++;
1484 while ((s1 = strpbrk(s1, c))) {
1485 *(s1++) = '\0';
1486 n++;
1487 }
1488 } else { /* space split */
1489 while (*s) {
1490 while (isspace(*s)) s++;
1491 if (! *s) break;
1492 n++;
1493 while (*s && !isspace(*s))
1494 *(s1++) = *(s++);
1495 *(s1++) = '\0';
1496 }
1497 }
1498 return n;
1499}
1500
1501static void split_f0(void) {
1502
1503 static char *fstrings = NULL;
1504 int i, n;
1505 char *s;
1506
1507 if (is_f0_split)
1508 return;
1509
1510 is_f0_split = TRUE;
1511 free(fstrings);
1512 fsrealloc(0);
1513 n = awk_split(getvar_s(V[F0]), &fsplitter.n, &fstrings);
1514 fsrealloc(n);
1515 s = fstrings;
1516 for (i=0; i<n; i++) {
1517 Fields[i].string = nextword(&s);
1518 Fields[i].type |= (VF_FSTR | VF_USER | VF_DIRTY);
1519 }
1520
1521 /* set NF manually to avoid side effects */
1522 clrvar(V[NF]);
1523 V[NF]->type = VF_NUMBER | VF_SPECIAL;
1524 V[NF]->number = nfields;
1525}
1526
1527/* perform additional actions when some internal variables changed */
1528static void handle_special(var *v) {
1529
1530 int n;
1531 char *b, *sep, *s;
1532 int sl, l, len, i, bsize;
1533
1534 if (! (v->type & VF_SPECIAL))
1535 return;
1536
1537 if (v == V[NF]) {
1538 n = (int)getvar_i(v);
1539 fsrealloc(n);
1540
1541 /* recalculate $0 */
1542 sep = getvar_s(V[OFS]);
1543 sl = bb_strlen(sep);
1544 b = NULL;
1545 len = 0;
1546 for (i=0; i<n; i++) {
1547 s = getvar_s(&Fields[i]);
1548 l = bb_strlen(s);
1549 if (b) {
1550 memcpy(b+len, sep, sl);
1551 len += sl;
1552 }
1553 qrealloc(&b, len+l+sl, &bsize);
1554 memcpy(b+len, s, l);
1555 len += l;
1556 }
1557 if (b) b[len] = '\0';
1558 setvar_p(V[F0], b);
1559 is_f0_split = TRUE;
1560
1561 } else if (v == V[F0]) {
1562 is_f0_split = FALSE;
1563
1564 } else if (v == V[FS]) {
1565 mk_splitter(getvar_s(v), &fsplitter);
1566
1567 } else if (v == V[RS]) {
1568 mk_splitter(getvar_s(v), &rsplitter);
1569
1570 } else if (v == V[IGNORECASE]) {
1571 icase = istrue(v);
1572
1573 } else { /* $n */
1574 n = getvar_i(V[NF]);
1575 setvar_i(V[NF], n > v-Fields ? n : v-Fields+1);
1576 /* right here v is invalid. Just to note... */
1577 }
1578}
1579
1580/* step through func/builtin/etc arguments */
1581static node *nextarg(node **pn) {
1582
1583 node *n;
1584
1585 n = *pn;
1586 if (n && (n->info & OPCLSMASK) == OC_COMMA) {
1587 *pn = n->r.n;
1588 n = n->l.n;
1589 } else {
1590 *pn = NULL;
1591 }
1592 return n;
1593}
1594
1595static void hashwalk_init(var *v, xhash *array) {
1596
1597 char **w;
1598 hash_item *hi;
1599 int i;
1600
1601 if (v->type & VF_WALK)
1602 free(v->x.walker);
1603
1604 v->type |= VF_WALK;
1605 w = v->x.walker = (char **)xcalloc(2 + 2*sizeof(char *) + array->glen, 1);
1606 *w = *(w+1) = (char *)(w + 2);
1607 for (i=0; i<array->csize; i++) {
1608 hi = array->items[i];
1609 while(hi) {
1610 strcpy(*w, hi->name);
1611 nextword(w);
1612 hi = hi->next;
1613 }
1614 }
1615}
1616
1617static int hashwalk_next(var *v) {
1618
1619 char **w;
1620
1621 w = v->x.walker;
1622 if (*(w+1) == *w)
1623 return FALSE;
1624
1625 setvar_s(v, nextword(w+1));
1626 return TRUE;
1627}
1628
1629/* evaluate node, return 1 when result is true, 0 otherwise */
1630static int ptest(node *pattern) {
1631 static var v;
1632
1633 return istrue(evaluate(pattern, &v));
1634}
1635
1636/* read next record from stream rsm into a variable v */
1637static int awk_getline(rstream *rsm, var *v) {
1638
1639 char *b;
1640 regmatch_t pmatch[2];
1641 int a, p, pp=0, size;
1642 int fd, so, eo, r, rp;
1643 char c, *m, *s;
1644
1645 /* we're using our own buffer since we need access to accumulating
1646 * characters
1647 */
1648 fd = fileno(rsm->F);
1649 m = rsm->buffer;
1650 a = rsm->adv;
1651 p = rsm->pos;
1652 size = rsm->size;
1653 c = (char) rsplitter.n.info;
1654 rp = 0;
1655
1656 if (! m) qrealloc(&m, 256, &size);
1657 do {
1658 b = m + a;
1659 so = eo = p;
1660 r = 1;
1661 if (p > 0) {
1662 if ((rsplitter.n.info & OPCLSMASK) == OC_REGEXP) {
1663 if (regexec(icase ? rsplitter.n.r.ire : rsplitter.n.l.re,
1664 b, 1, pmatch, 0) == 0) {
1665 so = pmatch[0].rm_so;
1666 eo = pmatch[0].rm_eo;
1667 if (b[eo] != '\0')
1668 break;
1669 }
1670 } else if (c != '\0') {
1671 s = strchr(b+pp, c);
1672 if (s) {
1673 so = eo = s-b;
1674 eo++;
1675 break;
1676 }
1677 } else {
1678 while (b[rp] == '\n')
1679 rp++;
1680 s = strstr(b+rp, "\n\n");
1681 if (s) {
1682 so = eo = s-b;
1683 while (b[eo] == '\n') eo++;
1684 if (b[eo] != '\0')
1685 break;
1686 }
1687 }
1688 }
1689
1690 if (a > 0) {
1691 memmove(m, (const void *)(m+a), p+1);
1692 b = m;
1693 a = 0;
1694 }
1695
1696 qrealloc(&m, a+p+128, &size);
1697 b = m + a;
1698 pp = p;
1699 p += safe_read(fd, b+p, size-p-1);
1700 if (p < pp) {
1701 p = 0;
1702 r = 0;
1703 setvar_i(V[ERRNO], errno);
1704 }
1705 b[p] = '\0';
1706
1707 } while (p > pp);
1708
1709 if (p == 0) {
1710 r--;
1711 } else {
1712 c = b[so]; b[so] = '\0';
1713 setvar_s(v, b+rp);
1714 v->type |= VF_USER;
1715 b[so] = c;
1716 c = b[eo]; b[eo] = '\0';
1717 setvar_s(V[RT], b+so);
1718 b[eo] = c;
1719 }
1720
1721 rsm->buffer = m;
1722 rsm->adv = a + eo;
1723 rsm->pos = p - eo;
1724 rsm->size = size;
1725
1726 return r;
1727}
1728
1729static int fmt_num(char *b, int size, char *format, double n, int int_as_int) {
1730
1731 int r=0;
1732 char c, *s=format;
1733
1734 if (int_as_int && n == (int)n) {
1735 r = snprintf(b, size, "%d", (int)n);
1736 } else {
1737 do { c = *s; } while (*s && *++s);
1738 if (strchr("diouxX", c)) {
1739 r = snprintf(b, size, format, (int)n);
1740 } else if (strchr("eEfgG", c)) {
1741 r = snprintf(b, size, format, n);
1742 } else {
1743 runtime_error(EMSG_INV_FMT);
1744 }
1745 }
1746 return r;
1747}
1748
1749
1750/* formatted output into an allocated buffer, return ptr to buffer */
1751static char *awk_printf(node *n) {
1752
1753 char *b = NULL;
1754 char *fmt, *s, *s1, *f;
1755 int i, j, incr, bsize;
1756 char c, c1;
1757 var *v, *arg;
1758
1759 v = nvalloc(1);
1760 fmt = f = bb_xstrdup(getvar_s(evaluate(nextarg(&n), v)));
1761
1762 i = 0;
1763 while (*f) {
1764 s = f;
1765 while (*f && (*f != '%' || *(++f) == '%'))
1766 f++;
1767 while (*f && !isalpha(*f))
1768 f++;
1769
1770 incr = (f - s) + MAXVARFMT;
1771 qrealloc(&b, incr+i, &bsize);
1772 c = *f; if (c != '\0') f++;
1773 c1 = *f ; *f = '\0';
1774 arg = evaluate(nextarg(&n), v);
1775
1776 j = i;
1777 if (c == 'c' || !c) {
1778 i += sprintf(b+i, s,
1779 is_numeric(arg) ? (char)getvar_i(arg) : *getvar_s(arg));
1780
1781 } else if (c == 's') {
1782 s1 = getvar_s(arg);
1783 qrealloc(&b, incr+i+bb_strlen(s1), &bsize);
1784 i += sprintf(b+i, s, s1);
1785
1786 } else {
1787 i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE);
1788 }
1789 *f = c1;
1790
1791 /* if there was an error while sprintf, return value is negative */
1792 if (i < j) i = j;
1793
1794 }
1795
1796 b = xrealloc(b, i+1);
1797 free(fmt);
1798 nvfree(v);
1799 b[i] = '\0';
1800 return b;
1801}
1802
1803/* common substitution routine
1804 * replace (nm) substring of (src) that match (n) with (repl), store
1805 * result into (dest), return number of substitutions. If nm=0, replace
1806 * all matches. If src or dst is NULL, use $0. If ex=TRUE, enable
1807 * subexpression matching (\1-\9)
1808 */
1809static int awk_sub(node *rn, char *repl, int nm, var *src, var *dest, int ex) {
1810
1811 char *ds = NULL;
1812 char *sp, *s;
1813 int c, i, j, di, rl, so, eo, nbs, n, dssize;
1814 regmatch_t pmatch[10];
1815 regex_t sreg, *re;
1816
1817 re = as_regex(rn, &sreg);
1818 if (! src) src = V[F0];
1819 if (! dest) dest = V[F0];
1820
1821 i = di = 0;
1822 sp = getvar_s(src);
1823 rl = bb_strlen(repl);
1824 while (regexec(re, sp, 10, pmatch, sp==getvar_s(src) ? 0:REG_NOTBOL) == 0) {
1825 so = pmatch[0].rm_so;
1826 eo = pmatch[0].rm_eo;
1827
1828 qrealloc(&ds, di + eo + rl, &dssize);
1829 memcpy(ds + di, sp, eo);
1830 di += eo;
1831 if (++i >= nm) {
1832 /* replace */
1833 di -= (eo - so);
1834 nbs = 0;
1835 for (s = repl; *s; s++) {
1836 ds[di++] = c = *s;
1837 if (c == '\\') {
1838 nbs++;
1839 continue;
1840 }
1841 if (c == '&' || (ex && c >= '0' && c <= '9')) {
1842 di -= ((nbs + 3) >> 1);
1843 j = 0;
1844 if (c != '&') {
1845 j = c - '0';
1846 nbs++;
1847 }
1848 if (nbs % 2) {
1849 ds[di++] = c;
1850 } else {
1851 n = pmatch[j].rm_eo - pmatch[j].rm_so;
1852 qrealloc(&ds, di + rl + n, &dssize);
1853 memcpy(ds + di, sp + pmatch[j].rm_so, n);
1854 di += n;
1855 }
1856 }
1857 nbs = 0;
1858 }
1859 }
1860
1861 sp += eo;
1862 if (i == nm) break;
1863 if (eo == so) {
1864 if (! (ds[di++] = *sp++)) break;
1865 }
1866 }
1867
1868 qrealloc(&ds, di + strlen(sp), &dssize);
1869 strcpy(ds + di, sp);
1870 setvar_p(dest, ds);
1871 if (re == &sreg) regfree(re);
1872 return i;
1873}
1874
1875static var *exec_builtin(node *op, var *res) {
1876
1877 int (*to_xxx)(int);
1878 var *tv;
1879 node *an[4];
1880 var *av[4];
1881 char *as[4];
1882 regmatch_t pmatch[2];
1883 regex_t sreg, *re;
1884 static tsplitter tspl;
1885 node *spl;
1886 unsigned long isr, info;
1887 int nargs;
1888 time_t tt;
1889 char *s, *s1;
1890 int i, l, ll, n;
1891
1892 tv = nvalloc(4);
1893 isr = info = op->info;
1894 op = op->l.n;
1895
1896 av[2] = av[3] = NULL;
1897 for (i=0 ; i<4 && op ; i++) {
1898 an[i] = nextarg(&op);
1899 if (isr & 0x09000000) av[i] = evaluate(an[i], &tv[i]);
1900 if (isr & 0x08000000) as[i] = getvar_s(av[i]);
1901 isr >>= 1;
1902 }
1903
1904 nargs = i;
1905 if (nargs < (info >> 30))
1906 runtime_error(EMSG_TOO_FEW_ARGS);
1907
1908 switch (info & OPNMASK) {
1909
1910 case B_a2:
1911#ifdef CONFIG_FEATURE_AWK_MATH
1912 setvar_i(res, atan2(getvar_i(av[i]), getvar_i(av[1])));
1913#else
1914 runtime_error(EMSG_NO_MATH);
1915#endif
1916 break;
1917
1918 case B_sp:
1919 if (nargs > 2) {
1920 spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ?
1921 an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl);
1922 } else {
1923 spl = &fsplitter.n;
1924 }
1925
1926 n = awk_split(as[0], spl, &s);
1927 s1 = s;
1928 clear_array(iamarray(av[1]));
1929 for (i=1; i<=n; i++)
1930 setari_u(av[1], i, nextword(&s1));
1931 free(s);
1932 setvar_i(res, n);
1933 break;
1934
1935 case B_ss:
1936 l = bb_strlen(as[0]);
1937 i = getvar_i(av[1]) - 1;
1938 if (i>l) i=l; if (i<0) i=0;
1939 n = (nargs > 2) ? getvar_i(av[2]) : l-i;
1940 if (n<0) n=0;
1941 s = xmalloc(n+1);
1942 strncpy(s, as[0]+i, n);
1943 s[n] = '\0';
1944 setvar_p(res, s);
1945 break;
1946
1947 case B_lo:
1948 to_xxx = tolower;
1949 goto lo_cont;
1950
1951 case B_up:
1952 to_xxx = toupper;
1953lo_cont:
1954 s1 = s = bb_xstrdup(as[0]);
1955 while (*s1) {
1956 *s1 = (*to_xxx)(*s1);
1957 s1++;
1958 }
1959 setvar_p(res, s);
1960 break;
1961
1962 case B_ix:
1963 n = 0;
1964 ll = bb_strlen(as[1]);
1965 l = bb_strlen(as[0]) - ll;
1966 if (ll > 0 && l >= 0) {
1967 if (! icase) {
1968 s = strstr(as[0], as[1]);
1969 if (s) n = (s - as[0]) + 1;
1970 } else {
1971 /* this piece of code is terribly slow and
1972 * really should be rewritten
1973 */
1974 for (i=0; i<=l; i++) {
1975 if (strncasecmp(as[0]+i, as[1], ll) == 0) {
1976 n = i+1;
1977 break;
1978 }
1979 }
1980 }
1981 }
1982 setvar_i(res, n);
1983 break;
1984
1985 case B_ti:
1986 if (nargs > 1)
1987 tt = getvar_i(av[1]);
1988 else
1989 time(&tt);
1990 s = (nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y";
1991 i = strftime(buf, MAXVARFMT, s, localtime(&tt));
1992 buf[i] = '\0';
1993 setvar_s(res, buf);
1994 break;
1995
1996 case B_ma:
1997 re = as_regex(an[1], &sreg);
1998 n = regexec(re, as[0], 1, pmatch, 0);
1999 if (n == 0) {
2000 pmatch[0].rm_so++;
2001 pmatch[0].rm_eo++;
2002 } else {
2003 pmatch[0].rm_so = 0;
2004 pmatch[0].rm_eo = -1;
2005 }
2006 setvar_i(newvar("RSTART"), pmatch[0].rm_so);
2007 setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so);
2008 setvar_i(res, pmatch[0].rm_so);
2009 if (re == &sreg) regfree(re);
2010 break;
2011
2012 case B_ge:
2013 awk_sub(an[0], as[1], getvar_i(av[2]), av[3], res, TRUE);
2014 break;
2015
2016 case B_gs:
2017 setvar_i(res, awk_sub(an[0], as[1], 0, av[2], av[2], FALSE));
2018 break;
2019
2020 case B_su:
2021 setvar_i(res, awk_sub(an[0], as[1], 1, av[2], av[2], FALSE));
2022 break;
2023 }
2024
2025 nvfree(tv);
2026 return res;
2027}
2028
2029/*
2030 * Evaluate node - the heart of the program. Supplied with subtree
2031 * and place where to store result. returns ptr to result.
2032 */
2033#define XC(n) ((n) >> 8)
2034
2035static var *evaluate(node *op, var *res) {
2036
2037 /* This procedure is recursive so we should count every byte */
2038 static var *fnargs = NULL;
2039 static unsigned int seed = 1;
2040 static regex_t sreg;
2041 node *op1;
2042 var *v1;
2043 union {
2044 var *v;
2045 char *s;
2046 double d;
2047 int i;
2048 } L, R;
2049 unsigned long opinfo;
2050 short opn;
2051 union {
2052 char *s;
2053 rstream *rsm;
2054 FILE *F;
2055 var *v;
2056 regex_t *re;
2057 unsigned long info;
2058 } X;
2059
2060 if (! op)
2061 return setvar_s(res, NULL);
2062
2063 v1 = nvalloc(2);
2064
2065 while (op) {
2066
2067 opinfo = op->info;
2068 opn = (short)(opinfo & OPNMASK);
2069 lineno = op->lineno;
2070
2071 /* execute inevitable things */
2072 op1 = op->l.n;
2073 if (opinfo & OF_RES1) X.v = L.v = evaluate(op1, v1);
2074 if (opinfo & OF_RES2) R.v = evaluate(op->r.n, v1+1);
2075 if (opinfo & OF_STR1) L.s = getvar_s(L.v);
2076 if (opinfo & OF_STR2) R.s = getvar_s(R.v);
2077 if (opinfo & OF_NUM1) L.d = getvar_i(L.v);
2078
2079 switch (XC(opinfo & OPCLSMASK)) {
2080
2081 /* -- iterative node type -- */
2082
2083 /* test pattern */
2084 case XC( OC_TEST ):
2085 if ((op1->info & OPCLSMASK) == OC_COMMA) {
2086 /* it's range pattern */
2087 if ((opinfo & OF_CHECKED) || ptest(op1->l.n)) {
2088 op->info |= OF_CHECKED;
2089 if (ptest(op1->r.n))
2090 op->info &= ~OF_CHECKED;
2091
2092 op = op->a.n;
2093 } else {
2094 op = op->r.n;
2095 }
2096 } else {
2097 op = (ptest(op1)) ? op->a.n : op->r.n;
2098 }
2099 break;
2100
2101 /* just evaluate an expression, also used as unconditional jump */
2102 case XC( OC_EXEC ):
2103 break;
2104
2105 /* branch, used in if-else and various loops */
2106 case XC( OC_BR ):
2107 op = istrue(L.v) ? op->a.n : op->r.n;
2108 break;
2109
2110 /* initialize for-in loop */
2111 case XC( OC_WALKINIT ):
2112 hashwalk_init(L.v, iamarray(R.v));
2113 break;
2114
2115 /* get next array item */
2116 case XC( OC_WALKNEXT ):
2117 op = hashwalk_next(L.v) ? op->a.n : op->r.n;
2118 break;
2119
2120 case XC( OC_PRINT ):
2121 case XC( OC_PRINTF ):
2122 X.F = stdout;
2123 if (op->r.n) {
2124 X.rsm = newfile(R.s);
2125 if (! X.rsm->F) {
2126 if (opn == '|') {
2127 if((X.rsm->F = popen(R.s, "w")) == NULL)
2128 bb_perror_msg_and_die("popen");
2129 X.rsm->is_pipe = 1;
2130 } else {
2131 X.rsm->F = bb_xfopen(R.s, opn=='w' ? "w" : "a");
2132 }
2133 }
2134 X.F = X.rsm->F;
2135 }
2136
2137 if ((opinfo & OPCLSMASK) == OC_PRINT) {
2138 if (! op1) {
2139 fputs(getvar_s(V[F0]), X.F);
2140 } else {
2141 while (op1) {
2142 L.v = evaluate(nextarg(&op1), v1);
2143 if (L.v->type & VF_NUMBER) {
2144 fmt_num(buf, MAXVARFMT, getvar_s(V[OFMT]),
2145 getvar_i(L.v), TRUE);
2146 fputs(buf, X.F);
2147 } else {
2148 fputs(getvar_s(L.v), X.F);
2149 }
2150
2151 if (op1) fputs(getvar_s(V[OFS]), X.F);
2152 }
2153 }
2154 fputs(getvar_s(V[ORS]), X.F);
2155
2156 } else { /* OC_PRINTF */
2157 L.s = awk_printf(op1);
2158 fputs(L.s, X.F);
2159 free(L.s);
2160 }
2161 fflush(X.F);
2162 break;
2163
2164 case XC( OC_DELETE ):
2165 X.info = op1->info & OPCLSMASK;
2166 if (X.info == OC_VAR) {
2167 R.v = op1->l.v;
2168 } else if (X.info == OC_FNARG) {
2169 R.v = &fnargs[op1->l.i];
2170 } else {
2171 runtime_error(EMSG_NOT_ARRAY);
2172 }
2173
2174 if (op1->r.n) {
2175 clrvar(L.v);
2176 L.s = getvar_s(evaluate(op1->r.n, v1));
2177 hash_remove(iamarray(R.v), L.s);
2178 } else {
2179 clear_array(iamarray(R.v));
2180 }
2181 break;
2182
2183 case XC( OC_NEWSOURCE ):
2184 programname = op->l.s;
2185 break;
2186
2187 case XC( OC_RETURN ):
2188 copyvar(res, L.v);
2189 break;
2190
2191 case XC( OC_NEXTFILE ):
2192 nextfile = TRUE;
2193 case XC( OC_NEXT ):
2194 nextrec = TRUE;
2195 case XC( OC_DONE ):
2196 clrvar(res);
2197 break;
2198
2199 case XC( OC_EXIT ):
2200 awk_exit(L.d);
2201
2202 /* -- recursive node type -- */
2203
2204 case XC( OC_VAR ):
2205 L.v = op->l.v;
2206 if (L.v == V[NF])
2207 split_f0();
2208 goto v_cont;
2209
2210 case XC( OC_FNARG ):
2211 L.v = &fnargs[op->l.i];
2212
2213v_cont:
2214 res = (op->r.n) ? findvar(iamarray(L.v), R.s) : L.v;
2215 break;
2216
2217 case XC( OC_IN ):
2218 setvar_i(res, hash_search(iamarray(R.v), L.s) ? 1 : 0);
2219 break;
2220
2221 case XC( OC_REGEXP ):
2222 op1 = op;
2223 L.s = getvar_s(V[F0]);
2224 goto re_cont;
2225
2226 case XC( OC_MATCH ):
2227 op1 = op->r.n;
2228re_cont:
2229 X.re = as_regex(op1, &sreg);
2230 R.i = regexec(X.re, L.s, 0, NULL, 0);
2231 if (X.re == &sreg) regfree(X.re);
2232 setvar_i(res, (R.i == 0 ? 1 : 0) ^ (opn == '!' ? 1 : 0));
2233 break;
2234
2235 case XC( OC_MOVE ):
2236 /* if source is a temporary string, jusk relink it to dest */
2237 if (R.v == v1+1 && R.v->string) {
2238 res = setvar_p(L.v, R.v->string);
2239 R.v->string = NULL;
2240 } else {
2241 res = copyvar(L.v, R.v);
2242 }
2243 break;
2244
2245 case XC( OC_TERNARY ):
2246 if ((op->r.n->info & OPCLSMASK) != OC_COLON)
2247 runtime_error(EMSG_POSSIBLE_ERROR);
2248 res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res);
2249 break;
2250
2251 case XC( OC_FUNC ):
2252 if (! op->r.f->body.first)
2253 runtime_error(EMSG_UNDEF_FUNC);
2254
2255 X.v = R.v = nvalloc(op->r.f->nargs+1);
2256 while (op1) {
2257 L.v = evaluate(nextarg(&op1), v1);
2258 copyvar(R.v, L.v);
2259 R.v->type |= VF_CHILD;
2260 R.v->x.parent = L.v;
2261 if (++R.v - X.v >= op->r.f->nargs)
2262 break;
2263 }
2264
2265 R.v = fnargs;
2266 fnargs = X.v;
2267
2268 L.s = programname;
2269 res = evaluate(op->r.f->body.first, res);
2270 programname = L.s;
2271
2272 nvfree(fnargs);
2273 fnargs = R.v;
2274 break;
2275
2276 case XC( OC_GETLINE ):
2277 case XC( OC_PGETLINE ):
2278 if (op1) {
2279 X.rsm = newfile(L.s);
2280 if (! X.rsm->F) {
2281 if ((opinfo & OPCLSMASK) == OC_PGETLINE) {
2282 X.rsm->F = popen(L.s, "r");
2283 X.rsm->is_pipe = TRUE;
2284 } else {
2285 X.rsm->F = fopen(L.s, "r"); /* not bb_xfopen! */
2286 }
2287 }
2288 } else {
2289 if (! iF) iF = next_input_file();
2290 X.rsm = iF;
2291 }
2292
2293 if (! X.rsm->F) {
2294 setvar_i(V[ERRNO], errno);
2295 setvar_i(res, -1);
2296 break;
2297 }
2298
2299 if (! op->r.n)
2300 R.v = V[F0];
2301
2302 L.i = awk_getline(X.rsm, R.v);
2303 if (L.i > 0) {
2304 if (! op1) {
2305 incvar(V[FNR]);
2306 incvar(V[NR]);
2307 }
2308 }
2309 setvar_i(res, L.i);
2310 break;
2311
2312 /* simple builtins */
2313 case XC( OC_FBLTIN ):
2314 switch (opn) {
2315
2316 case F_in:
2317 R.d = (int)L.d;
2318 break;
2319
2320 case F_rn:
2321 R.d = (double)rand() / (double)RAND_MAX;
2322 break;
2323
2324#ifdef CONFIG_FEATURE_AWK_MATH
2325 case F_co:
2326 R.d = cos(L.d);
2327 break;
2328
2329 case F_ex:
2330 R.d = exp(L.d);
2331 break;
2332
2333 case F_lg:
2334 R.d = log(L.d);
2335 break;
2336
2337 case F_si:
2338 R.d = sin(L.d);
2339 break;
2340
2341 case F_sq:
2342 R.d = sqrt(L.d);
2343 break;
2344#else
2345 case F_co:
2346 case F_ex:
2347 case F_lg:
2348 case F_si:
2349 case F_sq:
2350 runtime_error(EMSG_NO_MATH);
2351 break;
2352#endif
2353
2354 case F_sr:
2355 R.d = (double)seed;
2356 seed = op1 ? (unsigned int)L.d : (unsigned int)time(NULL);
2357 srand(seed);
2358 break;
2359
2360 case F_ti:
2361 R.d = time(NULL);
2362 break;
2363
2364 case F_le:
2365 if (! op1)
2366 L.s = getvar_s(V[F0]);
2367 R.d = bb_strlen(L.s);
2368 break;
2369
2370 case F_sy:
2371 fflush(NULL);
2372 R.d = (L.s && *L.s) ? system(L.s) : 0;
2373 break;
2374
2375 case F_ff:
2376 if (! op1)
2377 fflush(stdout);
2378 else {
2379 if (L.s && *L.s) {
2380 X.rsm = newfile(L.s);
2381 fflush(X.rsm->F);
2382 } else {
2383 fflush(NULL);
2384 }
2385 }
2386 break;
2387
2388 case F_cl:
2389 X.rsm = (rstream *)hash_search(fdhash, L.s);
2390 if (X.rsm) {
2391 R.i = X.rsm->is_pipe ? pclose(X.rsm->F) : fclose(X.rsm->F);
2392 free(X.rsm->buffer);
2393 hash_remove(fdhash, L.s);
2394 }
2395 if (R.i != 0)
2396 setvar_i(V[ERRNO], errno);
2397 R.d = (double)R.i;
2398 break;
2399 }
2400 setvar_i(res, R.d);
2401 break;
2402
2403 case XC( OC_BUILTIN ):
2404 res = exec_builtin(op, res);
2405 break;
2406
2407 case XC( OC_SPRINTF ):
2408 setvar_p(res, awk_printf(op1));
2409 break;
2410
2411 case XC( OC_UNARY ):
2412 X.v = R.v;
2413 L.d = R.d = getvar_i(R.v);
2414 switch (opn) {
2415 case 'P':
2416 L.d = ++R.d;
2417 goto r_op_change;
2418 case 'p':
2419 R.d++;
2420 goto r_op_change;
2421 case 'M':
2422 L.d = --R.d;
2423 goto r_op_change;
2424 case 'm':
2425 R.d--;
2426 goto r_op_change;
2427 case '!':
2428 L.d = istrue(X.v) ? 0 : 1;
2429 break;
2430 case '-':
2431 L.d = -R.d;
2432 break;
2433 r_op_change:
2434 setvar_i(X.v, R.d);
2435 }
2436 setvar_i(res, L.d);
2437 break;
2438
2439 case XC( OC_FIELD ):
2440 R.i = (int)getvar_i(R.v);
2441 if (R.i == 0) {
2442 res = V[F0];
2443 } else {
2444 split_f0();
2445 if (R.i > nfields)
2446 fsrealloc(R.i);
2447
2448 res = &Fields[R.i-1];
2449 }
2450 break;
2451
2452 /* concatenation (" ") and index joining (",") */
2453 case XC( OC_CONCAT ):
2454 case XC( OC_COMMA ):
2455 opn = bb_strlen(L.s) + bb_strlen(R.s) + 2;
2456 X.s = (char *)xmalloc(opn);
2457 strcpy(X.s, L.s);
2458 if ((opinfo & OPCLSMASK) == OC_COMMA) {
2459 L.s = getvar_s(V[SUBSEP]);
2460 X.s = (char *)xrealloc(X.s, opn + bb_strlen(L.s));
2461 strcat(X.s, L.s);
2462 }
2463 strcat(X.s, R.s);
2464 setvar_p(res, X.s);
2465 break;
2466
2467 case XC( OC_LAND ):
2468 setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0);
2469 break;
2470
2471 case XC( OC_LOR ):
2472 setvar_i(res, istrue(L.v) ? 1 : ptest(op->r.n));
2473 break;
2474
2475 case XC( OC_BINARY ):
2476 case XC( OC_REPLACE ):
2477 R.d = getvar_i(R.v);
2478 switch (opn) {
2479 case '+':
2480 L.d += R.d;
2481 break;
2482 case '-':
2483 L.d -= R.d;
2484 break;
2485 case '*':
2486 L.d *= R.d;
2487 break;
2488 case '/':
2489 if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO);
2490 L.d /= R.d;
2491 break;
2492 case '&':
2493#ifdef CONFIG_FEATURE_AWK_MATH
2494 L.d = pow(L.d, R.d);
2495#else
2496 runtime_error(EMSG_NO_MATH);
2497#endif
2498 break;
2499 case '%':
2500 if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO);
2501 L.d -= (int)(L.d / R.d) * R.d;
2502 break;
2503 }
2504 res = setvar_i(((opinfo&OPCLSMASK) == OC_BINARY) ? res : X.v, L.d);
2505 break;
2506
2507 case XC( OC_COMPARE ):
2508 if (is_numeric(L.v) && is_numeric(R.v)) {
2509 L.d = getvar_i(L.v) - getvar_i(R.v);
2510 } else {
2511 L.s = getvar_s(L.v);
2512 R.s = getvar_s(R.v);
2513 L.d = icase ? strcasecmp(L.s, R.s) : strcmp(L.s, R.s);
2514 }
2515 switch (opn & 0xfe) {
2516 case 0:
2517 R.i = (L.d > 0);
2518 break;
2519 case 2:
2520 R.i = (L.d >= 0);
2521 break;
2522 case 4:
2523 R.i = (L.d == 0);
2524 break;
2525 }
2526 setvar_i(res, (opn & 0x1 ? R.i : !R.i) ? 1 : 0);
2527 break;
2528
2529 default:
2530 runtime_error(EMSG_POSSIBLE_ERROR);
2531 }
2532 if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS)
2533 op = op->a.n;
2534 if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS)
2535 break;
2536 if (nextrec)
2537 break;
2538 }
2539 nvfree(v1);
2540 return res;
2541}
2542
2543
2544/* -------- main & co. -------- */
2545
2546static int awk_exit(int r) {
2547
2548 unsigned int i;
2549 hash_item *hi;
2550 static var tv;
2551
2552 if (! exiting) {
2553 exiting = TRUE;
2554 nextrec = FALSE;
2555 evaluate(endseq.first, &tv);
2556 }
2557
2558 /* waiting for children */
2559 for (i=0; i<fdhash->csize; i++) {
2560 hi = fdhash->items[i];
2561 while(hi) {
2562 if (hi->data.rs.F && hi->data.rs.is_pipe)
2563 pclose(hi->data.rs.F);
2564 hi = hi->next;
2565 }
2566 }
2567
2568 exit(r);
2569}
2570
2571/* if expr looks like "var=value", perform assignment and return 1,
2572 * otherwise return 0 */
2573static int is_assignment(char *expr) {
2574
2575 char *exprc, *s, *s0, *s1;
2576
2577 exprc = bb_xstrdup(expr);
2578 if (!isalnum_(*exprc) || (s = strchr(exprc, '=')) == NULL) {
2579 free(exprc);
2580 return FALSE;
2581 }
2582
2583 *(s++) = '\0';
2584 s0 = s1 = s;
2585 while (*s)
2586 *(s1++) = nextchar(&s);
2587
2588 *s1 = '\0';
2589 setvar_u(newvar(exprc), s0);
2590 free(exprc);
2591 return TRUE;
2592}
2593
2594/* switch to next input file */
2595static rstream *next_input_file(void) {
2596
2597 static rstream rsm;
2598 FILE *F = NULL;
2599 char *fname, *ind;
2600 static int files_happen = FALSE;
2601
2602 if (rsm.F) fclose(rsm.F);
2603 rsm.F = NULL;
2604 rsm.pos = rsm.adv = 0;
2605
2606 do {
2607 if (getvar_i(V[ARGIND])+1 >= getvar_i(V[ARGC])) {
2608 if (files_happen)
2609 return NULL;
2610 fname = "-";
2611 F = stdin;
2612 } else {
2613 ind = getvar_s(incvar(V[ARGIND]));
2614 fname = getvar_s(findvar(iamarray(V[ARGV]), ind));
2615 if (fname && *fname && !is_assignment(fname))
2616 F = afopen(fname, "r");
2617 }
2618 } while (!F);
2619
2620 files_happen = TRUE;
2621 setvar_s(V[FILENAME], fname);
2622 rsm.F = F;
2623 return &rsm;
2624}
2625
2626extern int awk_main(int argc, char **argv) {
2627
2628 char *s, *s1;
2629 int i, j, c;
2630 var *v;
2631 static var tv;
2632 char **envp;
2633 static int from_file = FALSE;
2634 rstream *rsm;
2635 FILE *F, *stdfiles[3];
2636 static char * stdnames = "/dev/stdin\0/dev/stdout\0/dev/stderr";
2637
2638 /* allocate global buffer */
2639 buf = xmalloc(MAXVARFMT+1);
2640
2641 vhash = hash_init();
2642 ahash = hash_init();
2643 fdhash = hash_init();
2644 fnhash = hash_init();
2645
2646 /* initialize variables */
2647 for (i=0; *vNames; i++) {
2648 V[i] = v = newvar(nextword(&vNames));
2649 if (*vValues != '\377')
2650 setvar_s(v, nextword(&vValues));
2651 else
2652 setvar_i(v, 0);
2653
2654 if (*vNames == '*') {
2655 v->type |= VF_SPECIAL;
2656 vNames++;
2657 }
2658 }
2659
2660 handle_special(V[FS]);
2661 handle_special(V[RS]);
2662
2663 stdfiles[0] = stdin;
2664 stdfiles[1] = stdout;
2665 stdfiles[2] = stderr;
2666 for (i=0; i<3; i++) {
2667 rsm = newfile(nextword(&stdnames));
2668 rsm->F = stdfiles[i];
2669 }
2670
2671 for (envp=environ; *envp; envp++) {
2672 s = bb_xstrdup(*envp);
2673 s1 = strchr(s, '=');
2674 if (!s1) {
2675 goto keep_going;
2676 }
2677 *(s1++) = '\0';
2678 setvar_u(findvar(iamarray(V[ENVIRON]), s), s1);
2679keep_going:
2680 free(s);
2681 }
2682
2683 while((c = getopt(argc, argv, "F:v:f:W:")) != EOF) {
2684 switch (c) {
2685 case 'F':
2686 setvar_s(V[FS], optarg);
2687 break;
2688 case 'v':
2689 if (! is_assignment(optarg))
2690 bb_show_usage();
2691 break;
2692 case 'f':
2693 from_file = TRUE;
2694 F = afopen(programname = optarg, "r");
2695 s = NULL;
2696 /* one byte is reserved for some trick in next_token */
2697 for (i=j=1; j>0; i+=j) {
2698 s = (char *)xrealloc(s, i+4096);
2699 j = fread(s+i, 1, 4094, F);
2700 }
2701 s[i] = '\0';
2702 fclose(F);
2703 parse_program(s+1);
2704 free(s);
2705 break;
2706 case 'W':
2707 bb_error_msg("Warning: unrecognized option '-W %s' ignored\n", optarg);
2708 break;
2709
2710 default:
2711 bb_show_usage();
2712 }
2713 }
2714
2715 if (!from_file) {
2716 if (argc == optind)
2717 bb_show_usage();
2718 programname="cmd. line";
2719 parse_program(argv[optind++]);
2720
2721 }
2722
2723 /* fill in ARGV array */
2724 setvar_i(V[ARGC], argc - optind + 1);
2725 setari_u(V[ARGV], 0, "awk");
2726 for(i=optind; i < argc; i++)
2727 setari_u(V[ARGV], i+1-optind, argv[i]);
2728
2729 evaluate(beginseq.first, &tv);
2730 if (! mainseq.first && ! endseq.first)
2731 awk_exit(EXIT_SUCCESS);
2732
2733 /* input file could already be opened in BEGIN block */
2734 if (! iF) iF = next_input_file();
2735
2736 /* passing through input files */
2737 while (iF) {
2738
2739 nextfile = FALSE;
2740 setvar_i(V[FNR], 0);
2741
2742 while ((c = awk_getline(iF, V[F0])) > 0) {
2743
2744 nextrec = FALSE;
2745 incvar(V[NR]);
2746 incvar(V[FNR]);
2747 evaluate(mainseq.first, &tv);
2748
2749 if (nextfile)
2750 break;
2751 }
2752
2753 if (c < 0)
2754 runtime_error(strerror(errno));
2755
2756 iF = next_input_file();
2757
2758 }
2759
2760 awk_exit(EXIT_SUCCESS);
2761
2762 return 0;
2763}
2764
diff --git a/busybox/editors/patch.c b/busybox/editors/patch.c
new file mode 100644
index 000000000..6a68d2ef8
--- /dev/null
+++ b/busybox/editors/patch.c
@@ -0,0 +1,290 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * busybox patch applet to handle the unified diff format.
4 * Copyright (C) 2003 Glenn McGrath <bug1@iinet.net.au>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 *
20 *
21 *
22 * This applet is written to work with patches generated by GNU diff,
23 * where there is equivalent functionality busybox patch shall behave
24 * as per GNU patch.
25 *
26 * There is a SUSv3 specification for patch, however it looks to be
27 * incomplete, it doesnt even mention unified diff format.
28 * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html
29 *
30 * Issues
31 * - Non-interactive
32 * - Patches must apply cleanly or the hunk will fail.
33 * - Reject file isnt saved
34 * -
35 */
36
37#include <getopt.h>
38#include <string.h>
39#include <stdlib.h>
40#include <unistd.h>
41#include "busybox.h"
42#include "libbb.h"
43
44static int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count)
45{
46 int i = 0;
47
48 while (src_stream && (i < lines_count)) {
49 char *line;
50 line = bb_get_line_from_file(src_stream);
51 if (line == NULL) {
52 break;
53 }
54 if (fputs(line, dest_stream) == EOF) {
55 bb_perror_msg_and_die("Error writing to new file");
56 }
57 free(line);
58
59 i++;
60 }
61 return(i);
62}
63
64/* If patch_level is -1 it will remove all directory names
65 * char *line must be greater than 4 chars
66 * returns NULL if the file doesnt exist or error
67 * returns malloc'ed filename
68 */
69
70static unsigned char *extract_filename(char *line, unsigned short patch_level)
71{
72 char *filename_start_ptr = line + 4;
73 int i;
74
75 /* Terminate string at end of source filename */
76 {
77 char *line_ptr;
78 line_ptr = strchr(filename_start_ptr, '\t');
79 if (!line_ptr) {
80 bb_perror_msg("Malformed line %s", line);
81 return(NULL);
82 }
83 *line_ptr = '\0';
84 }
85
86 /* Skip over (patch_level) number of leading directories */
87 for (i = 0; i < patch_level; i++) {
88 char *dirname_ptr;
89
90 dirname_ptr = strchr(filename_start_ptr, '/');
91 if (!dirname_ptr) {
92 break;
93 }
94 filename_start_ptr = dirname_ptr + 1;
95 }
96
97 return(bb_xstrdup(filename_start_ptr));
98}
99
100static int file_doesnt_exist(const char *filename)
101{
102 struct stat statbuf;
103 return(stat(filename, &statbuf));
104}
105
106extern int patch_main(int argc, char **argv)
107{
108 unsigned int patch_level = -1;
109 char *patch_line;
110 int ret = 0;
111
112 /* Handle 'p' option */
113 if (argv[1] && (argv[1][0] == '-') && (argv[1][1] == 'p')) {
114 patch_level = atoi(&argv[1][2]);
115 }
116
117 patch_line = bb_get_line_from_file(stdin);
118 while (patch_line) {
119 FILE *src_stream;
120 FILE *dst_stream;
121 char *original_filename;
122 char *new_filename;
123 char *backup_filename;
124 unsigned int src_cur_line = 1;
125 unsigned int dest_cur_line = 0;
126 unsigned int dest_beg_line;
127 unsigned int bad_hunk_count = 0;
128 unsigned int hunk_count = 0;
129 char copy_trailing_lines_flag = 0;
130
131 /* Skip everything upto the "---" marker
132 * No need to parse the lines "Only in <dir>", and "diff <args>"
133 */
134 while (patch_line && strncmp(patch_line, "--- ", 4) != 0) {
135 free(patch_line);
136 patch_line = bb_get_line_from_file(stdin);
137 }
138
139 /* Extract the filename used before the patch was generated */
140 original_filename = extract_filename(patch_line, patch_level);
141 free(patch_line);
142
143 patch_line = bb_get_line_from_file(stdin);
144 if (strncmp(patch_line, "+++ ", 4) != 0) {
145 ret = 2;
146 bb_error_msg("Invalid patch");
147 continue;
148 }
149 new_filename = extract_filename(patch_line, patch_level);
150 free(patch_line);
151
152 if (file_doesnt_exist(new_filename)) {
153 char *line_ptr;
154 /* Create leading directories */
155 line_ptr = strrchr(new_filename, '/');
156 if (line_ptr) {
157 *line_ptr = '\0';
158 bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
159 *line_ptr = '/';
160 }
161 dst_stream = bb_xfopen(new_filename, "w+");
162 backup_filename = NULL;
163 } else {
164 backup_filename = xmalloc(strlen(new_filename) + 6);
165 strcpy(backup_filename, new_filename);
166 strcat(backup_filename, ".orig");
167 if (rename(new_filename, backup_filename) == -1) {
168 bb_perror_msg_and_die("Couldnt create file %s", backup_filename);
169 }
170 dst_stream = bb_xfopen(new_filename, "w");
171 }
172
173 if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) {
174 src_stream = NULL;
175 } else {
176 if (strcmp(original_filename, new_filename) == 0) {
177 src_stream = bb_xfopen(backup_filename, "r");
178 } else {
179 src_stream = bb_xfopen(original_filename, "r");
180 }
181 }
182
183 printf("patching file %s\n", new_filename);
184
185 /* Handle each hunk */
186 patch_line = bb_get_line_from_file(stdin);
187 while (patch_line) {
188 unsigned int count;
189 unsigned int src_beg_line;
190 unsigned int unused;
191 unsigned int hunk_offset_start = 0;
192 int hunk_error = 0;
193
194 /* This bit should be improved */
195 if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) &&
196 (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) &&
197 (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) {
198 /* No more hunks for this file */
199 break;
200 }
201 free(patch_line);
202 hunk_count++;
203
204 if (src_beg_line && dest_beg_line) {
205 /* Copy unmodified lines upto start of hunk */
206 /* src_beg_line will be 0 if its a new file */
207 count = src_beg_line - src_cur_line;
208 if (copy_lines(src_stream, dst_stream, count) != count) {
209 bb_error_msg_and_die("Bad src file");
210 }
211 src_cur_line += count;
212 dest_cur_line += count;
213 copy_trailing_lines_flag = 1;
214 }
215 hunk_offset_start = src_cur_line;
216
217 while ((patch_line = bb_get_line_from_file(stdin)) != NULL) {
218 if ((*patch_line == '-') || (*patch_line == ' ')) {
219 char *src_line = NULL;
220 if (src_stream) {
221 src_line = bb_get_line_from_file(src_stream);
222 if (!src_line) {
223 hunk_error++;
224 break;
225 } else {
226 src_cur_line++;
227 }
228 if (strcmp(src_line, patch_line + 1) != 0) {
229 bb_error_msg("Hunk #%d FAILED at %d.", hunk_count, hunk_offset_start);
230 hunk_error++;
231 free(patch_line);
232 break;
233 }
234 free(src_line);
235 }
236 if (*patch_line == ' ') {
237 fputs(patch_line + 1, dst_stream);
238 dest_cur_line++;
239 }
240 } else if (*patch_line == '+') {
241 fputs(patch_line + 1, dst_stream);
242 dest_cur_line++;
243 } else {
244 break;
245 }
246 free(patch_line);
247 }
248 if (hunk_error) {
249 bad_hunk_count++;
250 }
251 }
252
253 /* Cleanup last patched file */
254 if (copy_trailing_lines_flag) {
255 copy_lines(src_stream, dst_stream, -1);
256 }
257 if (src_stream) {
258 fclose(src_stream);
259 }
260 if (dst_stream) {
261 fclose(dst_stream);
262 }
263 if (bad_hunk_count) {
264 if (!ret) {
265 ret = 1;
266 }
267 bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count);
268 } else {
269 /* It worked, we can remove the backup */
270 if (backup_filename) {
271 unlink(backup_filename);
272 }
273 if ((dest_cur_line == 0) || (dest_beg_line == 0)) {
274 /* The new patched file is empty, remove it */
275 if (unlink(new_filename) == -1) {
276 bb_perror_msg_and_die("Couldnt remove file %s", new_filename);
277 }
278 if (unlink(original_filename) == -1) {
279 bb_perror_msg_and_die("Couldnt remove original file %s", new_filename);
280 }
281 }
282 }
283 }
284
285 /* 0 = SUCCESS
286 * 1 = Some hunks failed
287 * 2 = More serious problems
288 */
289 return(ret);
290}
diff --git a/busybox/editors/sed.c b/busybox/editors/sed.c
new file mode 100644
index 000000000..3d6871621
--- /dev/null
+++ b/busybox/editors/sed.c
@@ -0,0 +1,1220 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * sed.c - very minimalist version of sed
4 *
5 * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley
6 * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org>
7 * Copyright (C) 2002 Matt Kraai
8 * Copyright (C) 2003 by Glenn McGrath <bug1@iinet.net.au>
9 * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27/* Code overview.
28
29 Files are laid out to avoid unnecessary function declarations. So for
30 example, every function add_cmd calls occurs before add_cmd in this file.
31
32 add_cmd() is called on each line of sed command text (from a file or from
33 the command line). It calls get_address() and parse_cmd_args(). The
34 resulting sed_cmd_t structures are appended to a linked list
35 (sed_cmd_head/sed_cmd_tail).
36
37 process_file() does actual sedding, reading data lines from an input FILE *
38 (which could be stdin) and applying the sed command list (sed_cmd_head) to
39 each of the resulting lines.
40
41 sed_main() is where external code calls into this, with a command line.
42*/
43
44
45/*
46 Supported features and commands in this version of sed:
47
48 - comments ('#')
49 - address matching: num|/matchstr/[,num|/matchstr/|$]command
50 - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags)
51 - edit commands: (a)ppend, (i)nsert, (c)hange
52 - file commands: (r)ead
53 - backreferences in substitution expressions (\1, \2...\9)
54 - grouped commands: {cmd1;cmd2}
55 - transliteration (y/source-chars/dest-chars/)
56 - pattern space hold space storing / swapping (g, h, x)
57 - labels / branching (: label, b, t)
58
59 (Note: Specifying an address (range) to match is *optional*; commands
60 default to the whole pattern space if no specific address match was
61 requested.)
62
63 Unsupported features:
64
65 - GNU extensions
66 - and more.
67
68 Todo:
69
70 - Create a wrapper around regex to make libc's regex conform with sed
71 - Fix bugs
72
73
74 Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
75*/
76
77#include <stdio.h>
78#include <unistd.h> /* for getopt() */
79#include <regex.h>
80#include <string.h> /* for strdup() */
81#include <errno.h>
82#include <ctype.h> /* for isspace() */
83#include <stdlib.h>
84#include "busybox.h"
85
86typedef struct sed_cmd_s {
87 /* Ordered by alignment requirements: currently 36 bytes on x86 */
88
89 /* address storage */
90 regex_t *beg_match; /* sed -e '/match/cmd' */
91 regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */
92 regex_t *sub_match; /* For 's/sub_match/string/' */
93 int beg_line; /* 'sed 1p' 0 == apply commands to all lines */
94 int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
95
96 FILE *file; /* File (sr) command writes to, -1 for none. */
97 char *string; /* Data string for (saicytb) commands. */
98
99 unsigned short which_match; /* (s) Which match to replace (0 for all) */
100
101 /* Bitfields (gcc won't group them if we don't) */
102 unsigned int invert:1; /* the '!' after the address */
103 unsigned int in_match:1; /* Next line also included in match? */
104 unsigned int no_newline:1; /* Last line written by (sr) had no '\n' */
105 unsigned int sub_p:1; /* (s) print option */
106
107
108 /* GENERAL FIELDS */
109 char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
110 struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
111} sed_cmd_t;
112
113/* globals */
114/* options */
115static int be_quiet = 0, in_place=0, regex_type=0;
116FILE *nonstdout;
117char *outname;
118
119
120static const char bad_format_in_subst[] =
121 "bad format in substitution expression";
122const char *const semicolon_whitespace = "; \n\r\t\v";
123
124regmatch_t regmatch[10];
125static regex_t *previous_regex_ptr = NULL;
126
127/* linked list of sed commands */
128static sed_cmd_t sed_cmd_head;
129static sed_cmd_t *sed_cmd_tail = &sed_cmd_head;
130
131/* Linked list of append lines */
132struct append_list {
133 char *string;
134 struct append_list *next;
135};
136struct append_list *append_head=NULL, *append_tail=NULL;
137
138#ifdef CONFIG_FEATURE_CLEAN_UP
139static void free_and_close_stuff(void)
140{
141 sed_cmd_t *sed_cmd = sed_cmd_head.next;
142
143 while(append_head) {
144 append_tail=append_head->next;
145 free(append_head->string);
146 free(append_head);
147 append_head=append_tail;
148 }
149
150 while (sed_cmd) {
151 sed_cmd_t *sed_cmd_next = sed_cmd->next;
152
153 if(sed_cmd->file)
154 bb_xprint_and_close_file(sed_cmd->file);
155
156 if (sed_cmd->beg_match) {
157 regfree(sed_cmd->beg_match);
158 free(sed_cmd->beg_match);
159 }
160 if (sed_cmd->end_match) {
161 regfree(sed_cmd->end_match);
162 free(sed_cmd->end_match);
163 }
164 if (sed_cmd->sub_match) {
165 regfree(sed_cmd->sub_match);
166 free(sed_cmd->sub_match);
167 }
168 free(sed_cmd->string);
169 free(sed_cmd);
170 sed_cmd = sed_cmd_next;
171 }
172}
173#endif
174
175/* If something bad happens during -i operation, delete temp file */
176
177static void cleanup_outname(void)
178{
179 if(outname) unlink(outname);
180}
181
182/* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */
183
184static void parse_escapes(char *dest, const char *string, int len, char from, char to)
185{
186 int i=0;
187
188 while(i<len) {
189 if(string[i] == '\\') {
190 if(!to || string[i+1] == from) {
191 *(dest++) = to ? to : string[i+1];
192 i+=2;
193 continue;
194 } else *(dest++)=string[i++];
195 }
196 *(dest++) = string[i++];
197 }
198 *dest=0;
199}
200
201static char *copy_parsing_slashn(const char *string, int len)
202{
203 char *dest=xmalloc(len+1);
204
205 parse_escapes(dest,string,len,'n','\n');
206 return dest;
207}
208
209
210/*
211 * index_of_next_unescaped_regexp_delim - walks left to right through a string
212 * beginning at a specified index and returns the index of the next regular
213 * expression delimiter (typically a forward * slash ('/')) not preceded by
214 * a backslash ('\').
215 */
216static int index_of_next_unescaped_regexp_delim(const char delimiter,
217 const char *str)
218{
219 int bracket = -1;
220 int escaped = 0;
221 int idx = 0;
222 char ch;
223
224 for (; (ch = str[idx]); idx++) {
225 if (bracket != -1) {
226 if (ch == ']' && !(bracket == idx - 1 || (bracket == idx - 2
227 && str[idx - 1] == '^')))
228 bracket = -1;
229 } else if (escaped)
230 escaped = 0;
231 else if (ch == '\\')
232 escaped = 1;
233 else if (ch == '[')
234 bracket = idx;
235 else if (ch == delimiter)
236 return idx;
237 }
238
239 /* if we make it to here, we've hit the end of the string */
240 return -1;
241}
242
243/*
244 * Returns the index of the third delimiter
245 */
246static int parse_regex_delim(const char *cmdstr, char **match, char **replace)
247{
248 const char *cmdstr_ptr = cmdstr;
249 char delimiter;
250 int idx = 0;
251
252 /* verify that the 's' or 'y' is followed by something. That something
253 * (typically a 'slash') is now our regexp delimiter... */
254 if (*cmdstr == '\0') bb_error_msg_and_die(bad_format_in_subst);
255 delimiter = *(cmdstr_ptr++);
256
257 /* save the match string */
258 idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
259 if (idx == -1) {
260 bb_error_msg_and_die(bad_format_in_subst);
261 }
262 *match = copy_parsing_slashn(cmdstr_ptr, idx);
263
264 /* save the replacement string */
265 cmdstr_ptr += idx + 1;
266 idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
267 if (idx == -1) {
268 bb_error_msg_and_die(bad_format_in_subst);
269 }
270 *replace = copy_parsing_slashn(cmdstr_ptr, idx);
271
272 return ((cmdstr_ptr - cmdstr) + idx);
273}
274
275/*
276 * returns the index in the string just past where the address ends.
277 */
278static int get_address(char *my_str, int *linenum, regex_t ** regex)
279{
280 char *pos = my_str;
281
282 if (isdigit(*my_str)) {
283 *linenum = strtol(my_str, &pos, 10);
284 /* endstr shouldnt ever equal NULL */
285 } else if (*my_str == '$') {
286 *linenum = -1;
287 pos++;
288 } else if (*my_str == '/' || *my_str == '\\') {
289 int next;
290 char delimiter;
291 char *temp;
292
293 if (*my_str == '\\') delimiter = *(++pos);
294 else delimiter = '/';
295 next = index_of_next_unescaped_regexp_delim(delimiter, ++pos);
296 if (next == -1)
297 bb_error_msg_and_die("unterminated match expression");
298
299 temp=copy_parsing_slashn(pos,next);
300 *regex = (regex_t *) xmalloc(sizeof(regex_t));
301 xregcomp(*regex, temp, regex_type|REG_NEWLINE);
302 free(temp);
303 /* Move position to next character after last delimiter */
304 pos+=(next+1);
305 }
306 return pos - my_str;
307}
308
309/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */
310static int parse_file_cmd(sed_cmd_t * sed_cmd, const char *filecmdstr, char **retval)
311{
312 int start = 0, idx, hack=0;
313
314 /* Skip whitespace, then grab filename to end of line */
315 while (isspace(filecmdstr[start])) start++;
316 idx=start;
317 while(filecmdstr[idx] && filecmdstr[idx]!='\n') idx++;
318 /* If lines glued together, put backslash back. */
319 if(filecmdstr[idx]=='\n') hack=1;
320 if(idx==start) bb_error_msg_and_die("Empty filename");
321 *retval = bb_xstrndup(filecmdstr+start, idx-start+hack+1);
322 if(hack) *(idx+*retval)='\\';
323
324 return idx;
325}
326
327static int parse_subst_cmd(sed_cmd_t * const sed_cmd, char *substr)
328{
329 int cflags = regex_type;
330 char *match;
331 int idx = 0;
332
333 /*
334 * A substitution command should look something like this:
335 * s/match/replace/ #gIpw
336 * || | |||
337 * mandatory optional
338 */
339 idx = parse_regex_delim(substr, &match, &sed_cmd->string);
340
341 /* determine the number of back references in the match string */
342 /* Note: we compute this here rather than in the do_subst_command()
343 * function to save processor time, at the expense of a little more memory
344 * (4 bits) per sed_cmd */
345
346 /* process the flags */
347
348 sed_cmd->which_match=1;
349 while (substr[++idx]) {
350 /* Parse match number */
351 if(isdigit(substr[idx])) {
352 if(match[0]!='^') {
353 /* Match 0 treated as all, multiple matches we take the last one. */
354 char *pos=substr+idx;
355 sed_cmd->which_match=(unsigned short)strtol(substr+idx,&pos,10);
356 idx=pos-substr;
357 }
358 continue;
359 }
360 /* Skip spaces */
361 if(isspace(substr[idx])) continue;
362
363 switch (substr[idx]) {
364 /* Replace all occurrences */
365 case 'g':
366 if (match[0] != '^') sed_cmd->which_match = 0;
367 break;
368 /* Print pattern space */
369 case 'p':
370 sed_cmd->sub_p = 1;
371 break;
372 case 'w':
373 {
374 char *temp;
375 idx+=parse_file_cmd(sed_cmd,substr+idx,&temp);
376
377 break;
378 }
379 /* Ignore case (gnu exension) */
380 case 'I':
381 cflags |= REG_ICASE;
382 break;
383 case ';':
384 case '}':
385 goto out;
386 default:
387 bb_error_msg_and_die("bad option in substitution expression");
388 }
389 }
390out:
391 /* compile the match string into a regex */
392 if (*match != '\0') {
393 /* If match is empty, we use last regex used at runtime */
394 sed_cmd->sub_match = (regex_t *) xmalloc(sizeof(regex_t));
395 xregcomp(sed_cmd->sub_match, match, cflags);
396 }
397 free(match);
398
399 return idx;
400}
401
402/*
403 * Process the commands arguments
404 */
405static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr)
406{
407 /* handle (s)ubstitution command */
408 if (sed_cmd->cmd == 's') cmdstr += parse_subst_cmd(sed_cmd, cmdstr);
409 /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
410 else if (strchr("aic", sed_cmd->cmd)) {
411 if ((sed_cmd->end_line || sed_cmd->end_match) && sed_cmd->cmd != 'c')
412 bb_error_msg_and_die
413 ("only a beginning address can be specified for edit commands");
414 for(;;) {
415 if(*cmdstr=='\n' || *cmdstr=='\\') {
416 cmdstr++;
417 break;
418 } else if(isspace(*cmdstr)) cmdstr++;
419 else break;
420 }
421 sed_cmd->string = bb_xstrdup(cmdstr);
422 parse_escapes(sed_cmd->string,sed_cmd->string,strlen(cmdstr),0,0);
423 cmdstr += strlen(cmdstr);
424 /* handle file cmds: (r)ead */
425 } else if(strchr("rw", sed_cmd->cmd)) {
426 if (sed_cmd->end_line || sed_cmd->end_match)
427 bb_error_msg_and_die("Command only uses one address");
428 cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string);
429 if(sed_cmd->cmd=='w')
430 sed_cmd->file=bb_xfopen(sed_cmd->string,"w");
431 /* handle branch commands */
432 } else if (strchr(":bt", sed_cmd->cmd)) {
433 int length;
434
435 while(isspace(*cmdstr)) cmdstr++;
436 length = strcspn(cmdstr, semicolon_whitespace);
437 if (length) {
438 sed_cmd->string = strndup(cmdstr, length);
439 cmdstr += length;
440 }
441 }
442 /* translation command */
443 else if (sed_cmd->cmd == 'y') {
444 char *match, *replace;
445 int i=cmdstr[0];
446
447 cmdstr+=parse_regex_delim(cmdstr, &match, &replace)+1;
448 /* \n already parsed, but \delimiter needs unescaping. */
449 parse_escapes(match,match,strlen(match),i,i);
450 parse_escapes(replace,replace,strlen(replace),i,i);
451
452 sed_cmd->string = xcalloc(1, (strlen(match) + 1) * 2);
453 for (i = 0; match[i] && replace[i]; i++) {
454 sed_cmd->string[i * 2] = match[i];
455 sed_cmd->string[(i * 2) + 1] = replace[i];
456 }
457 free(match);
458 free(replace);
459 }
460 /* if it wasnt a single-letter command that takes no arguments
461 * then it must be an invalid command.
462 */
463 else if (strchr("dDgGhHlnNpPqx={}", sed_cmd->cmd) == 0) {
464 bb_error_msg_and_die("Unsupported command %c", sed_cmd->cmd);
465 }
466
467 /* give back whatever's left over */
468 return (cmdstr);
469}
470
471
472/* Parse address+command sets, skipping comment lines. */
473
474void add_cmd(char *cmdstr)
475{
476 static char *add_cmd_line=NULL;
477 sed_cmd_t *sed_cmd;
478 int temp;
479
480 /* Append this line to any unfinished line from last time. */
481 if(add_cmd_line) {
482 int lastlen=strlen(add_cmd_line);
483 char *tmp=xmalloc(lastlen+strlen(cmdstr)+2);
484
485 memcpy(tmp,add_cmd_line,lastlen);
486 tmp[lastlen]='\n';
487 strcpy(tmp+lastlen+1,cmdstr);
488 free(add_cmd_line);
489 cmdstr=add_cmd_line=tmp;
490 } else add_cmd_line=NULL;
491
492 /* If this line ends with backslash, request next line. */
493 temp=strlen(cmdstr);
494 if(temp && cmdstr[temp-1]=='\\') {
495 if(!add_cmd_line) add_cmd_line=strdup(cmdstr);
496 add_cmd_line[temp-1]=0;
497 return;
498 }
499
500 /* Loop parsing all commands in this line. */
501 while(*cmdstr) {
502 /* Skip leading whitespace and semicolons */
503 cmdstr += strspn(cmdstr, semicolon_whitespace);
504
505 /* If no more commands, exit. */
506 if(!*cmdstr) break;
507
508 /* if this is a comment, jump past it and keep going */
509 if (*cmdstr == '#') {
510 /* "#n" is the same as using -n on the command line */
511 if (cmdstr[1] == 'n') be_quiet++;
512 if(!(cmdstr=strpbrk(cmdstr, "\n\r"))) break;
513 continue;
514 }
515
516 /* parse the command
517 * format is: [addr][,addr][!]cmd
518 * |----||-----||-|
519 * part1 part2 part3
520 */
521
522 sed_cmd = xcalloc(1, sizeof(sed_cmd_t));
523
524 /* first part (if present) is an address: either a '$', a number or a /regex/ */
525 cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
526
527 /* second part (if present) will begin with a comma */
528 if (*cmdstr == ',') {
529 int idx;
530
531 cmdstr++;
532 idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
533 if (!idx) bb_error_msg_and_die("get_address: no address found in string\n");
534 cmdstr += idx;
535 }
536
537 /* skip whitespace before the command */
538 while (isspace(*cmdstr)) cmdstr++;
539
540 /* Check for inversion flag */
541 if (*cmdstr == '!') {
542 sed_cmd->invert = 1;
543 cmdstr++;
544
545 /* skip whitespace before the command */
546 while (isspace(*cmdstr)) cmdstr++;
547 }
548
549 /* last part (mandatory) will be a command */
550 if (!*cmdstr) bb_error_msg_and_die("missing command");
551 sed_cmd->cmd = *(cmdstr++);
552 cmdstr = parse_cmd_args(sed_cmd, cmdstr);
553
554 /* Add the command to the command array */
555 sed_cmd_tail->next = sed_cmd;
556 sed_cmd_tail = sed_cmd_tail->next;
557 }
558
559 /* If we glued multiple lines together, free the memory. */
560 if(add_cmd_line) {
561 free(add_cmd_line);
562 add_cmd_line=NULL;
563 }
564}
565
566struct pipeline {
567 char *buf; /* Space to hold string */
568 int idx; /* Space used */
569 int len; /* Space allocated */
570} pipeline;
571
572#define PIPE_GROW 64
573
574void pipe_putc(char c)
575{
576 if(pipeline.idx==pipeline.len) {
577 pipeline.buf = xrealloc(pipeline.buf, pipeline.len + PIPE_GROW);
578 pipeline.len+=PIPE_GROW;
579 }
580 pipeline.buf[pipeline.idx++] = (c);
581}
582
583static void do_subst_w_backrefs(const char *line, const char *replace)
584{
585 int i,j;
586
587 /* go through the replacement string */
588 for (i = 0; replace[i]; i++) {
589 /* if we find a backreference (\1, \2, etc.) print the backref'ed * text */
590 if (replace[i] == '\\' && replace[i+1]>'0' && replace[i+1]<='9') {
591 int backref=replace[++i]-'0';
592
593 /* print out the text held in regmatch[backref] */
594 if(regmatch[backref].rm_so != -1)
595 for (j = regmatch[backref].rm_so; j < regmatch[backref].rm_eo; j++)
596 pipe_putc(line[j]);
597 }
598
599 /* if we find a backslash escaped character, print the character */
600 else if (replace[i] == '\\') pipe_putc(replace[++i]);
601
602 /* if we find an unescaped '&' print out the whole matched text. */
603 else if (replace[i] == '&')
604 for (j = regmatch[0].rm_so; j < regmatch[0].rm_eo; j++)
605 pipe_putc(line[j]);
606 /* Otherwise just output the character. */
607 else pipe_putc(replace[i]);
608 }
609}
610
611static int do_subst_command(sed_cmd_t * sed_cmd, char **line)
612{
613 char *oldline = *line;
614 int altered = 0;
615 int match_count=0;
616 regex_t *current_regex;
617
618 /* Handle empty regex. */
619 if (sed_cmd->sub_match == NULL) {
620 current_regex = previous_regex_ptr;
621 if(!current_regex)
622 bb_error_msg_and_die("No previous regexp.");
623 } else previous_regex_ptr = current_regex = sed_cmd->sub_match;
624
625 /* Find the first match */
626 if(REG_NOMATCH==regexec(current_regex, oldline, 10, regmatch, 0))
627 return 0;
628
629 /* Initialize temporary output buffer. */
630 pipeline.buf=xmalloc(PIPE_GROW);
631 pipeline.len=PIPE_GROW;
632 pipeline.idx=0;
633
634 /* Now loop through, substituting for matches */
635 do {
636 int i;
637
638 /* Work around bug in glibc regexec, demonstrated by:
639 echo " a.b" | busybox sed 's [^ .]* x g'
640 The match_count check is so not to break
641 echo "hi" | busybox sed 's/^/!/g' */
642 if(!regmatch[0].rm_so && !regmatch[0].rm_eo && match_count) {
643 pipe_putc(*(oldline++));
644 continue;
645 }
646
647 match_count++;
648
649 /* If we aren't interested in this match, output old line to
650 end of match and continue */
651 if(sed_cmd->which_match && sed_cmd->which_match!=match_count) {
652 for(i=0;i<regmatch[0].rm_eo;i++)
653 pipe_putc(oldline[i]);
654 continue;
655 }
656
657 /* print everything before the match */
658 for (i = 0; i < regmatch[0].rm_so; i++) pipe_putc(oldline[i]);
659
660 /* then print the substitution string */
661 do_subst_w_backrefs(oldline, sed_cmd->string);
662
663 /* advance past the match */
664 oldline += regmatch[0].rm_eo;
665 /* flag that something has changed */
666 altered++;
667
668 /* if we're not doing this globally, get out now */
669 if (sed_cmd->which_match) break;
670 } while (*oldline && (regexec(current_regex, oldline, 10, regmatch, 0) != REG_NOMATCH));
671
672 /* Copy rest of string into output pipeline */
673
674 while(*oldline) pipe_putc(*(oldline++));
675 pipe_putc(0);
676
677 free(*line);
678 *line = pipeline.buf;
679 return altered;
680}
681
682/* Set command pointer to point to this label. (Does not handle null label.) */
683static sed_cmd_t *branch_to(const char *label)
684{
685 sed_cmd_t *sed_cmd;
686
687 for (sed_cmd = sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) {
688 if ((sed_cmd->cmd == ':') && (sed_cmd->string) && (strcmp(sed_cmd->string, label) == 0)) {
689 return (sed_cmd);
690 }
691 }
692 bb_error_msg_and_die("Can't find label for jump to `%s'", label);
693}
694
695/* Append copy of string to append buffer */
696static void append(char *s)
697{
698 struct append_list *temp=calloc(1,sizeof(struct append_list));
699
700 if(append_head)
701 append_tail=(append_tail->next=temp);
702 else append_head=append_tail=temp;
703 temp->string=strdup(s);
704}
705
706static void flush_append(void)
707{
708 /* Output appended lines. */
709 while(append_head) {
710 fprintf(nonstdout,"%s\n",append_head->string);
711 append_tail=append_head->next;
712 free(append_head->string);
713 free(append_head);
714 append_head=append_tail;
715 }
716 append_head=append_tail=NULL;
717}
718
719/* Get next line of input, flushing append buffer and noting if we hit EOF
720 * without a newline on the last line.
721 */
722static char *get_next_line(FILE * file, int *no_newline)
723{
724 char *temp;
725 int len;
726
727 flush_append();
728 temp=bb_get_line_from_file(file);
729 if(temp) {
730 len=strlen(temp);
731 if(len && temp[len-1]=='\n') temp[len-1]=0;
732 else *no_newline=1;
733 }
734
735 return temp;
736}
737
738/* Output line of text. missing_newline means the last line output did not
739 end with a newline. no_newline means this line does not end with a
740 newline. */
741
742static int puts_maybe_newline(char *s, FILE *file, int missing_newline, int no_newline)
743{
744 if(missing_newline) fputc('\n',file);
745 fputs(s,file);
746 if(!no_newline) fputc('\n',file);
747
748 if(ferror(file)) {
749 fprintf(stderr,"Write failed.\n");
750 exit(4); /* It's what gnu sed exits with... */
751 }
752
753 return no_newline;
754}
755
756#define sed_puts(s,n) missing_newline=puts_maybe_newline(s,nonstdout,missing_newline,n)
757
758static void process_file(FILE *file)
759{
760 char *pattern_space, *next_line, *hold_space=NULL;
761 static int linenum = 0, missing_newline=0;
762 int no_newline,next_no_newline=0;
763
764 next_line = get_next_line(file,&next_no_newline);
765
766 /* go through every line in the file */
767 for(;;) {
768 sed_cmd_t *sed_cmd;
769 int substituted=0;
770
771 /* Advance to next line. Stop if out of lines. */
772 if(!(pattern_space=next_line)) break;
773 no_newline=next_no_newline;
774
775 /* Read one line in advance so we can act on the last line, the '$' address */
776 next_line = get_next_line(file,&next_no_newline);
777 linenum++;
778restart:
779 /* for every line, go through all the commands */
780 for (sed_cmd = sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) {
781 int old_matched, matched;
782
783 old_matched = sed_cmd->in_match;
784
785 /* Determine if this command matches this line: */
786
787 /* Are we continuing a previous multi-line match? */
788
789 sed_cmd->in_match = sed_cmd->in_match
790
791 /* Or is no range necessary? */
792 || (!sed_cmd->beg_line && !sed_cmd->end_line
793 && !sed_cmd->beg_match && !sed_cmd->end_match)
794
795 /* Or did we match the start of a numerical range? */
796 || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum))
797
798 /* Or does this line match our begin address regex? */
799 || (sed_cmd->beg_match &&
800 !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0))
801
802 /* Or did we match last line of input? */
803 || (sed_cmd->beg_line == -1 && next_line == NULL);
804
805 /* Snapshot the value */
806
807 matched = sed_cmd->in_match;
808
809 /* Is this line the end of the current match? */
810
811 if(matched) {
812 sed_cmd->in_match = !(
813 /* has the ending line come, or is this a single address command? */
814 (sed_cmd->end_line ?
815 sed_cmd->end_line==-1 ?
816 !next_line
817 : sed_cmd->end_line<=linenum
818 : !sed_cmd->end_match)
819 /* or does this line matches our last address regex */
820 || (sed_cmd->end_match && old_matched && (regexec(sed_cmd->end_match, pattern_space, 0, NULL, 0) == 0))
821 );
822 }
823
824 /* Skip blocks of commands we didn't match. */
825 if (sed_cmd->cmd == '{') {
826 if(sed_cmd->invert ? matched : !matched)
827 while(sed_cmd && sed_cmd->cmd!='}') sed_cmd=sed_cmd->next;
828 if(!sed_cmd) bb_error_msg_and_die("Unterminated {");
829 continue;
830 }
831
832 /* Okay, so did this line match? */
833 if (sed_cmd->invert ? !matched : matched) {
834 /* Update last used regex in case a blank substitute BRE is found */
835 if (sed_cmd->beg_match) {
836 previous_regex_ptr = sed_cmd->beg_match;
837 }
838
839 /* actual sedding */
840 switch (sed_cmd->cmd) {
841
842 /* Print line number */
843 case '=':
844 fprintf(nonstdout,"%d\n", linenum);
845 break;
846
847 /* Write the current pattern space up to the first newline */
848 case 'P':
849 {
850 char *tmp = strchr(pattern_space, '\n');
851
852 if (tmp) {
853 *tmp = '\0';
854 sed_puts(pattern_space,1);
855 *tmp = '\n';
856 break;
857 }
858 /* Fall Through */
859 }
860
861 /* Write the current pattern space to output */
862 case 'p':
863 sed_puts(pattern_space,no_newline);
864 break;
865 /* Delete up through first newline */
866 case 'D':
867 {
868 char *tmp = strchr(pattern_space,'\n');
869
870 if(tmp) {
871 tmp=bb_xstrdup(tmp+1);
872 free(pattern_space);
873 pattern_space=tmp;
874 goto restart;
875 }
876 }
877 /* discard this line. */
878 case 'd':
879 goto discard_line;
880
881 /* Substitute with regex */
882 case 's':
883 if(do_subst_command(sed_cmd, &pattern_space)) {
884 substituted|=1;
885
886 /* handle p option */
887 if(sed_cmd->sub_p)
888 sed_puts(pattern_space,no_newline);
889 /* handle w option */
890 if(sed_cmd->file)
891 sed_cmd->no_newline=puts_maybe_newline(pattern_space, sed_cmd->file, sed_cmd->no_newline, no_newline);
892
893 }
894 break;
895
896 /* Append line to linked list to be printed later */
897 case 'a':
898 {
899 append(sed_cmd->string);
900 break;
901 }
902
903 /* Insert text before this line */
904 case 'i':
905 sed_puts(sed_cmd->string,1);
906 break;
907
908 /* Cut and paste text (replace) */
909 case 'c':
910 /* Only triggers on last line of a matching range. */
911 if (!sed_cmd->in_match) sed_puts(sed_cmd->string,1);
912 goto discard_line;
913
914 /* Read file, append contents to output */
915 case 'r':
916 {
917 FILE *outfile;
918
919 outfile = fopen(sed_cmd->string, "r");
920 if (outfile) {
921 char *line;
922
923 while ((line = bb_get_chomped_line_from_file(outfile))
924 != NULL)
925 append(line);
926 bb_xprint_and_close_file(outfile);
927 }
928
929 break;
930 }
931
932 /* Write pattern space to file. */
933 case 'w':
934 sed_cmd->no_newline=puts_maybe_newline(pattern_space,sed_cmd->file, sed_cmd->no_newline,no_newline);
935 break;
936
937 /* Read next line from input */
938 case 'n':
939 if (!be_quiet)
940 sed_puts(pattern_space,no_newline);
941 if (next_line) {
942 free(pattern_space);
943 pattern_space = next_line;
944 no_newline=next_no_newline;
945 next_line = get_next_line(file,&next_no_newline);
946 linenum++;
947 break;
948 }
949 /* fall through */
950
951 /* Quit. End of script, end of input. */
952 case 'q':
953 /* Exit the outer while loop */
954 free(next_line);
955 next_line = NULL;
956 goto discard_commands;
957
958 /* Append the next line to the current line */
959 case 'N':
960 {
961 /* If no next line, jump to end of script and exit. */
962 if (next_line == NULL) {
963 /* Jump to end of script and exit */
964 free(next_line);
965 next_line = NULL;
966 goto discard_line;
967 /* append next_line, read new next_line. */
968 } else {
969 int len=strlen(pattern_space);
970
971 pattern_space = realloc(pattern_space, len + strlen(next_line) + 2);
972 pattern_space[len]='\n';
973 strcpy(pattern_space+len+1, next_line);
974 no_newline=next_no_newline;
975 next_line = get_next_line(file,&next_no_newline);
976 linenum++;
977 }
978 break;
979 }
980
981 /* Test if substition worked, branch if so. */
982 case 't':
983 if (!substituted) break;
984 substituted=0;
985 /* Fall through */
986 /* Branch to label */
987 case 'b':
988 if (!sed_cmd->string) goto discard_commands;
989 else sed_cmd = branch_to(sed_cmd->string);
990 break;
991 /* Transliterate characters */
992 case 'y':
993 {
994 int i;
995
996 for (i = 0; pattern_space[i]; i++) {
997 int j;
998
999 for (j = 0; sed_cmd->string[j]; j += 2) {
1000 if (pattern_space[i] == sed_cmd->string[j]) {
1001 pattern_space[i] = sed_cmd->string[j + 1];
1002 }
1003 }
1004 }
1005
1006 break;
1007 }
1008 case 'g': /* Replace pattern space with hold space */
1009 free(pattern_space);
1010 if (hold_space) {
1011 pattern_space = strdup(hold_space);
1012 no_newline=0;
1013 }
1014 break;
1015 case 'G': /* Append newline and hold space to pattern space */
1016 {
1017 int pattern_space_size = 2;
1018 int hold_space_size = 0;
1019
1020 if (pattern_space)
1021 pattern_space_size += strlen(pattern_space);
1022 if (hold_space) hold_space_size = strlen(hold_space);
1023 pattern_space = xrealloc(pattern_space, pattern_space_size + hold_space_size);
1024 if (pattern_space_size == 2) pattern_space[0]=0;
1025 strcat(pattern_space, "\n");
1026 if (hold_space) strcat(pattern_space, hold_space);
1027 no_newline=0;
1028
1029 break;
1030 }
1031 case 'h': /* Replace hold space with pattern space */
1032 free(hold_space);
1033 hold_space = strdup(pattern_space);
1034 break;
1035 case 'H': /* Append newline and pattern space to hold space */
1036 {
1037 int hold_space_size = 2;
1038 int pattern_space_size = 0;
1039
1040 if (hold_space) hold_space_size += strlen(hold_space);
1041 if (pattern_space)
1042 pattern_space_size = strlen(pattern_space);
1043 hold_space = xrealloc(hold_space,
1044 hold_space_size + pattern_space_size);
1045
1046 if (hold_space_size == 2) hold_space[0]=0;
1047 strcat(hold_space, "\n");
1048 if (pattern_space) strcat(hold_space, pattern_space);
1049
1050 break;
1051 }
1052 case 'x': /* Exchange hold and pattern space */
1053 {
1054 char *tmp = pattern_space;
1055 pattern_space = hold_space;
1056 no_newline=0;
1057 hold_space = tmp;
1058 break;
1059 }
1060 }
1061 }
1062 }
1063
1064 /*
1065 * exit point from sedding...
1066 */
1067discard_commands:
1068 /* we will print the line unless we were told to be quiet ('-n')
1069 or if the line was suppressed (ala 'd'elete) */
1070 if (!be_quiet) sed_puts(pattern_space,no_newline);
1071
1072 /* Delete and such jump here. */
1073discard_line:
1074 flush_append();
1075 free(pattern_space);
1076 }
1077}
1078
1079/* It is possible to have a command line argument with embedded
1080 newlines. This counts as multiple command lines. */
1081
1082static void add_cmd_block(char *cmdstr)
1083{
1084 int go=1;
1085 char *temp=bb_xstrdup(cmdstr),*temp2=temp;
1086
1087 while(go) {
1088 int len=strcspn(temp2,"\n");
1089 if(!temp2[len]) go=0;
1090 else temp2[len]=0;
1091 add_cmd(temp2);
1092 temp2+=len+1;
1093 }
1094 free(temp);
1095}
1096
1097extern int sed_main(int argc, char **argv)
1098{
1099 int status = EXIT_SUCCESS;
1100 int opt;
1101 uint8_t getpat = 1;
1102
1103#ifdef CONFIG_FEATURE_CLEAN_UP
1104 /* destroy command strings on exit */
1105 if (atexit(free_and_close_stuff) == -1)
1106 bb_perror_msg_and_die("atexit");
1107#endif
1108
1109#define LIE_TO_AUTOCONF
1110#ifdef LIE_TO_AUTOCONF
1111 if(argc==2 && !strcmp(argv[1],"--version")) {
1112 printf("This is not GNU sed version 4.0\n");
1113 exit(0);
1114 }
1115#endif
1116
1117 /* do normal option parsing */
1118 while ((opt = getopt(argc, argv, "irne:f:")) > 0) {
1119 switch (opt) {
1120 case 'i':
1121 in_place++;
1122 atexit(cleanup_outname);
1123 break;
1124 case 'r':
1125 regex_type|=REG_EXTENDED;
1126 break;
1127 case 'n':
1128 be_quiet++;
1129 break;
1130 case 'e':
1131 add_cmd_block(optarg);
1132 getpat=0;
1133 break;
1134 case 'f':
1135 {
1136 FILE *cmdfile;
1137 char *line;
1138
1139 cmdfile = bb_xfopen(optarg, "r");
1140
1141 while ((line = bb_get_chomped_line_from_file(cmdfile))
1142 != NULL) {
1143 add_cmd(line);
1144 getpat=0;
1145 free(line);
1146 }
1147 bb_xprint_and_close_file(cmdfile);
1148
1149 break;
1150 }
1151 default:
1152 bb_show_usage();
1153 }
1154 }
1155
1156 /* if we didn't get a pattern from a -e and no command file was specified,
1157 * argv[optind] should be the pattern. no pattern, no worky */
1158 if(getpat) {
1159 if (argv[optind] == NULL)
1160 bb_show_usage();
1161 else
1162 add_cmd_block(argv[optind++]);
1163 }
1164 /* Flush any unfinished commands. */
1165 add_cmd("");
1166
1167 /* By default, we write to stdout */
1168 nonstdout=stdout;
1169
1170 /* argv[(optind)..(argc-1)] should be names of file to process. If no
1171 * files were specified or '-' was specified, take input from stdin.
1172 * Otherwise, we process all the files specified. */
1173 if (argv[optind] == NULL) {
1174 if(in_place) {
1175 fprintf(stderr,"sed: Filename required for -i\n");
1176 exit(1);
1177 }
1178 process_file(stdin);
1179 } else {
1180 int i;
1181 FILE *file;
1182
1183 for (i = optind; i < argc; i++) {
1184 if(!strcmp(argv[i], "-") && !in_place) {
1185 process_file(stdin);
1186 } else {
1187 file = bb_wfopen(argv[i], "r");
1188 if (file) {
1189 if(in_place) {
1190 struct stat statbuf;
1191 outname=bb_xstrndup(argv[i],strlen(argv[i])+6);
1192 strcat(outname,"XXXXXX");
1193 /* Set permissions of output file */
1194 fstat(fileno(file),&statbuf);
1195 mkstemp(outname);
1196 nonstdout=bb_wfopen(outname,"w");
1197 /* Set permissions of output file */
1198 fstat(fileno(file),&statbuf);
1199 fchmod(fileno(nonstdout),statbuf.st_mode);
1200 atexit(cleanup_outname);
1201 }
1202 process_file(file);
1203 fclose(file);
1204 if(in_place) {
1205 fclose(nonstdout);
1206 nonstdout=stdout;
1207 unlink(argv[i]);
1208 rename(outname,argv[i]);
1209 free(outname);
1210 outname=0;
1211 }
1212 } else {
1213 status = EXIT_FAILURE;
1214 }
1215 }
1216 }
1217 }
1218
1219 return status;
1220}
diff --git a/busybox/editors/vi.c b/busybox/editors/vi.c
new file mode 100644
index 000000000..cd6cf0ea1
--- /dev/null
+++ b/busybox/editors/vi.c
@@ -0,0 +1,3983 @@
1/* vi: set sw=8 ts=8: */
2/*
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21static const char vi_Version[] =
22 "$Id: vi.c,v 1.38 2004/08/19 19:15:06 andersen Exp $";
23
24/*
25 * To compile for standalone use:
26 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
27 * or
28 * gcc -Wall -Os -s -DSTANDALONE -DCONFIG_FEATURE_VI_CRASHME -o vi vi.c # include testing features
29 * strip vi
30 */
31
32/*
33 * Things To Do:
34 * EXINIT
35 * $HOME/.exrc and ./.exrc
36 * add magic to search /foo.*bar
37 * add :help command
38 * :map macros
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 * More intelligence in refresh()
43 * ":r !cmd" and "!cmd" to filter text through an external command
44 * A true "undo" facility
45 * An "ex" line oriented mode- maybe using "cmdedit"
46 */
47
48//---- Feature -------------- Bytes to implement
49#ifdef STANDALONE
50#define vi_main main
51#define CONFIG_FEATURE_VI_COLON // 4288
52#define CONFIG_FEATURE_VI_YANKMARK // 1408
53#define CONFIG_FEATURE_VI_SEARCH // 1088
54#define CONFIG_FEATURE_VI_USE_SIGNALS // 1056
55#define CONFIG_FEATURE_VI_DOT_CMD // 576
56#define CONFIG_FEATURE_VI_READONLY // 128
57#define CONFIG_FEATURE_VI_SETOPTS // 576
58#define CONFIG_FEATURE_VI_SET // 224
59#define CONFIG_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
60// To test editor using CRASHME:
61// vi -C filename
62// To stop testing, wait until all to text[] is deleted, or
63// Ctrl-Z and kill -9 %1
64// while in the editor Ctrl-T will toggle the crashme function on and off.
65//#define CONFIG_FEATURE_VI_CRASHME // randomly pick commands to execute
66#endif /* STANDALONE */
67
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#include <termios.h>
72#include <unistd.h>
73#include <sys/ioctl.h>
74#include <sys/time.h>
75#include <sys/types.h>
76#include <sys/stat.h>
77#include <time.h>
78#include <fcntl.h>
79#include <signal.h>
80#include <setjmp.h>
81#include <regex.h>
82#include <ctype.h>
83#include <assert.h>
84#include <errno.h>
85#include <stdarg.h>
86#ifndef STANDALONE
87#include "busybox.h"
88#endif /* STANDALONE */
89
90#ifdef CONFIG_LOCALE_SUPPORT
91#define Isprint(c) isprint((c))
92#else
93#define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
94#endif
95
96#ifndef TRUE
97#define TRUE ((int)1)
98#define FALSE ((int)0)
99#endif /* TRUE */
100#define MAX_SCR_COLS BUFSIZ
101
102// Misc. non-Ascii keys that report an escape sequence
103#define VI_K_UP 128 // cursor key Up
104#define VI_K_DOWN 129 // cursor key Down
105#define VI_K_RIGHT 130 // Cursor Key Right
106#define VI_K_LEFT 131 // cursor key Left
107#define VI_K_HOME 132 // Cursor Key Home
108#define VI_K_END 133 // Cursor Key End
109#define VI_K_INSERT 134 // Cursor Key Insert
110#define VI_K_PAGEUP 135 // Cursor Key Page Up
111#define VI_K_PAGEDOWN 136 // Cursor Key Page Down
112#define VI_K_FUN1 137 // Function Key F1
113#define VI_K_FUN2 138 // Function Key F2
114#define VI_K_FUN3 139 // Function Key F3
115#define VI_K_FUN4 140 // Function Key F4
116#define VI_K_FUN5 141 // Function Key F5
117#define VI_K_FUN6 142 // Function Key F6
118#define VI_K_FUN7 143 // Function Key F7
119#define VI_K_FUN8 144 // Function Key F8
120#define VI_K_FUN9 145 // Function Key F9
121#define VI_K_FUN10 146 // Function Key F10
122#define VI_K_FUN11 147 // Function Key F11
123#define VI_K_FUN12 148 // Function Key F12
124
125/* vt102 typical ESC sequence */
126/* terminal standout start/normal ESC sequence */
127static const char SOs[] = "\033[7m";
128static const char SOn[] = "\033[0m";
129/* terminal bell sequence */
130static const char bell[] = "\007";
131/* Clear-end-of-line and Clear-end-of-screen ESC sequence */
132static const char Ceol[] = "\033[0K";
133static const char Ceos [] = "\033[0J";
134/* Cursor motion arbitrary destination ESC sequence */
135static const char CMrc[] = "\033[%d;%dH";
136/* Cursor motion up and down ESC sequence */
137static const char CMup[] = "\033[A";
138static const char CMdown[] = "\n";
139
140
141static const int YANKONLY = FALSE;
142static const int YANKDEL = TRUE;
143static const int FORWARD = 1; // code depends on "1" for array index
144static const int BACK = -1; // code depends on "-1" for array index
145static const int LIMITED = 0; // how much of text[] in char_search
146static const int FULL = 1; // how much of text[] in char_search
147
148static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
149static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
150static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
151static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
152static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
153
154typedef unsigned char Byte;
155
156static int vi_setops;
157#define VI_AUTOINDENT 1
158#define VI_SHOWMATCH 2
159#define VI_IGNORECASE 4
160#define VI_ERR_METHOD 8
161#define autoindent (vi_setops & VI_AUTOINDENT)
162#define showmatch (vi_setops & VI_SHOWMATCH )
163#define ignorecase (vi_setops & VI_IGNORECASE)
164/* indicate error with beep or flash */
165#define err_method (vi_setops & VI_ERR_METHOD)
166
167
168static int editing; // >0 while we are editing a file
169static int cmd_mode; // 0=command 1=insert
170static int file_modified; // buffer contents changed
171static int fn_start; // index of first cmd line file name
172static int save_argc; // how many file names on cmd line
173static int cmdcnt; // repetition count
174static fd_set rfds; // use select() for small sleeps
175static struct timeval tv; // use select() for small sleeps
176static int rows, columns; // the terminal screen is this size
177static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
178static Byte *status_buffer; // mesages to the user
179static Byte *cfn; // previous, current, and next file name
180static Byte *text, *end, *textend; // pointers to the user data in memory
181static Byte *screen; // pointer to the virtual screen buffer
182static int screensize; // and its size
183static Byte *screenbegin; // index into text[], of top line on the screen
184static Byte *dot; // where all the action takes place
185static int tabstop;
186static struct termios term_orig, term_vi; // remember what the cooked mode was
187static Byte erase_char; // the users erase character
188static Byte last_input_char; // last char read from user
189static Byte last_forward_char; // last char searched for with 'f'
190
191#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
192static int last_row; // where the cursor was last moved to
193#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
194#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
195static jmp_buf restart; // catch_sig()
196#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
197#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
198static int my_pid;
199#endif
200#ifdef CONFIG_FEATURE_VI_DOT_CMD
201static int adding2q; // are we currently adding user input to q
202static Byte *last_modifying_cmd; // last modifying cmd for "."
203static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
204#endif /* CONFIG_FEATURE_VI_DOT_CMD */
205#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
206static Byte *modifying_cmds; // cmds that modify text[]
207#endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
208#ifdef CONFIG_FEATURE_VI_READONLY
209static int vi_readonly, readonly;
210#endif /* CONFIG_FEATURE_VI_READONLY */
211#ifdef CONFIG_FEATURE_VI_YANKMARK
212static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
213static int YDreg, Ureg; // default delete register and orig line for "U"
214static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
215static Byte *context_start, *context_end;
216#endif /* CONFIG_FEATURE_VI_YANKMARK */
217#ifdef CONFIG_FEATURE_VI_SEARCH
218static Byte *last_search_pattern; // last pattern from a '/' or '?' search
219#endif /* CONFIG_FEATURE_VI_SEARCH */
220
221
222static void edit_file(Byte *); // edit one file
223static void do_cmd(Byte); // execute a command
224static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
225static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
226static Byte *end_line(Byte *); // return pointer to cur line E-o-l
227static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
228static Byte *next_line(Byte *); // return pointer to next line B-o-l
229static Byte *end_screen(void); // get pointer to last char on screen
230static int count_lines(Byte *, Byte *); // count line from start to stop
231static Byte *find_line(int); // find begining of line #li
232static Byte *move_to_col(Byte *, int); // move "p" to column l
233static int isblnk(Byte); // is the char a blank or tab
234static void dot_left(void); // move dot left- dont leave line
235static void dot_right(void); // move dot right- dont leave line
236static void dot_begin(void); // move dot to B-o-l
237static void dot_end(void); // move dot to E-o-l
238static void dot_next(void); // move dot to next line B-o-l
239static void dot_prev(void); // move dot to prev line B-o-l
240static void dot_scroll(int, int); // move the screen up or down
241static void dot_skip_over_ws(void); // move dot pat WS
242static void dot_delete(void); // delete the char at 'dot'
243static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
244static Byte *new_screen(int, int); // malloc virtual screen memory
245static Byte *new_text(int); // malloc memory for text[] buffer
246static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
247static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
248static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
249static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
250static Byte *skip_thing(Byte *, int, int, int); // skip some object
251static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
252static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
253static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
254static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
255static void show_help(void); // display some help info
256static void rawmode(void); // set "raw" mode on tty
257static void cookmode(void); // return to "cooked" mode on tty
258static int mysleep(int); // sleep for 'h' 1/100 seconds
259static Byte readit(void); // read (maybe cursor) key from stdin
260static Byte get_one_char(void); // read 1 char from stdin
261static int file_size(const Byte *); // what is the byte size of "fn"
262static int file_insert(Byte *, Byte *, int);
263static int file_write(Byte *, Byte *, Byte *);
264static void place_cursor(int, int, int);
265static void screen_erase(void);
266static void clear_to_eol(void);
267static void clear_to_eos(void);
268static void standout_start(void); // send "start reverse video" sequence
269static void standout_end(void); // send "end reverse video" sequence
270static void flash(int); // flash the terminal screen
271static void show_status_line(void); // put a message on the bottom line
272static void psb(const char *, ...); // Print Status Buf
273static void psbs(const char *, ...); // Print Status Buf in standout mode
274static void ni(Byte *); // display messages
275static void edit_status(void); // show file status on status line
276static void redraw(int); // force a full screen refresh
277static void format_line(Byte*, Byte*, int);
278static void refresh(int); // update the terminal from screen[]
279
280static void Indicate_Error(void); // use flash or beep to indicate error
281#define indicate_error(c) Indicate_Error()
282
283#ifdef CONFIG_FEATURE_VI_SEARCH
284static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
285static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
286#endif /* CONFIG_FEATURE_VI_SEARCH */
287#ifdef CONFIG_FEATURE_VI_COLON
288static void Hit_Return(void);
289static Byte *get_one_address(Byte *, int *); // get colon addr, if present
290static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
291static void colon(Byte *); // execute the "colon" mode cmds
292#endif /* CONFIG_FEATURE_VI_COLON */
293#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
294static void winch_sig(int); // catch window size changes
295static void suspend_sig(int); // catch ctrl-Z
296static void catch_sig(int); // catch ctrl-C and alarm time-outs
297static void core_sig(int); // catch a core dump signal
298#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
299#ifdef CONFIG_FEATURE_VI_DOT_CMD
300static void start_new_cmd_q(Byte); // new queue for command
301static void end_cmd_q(void); // stop saving input chars
302#else /* CONFIG_FEATURE_VI_DOT_CMD */
303#define end_cmd_q()
304#endif /* CONFIG_FEATURE_VI_DOT_CMD */
305#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
306static void window_size_get(int); // find out what size the window is
307#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
308#ifdef CONFIG_FEATURE_VI_SETOPTS
309static void showmatching(Byte *); // show the matching pair () [] {}
310#endif /* CONFIG_FEATURE_VI_SETOPTS */
311#if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
312static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
313#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
314#ifdef CONFIG_FEATURE_VI_YANKMARK
315static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
316static Byte what_reg(void); // what is letter of current YDreg
317static void check_context(Byte); // remember context for '' command
318#endif /* CONFIG_FEATURE_VI_YANKMARK */
319#ifdef CONFIG_FEATURE_VI_CRASHME
320static void crash_dummy();
321static void crash_test();
322static int crashme = 0;
323#endif /* CONFIG_FEATURE_VI_CRASHME */
324
325
326static void write1(const char *out)
327{
328 fputs(out, stdout);
329}
330
331extern int vi_main(int argc, char **argv)
332{
333 int c;
334 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, 200);
335
336#ifdef CONFIG_FEATURE_VI_YANKMARK
337 int i;
338#endif /* CONFIG_FEATURE_VI_YANKMARK */
339#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
340 my_pid = getpid();
341#endif
342#ifdef CONFIG_FEATURE_VI_CRASHME
343 (void) srand((long) my_pid);
344#endif /* CONFIG_FEATURE_VI_CRASHME */
345
346 status_buffer = STATUS_BUFFER;
347
348#ifdef CONFIG_FEATURE_VI_READONLY
349 vi_readonly = readonly = FALSE;
350 if (strncmp(argv[0], "view", 4) == 0) {
351 readonly = TRUE;
352 vi_readonly = TRUE;
353 }
354#endif /* CONFIG_FEATURE_VI_READONLY */
355 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
356#ifdef CONFIG_FEATURE_VI_YANKMARK
357 for (i = 0; i < 28; i++) {
358 reg[i] = 0;
359 } // init the yank regs
360#endif /* CONFIG_FEATURE_VI_YANKMARK */
361#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
362 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
363#endif /* CONFIG_FEATURE_VI_DOT_CMD */
364
365 // 1- process $HOME/.exrc file
366 // 2- process EXINIT variable from environment
367 // 3- process command line args
368 while ((c = getopt(argc, argv, "hCR")) != -1) {
369 switch (c) {
370#ifdef CONFIG_FEATURE_VI_CRASHME
371 case 'C':
372 crashme = 1;
373 break;
374#endif /* CONFIG_FEATURE_VI_CRASHME */
375#ifdef CONFIG_FEATURE_VI_READONLY
376 case 'R': // Read-only flag
377 readonly = TRUE;
378 vi_readonly = TRUE;
379 break;
380#endif /* CONFIG_FEATURE_VI_READONLY */
381 //case 'r': // recover flag- ignore- we don't use tmp file
382 //case 'x': // encryption flag- ignore
383 //case 'c': // execute command first
384 //case 'h': // help -- just use default
385 default:
386 show_help();
387 return 1;
388 }
389 }
390
391 // The argv array can be used by the ":next" and ":rewind" commands
392 // save optind.
393 fn_start = optind; // remember first file name for :next and :rew
394 save_argc = argc;
395
396 //----- This is the main file handling loop --------------
397 if (optind >= argc) {
398 editing = 1; // 0= exit, 1= one file, 2= multiple files
399 edit_file(0);
400 } else {
401 for (; optind < argc; optind++) {
402 editing = 1; // 0=exit, 1=one file, 2+ =many files
403 free(cfn);
404 cfn = (Byte *) bb_xstrdup(argv[optind]);
405 edit_file(cfn);
406 }
407 }
408 //-----------------------------------------------------------
409
410 return (0);
411}
412
413#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
414//----- See what the window size currently is --------------------
415static inline void window_size_get(int fd)
416{
417 get_terminal_width_height(fd, &columns, &rows);
418}
419#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
420
421static void edit_file(Byte * fn)
422{
423 Byte c;
424 int cnt, size, ch;
425
426#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
427 int sig;
428#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
429#ifdef CONFIG_FEATURE_VI_YANKMARK
430 static Byte *cur_line;
431#endif /* CONFIG_FEATURE_VI_YANKMARK */
432
433 rawmode();
434 rows = 24;
435 columns = 80;
436 ch= -1;
437#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
438 window_size_get(0);
439#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
440 new_screen(rows, columns); // get memory for virtual screen
441
442 cnt = file_size(fn); // file size
443 size = 2 * cnt; // 200% of file size
444 new_text(size); // get a text[] buffer
445 screenbegin = dot = end = text;
446 if (fn != 0) {
447 ch= file_insert(fn, text, cnt);
448 }
449 if (ch < 1) {
450 (void) char_insert(text, '\n'); // start empty buf with dummy line
451 }
452 file_modified = FALSE;
453#ifdef CONFIG_FEATURE_VI_YANKMARK
454 YDreg = 26; // default Yank/Delete reg
455 Ureg = 27; // hold orig line for "U" cmd
456 for (cnt = 0; cnt < 28; cnt++) {
457 mark[cnt] = 0;
458 } // init the marks
459 mark[26] = mark[27] = text; // init "previous context"
460#endif /* CONFIG_FEATURE_VI_YANKMARK */
461
462 last_forward_char = last_input_char = '\0';
463 crow = 0;
464 ccol = 0;
465 edit_status();
466
467#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
468 catch_sig(0);
469 core_sig(0);
470 signal(SIGWINCH, winch_sig);
471 signal(SIGTSTP, suspend_sig);
472 sig = setjmp(restart);
473 if (sig != 0) {
474 const char *msg = "";
475
476 if (sig == SIGWINCH)
477 msg = "(window resize)";
478 if (sig == SIGHUP)
479 msg = "(hangup)";
480 if (sig == SIGINT)
481 msg = "(interrupt)";
482 if (sig == SIGTERM)
483 msg = "(terminate)";
484 if (sig == SIGBUS)
485 msg = "(bus error)";
486 if (sig == SIGSEGV)
487 msg = "(I tried to touch invalid memory)";
488 if (sig == SIGALRM)
489 msg = "(alarm)";
490
491 psbs("-- caught signal %d %s--", sig, msg);
492 screenbegin = dot = text;
493 }
494#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
495
496 editing = 1;
497 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
498 cmdcnt = 0;
499 tabstop = 8;
500 offset = 0; // no horizontal offset
501 c = '\0';
502#ifdef CONFIG_FEATURE_VI_DOT_CMD
503 free(last_modifying_cmd);
504 free(ioq_start);
505 ioq = ioq_start = last_modifying_cmd = 0;
506 adding2q = 0;
507#endif /* CONFIG_FEATURE_VI_DOT_CMD */
508 redraw(FALSE); // dont force every col re-draw
509 show_status_line();
510
511 //------This is the main Vi cmd handling loop -----------------------
512 while (editing > 0) {
513#ifdef CONFIG_FEATURE_VI_CRASHME
514 if (crashme > 0) {
515 if ((end - text) > 1) {
516 crash_dummy(); // generate a random command
517 } else {
518 crashme = 0;
519 dot =
520 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
521 refresh(FALSE);
522 }
523 }
524#endif /* CONFIG_FEATURE_VI_CRASHME */
525 last_input_char = c = get_one_char(); // get a cmd from user
526#ifdef CONFIG_FEATURE_VI_YANKMARK
527 // save a copy of the current line- for the 'U" command
528 if (begin_line(dot) != cur_line) {
529 cur_line = begin_line(dot);
530 text_yank(begin_line(dot), end_line(dot), Ureg);
531 }
532#endif /* CONFIG_FEATURE_VI_YANKMARK */
533#ifdef CONFIG_FEATURE_VI_DOT_CMD
534 // These are commands that change text[].
535 // Remember the input for the "." command
536 if (!adding2q && ioq_start == 0
537 && strchr((char *) modifying_cmds, c) != NULL) {
538 start_new_cmd_q(c);
539 }
540#endif /* CONFIG_FEATURE_VI_DOT_CMD */
541 do_cmd(c); // execute the user command
542 //
543 // poll to see if there is input already waiting. if we are
544 // not able to display output fast enough to keep up, skip
545 // the display update until we catch up with input.
546 if (mysleep(0) == 0) {
547 // no input pending- so update output
548 refresh(FALSE);
549 show_status_line();
550 }
551#ifdef CONFIG_FEATURE_VI_CRASHME
552 if (crashme > 0)
553 crash_test(); // test editor variables
554#endif /* CONFIG_FEATURE_VI_CRASHME */
555 }
556 //-------------------------------------------------------------------
557
558 place_cursor(rows, 0, FALSE); // go to bottom of screen
559 clear_to_eol(); // Erase to end of line
560 cookmode();
561}
562
563//----- The Colon commands -------------------------------------
564#ifdef CONFIG_FEATURE_VI_COLON
565static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
566{
567 int st;
568 Byte *q;
569
570#ifdef CONFIG_FEATURE_VI_YANKMARK
571 Byte c;
572#endif /* CONFIG_FEATURE_VI_YANKMARK */
573#ifdef CONFIG_FEATURE_VI_SEARCH
574 Byte *pat, buf[BUFSIZ];
575#endif /* CONFIG_FEATURE_VI_SEARCH */
576
577 *addr = -1; // assume no addr
578 if (*p == '.') { // the current line
579 p++;
580 q = begin_line(dot);
581 *addr = count_lines(text, q);
582#ifdef CONFIG_FEATURE_VI_YANKMARK
583 } else if (*p == '\'') { // is this a mark addr
584 p++;
585 c = tolower(*p);
586 p++;
587 if (c >= 'a' && c <= 'z') {
588 // we have a mark
589 c = c - 'a';
590 q = mark[(int) c];
591 if (q != NULL) { // is mark valid
592 *addr = count_lines(text, q); // count lines
593 }
594 }
595#endif /* CONFIG_FEATURE_VI_YANKMARK */
596#ifdef CONFIG_FEATURE_VI_SEARCH
597 } else if (*p == '/') { // a search pattern
598 q = buf;
599 for (p++; *p; p++) {
600 if (*p == '/')
601 break;
602 *q++ = *p;
603 *q = '\0';
604 }
605 pat = (Byte *) bb_xstrdup((char *) buf); // save copy of pattern
606 if (*p == '/')
607 p++;
608 q = char_search(dot, pat, FORWARD, FULL);
609 if (q != NULL) {
610 *addr = count_lines(text, q);
611 }
612 free(pat);
613#endif /* CONFIG_FEATURE_VI_SEARCH */
614 } else if (*p == '$') { // the last line in file
615 p++;
616 q = begin_line(end - 1);
617 *addr = count_lines(text, q);
618 } else if (isdigit(*p)) { // specific line number
619 sscanf((char *) p, "%d%n", addr, &st);
620 p += st;
621 } else { // I don't reconise this
622 // unrecognised address- assume -1
623 *addr = -1;
624 }
625 return (p);
626}
627
628static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
629{
630 //----- get the address' i.e., 1,3 'a,'b -----
631 // get FIRST addr, if present
632 while (isblnk(*p))
633 p++; // skip over leading spaces
634 if (*p == '%') { // alias for 1,$
635 p++;
636 *b = 1;
637 *e = count_lines(text, end-1);
638 goto ga0;
639 }
640 p = get_one_address(p, b);
641 while (isblnk(*p))
642 p++;
643 if (*p == ',') { // is there a address separator
644 p++;
645 while (isblnk(*p))
646 p++;
647 // get SECOND addr, if present
648 p = get_one_address(p, e);
649 }
650ga0:
651 while (isblnk(*p))
652 p++; // skip over trailing spaces
653 return (p);
654}
655
656#ifdef CONFIG_FEATURE_VI_SETOPTS
657static void setops(const Byte *args, const char *opname, int flg_no,
658 const char *short_opname, int opt)
659{
660 const char *a = (char *) args + flg_no;
661 int l = strlen(opname) - 1; /* opname have + ' ' */
662
663 if (strncasecmp(a, opname, l) == 0 ||
664 strncasecmp(a, short_opname, 2) == 0) {
665 if(flg_no)
666 vi_setops &= ~opt;
667 else
668 vi_setops |= opt;
669 }
670}
671#endif
672
673static void colon(Byte * buf)
674{
675 Byte c, *orig_buf, *buf1, *q, *r;
676 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
677 int i, l, li, ch, st, b, e;
678 int useforce = FALSE, forced = FALSE;
679 struct stat st_buf;
680
681 // :3154 // if (-e line 3154) goto it else stay put
682 // :4,33w! foo // write a portion of buffer to file "foo"
683 // :w // write all of buffer to current file
684 // :q // quit
685 // :q! // quit- dont care about modified file
686 // :'a,'z!sort -u // filter block through sort
687 // :'f // goto mark "f"
688 // :'fl // list literal the mark "f" line
689 // :.r bar // read file "bar" into buffer before dot
690 // :/123/,/abc/d // delete lines from "123" line to "abc" line
691 // :/xyz/ // goto the "xyz" line
692 // :s/find/replace/ // substitute pattern "find" with "replace"
693 // :!<cmd> // run <cmd> then return
694 //
695
696 if (strlen((char *) buf) <= 0)
697 goto vc1;
698 if (*buf == ':')
699 buf++; // move past the ':'
700
701 li = st = ch = i = 0;
702 b = e = -1;
703 q = text; // assume 1,$ for the range
704 r = end - 1;
705 li = count_lines(text, end - 1);
706 fn = cfn; // default to current file
707 memset(cmd, '\0', BUFSIZ); // clear cmd[]
708 memset(args, '\0', BUFSIZ); // clear args[]
709
710 // look for optional address(es) :. :1 :1,9 :'q,'a :%
711 buf = get_address(buf, &b, &e);
712
713 // remember orig command line
714 orig_buf = buf;
715
716 // get the COMMAND into cmd[]
717 buf1 = cmd;
718 while (*buf != '\0') {
719 if (isspace(*buf))
720 break;
721 *buf1++ = *buf++;
722 }
723 // get any ARGuments
724 while (isblnk(*buf))
725 buf++;
726 strcpy((char *) args, (char *) buf);
727 buf1 = last_char_is((char *)cmd, '!');
728 if (buf1) {
729 useforce = TRUE;
730 *buf1 = '\0'; // get rid of !
731 }
732 if (b >= 0) {
733 // if there is only one addr, then the addr
734 // is the line number of the single line the
735 // user wants. So, reset the end
736 // pointer to point at end of the "b" line
737 q = find_line(b); // what line is #b
738 r = end_line(q);
739 li = 1;
740 }
741 if (e >= 0) {
742 // we were given two addrs. change the
743 // end pointer to the addr given by user.
744 r = find_line(e); // what line is #e
745 r = end_line(r);
746 li = e - b + 1;
747 }
748 // ------------ now look for the command ------------
749 i = strlen((char *) cmd);
750 if (i == 0) { // :123CR goto line #123
751 if (b >= 0) {
752 dot = find_line(b); // what line is #b
753 dot_skip_over_ws();
754 }
755 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
756 // :!ls run the <cmd>
757 (void) alarm(0); // wait for input- no alarms
758 place_cursor(rows - 1, 0, FALSE); // go to Status line
759 clear_to_eol(); // clear the line
760 cookmode();
761 system(orig_buf+1); // run the cmd
762 rawmode();
763 Hit_Return(); // let user see results
764 (void) alarm(3); // done waiting for input
765 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
766 if (b < 0) { // no addr given- use defaults
767 b = e = count_lines(text, dot);
768 }
769 psb("%d", b);
770 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
771 if (b < 0) { // no addr given- use defaults
772 q = begin_line(dot); // assume .,. for the range
773 r = end_line(dot);
774 }
775 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
776 dot_skip_over_ws();
777 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
778 int sr;
779 sr= 0;
780 // don't edit, if the current file has been modified
781 if (file_modified && ! useforce) {
782 psbs("No write since last change (:edit! overrides)");
783 goto vc1;
784 }
785 if (strlen(args) > 0) {
786 // the user supplied a file name
787 fn= args;
788 } else if (cfn != 0 && strlen(cfn) > 0) {
789 // no user supplied name- use the current filename
790 fn= cfn;
791 goto vc5;
792 } else {
793 // no user file name, no current name- punt
794 psbs("No current filename");
795 goto vc1;
796 }
797
798 // see if file exists- if not, its just a new file request
799 if ((sr=stat((char*)fn, &st_buf)) < 0) {
800 // This is just a request for a new file creation.
801 // The file_insert below will fail but we get
802 // an empty buffer with a file name. Then the "write"
803 // command can do the create.
804 } else {
805 if ((st_buf.st_mode & (S_IFREG)) == 0) {
806 // This is not a regular file
807 psbs("\"%s\" is not a regular file", fn);
808 goto vc1;
809 }
810 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
811 // dont have any read permissions
812 psbs("\"%s\" is not readable", fn);
813 goto vc1;
814 }
815 }
816
817 // There is a read-able regular file
818 // make this the current file
819 q = (Byte *) bb_xstrdup((char *) fn); // save the cfn
820 free(cfn); // free the old name
821 cfn = q; // remember new cfn
822
823 vc5:
824 // delete all the contents of text[]
825 new_text(2 * file_size(fn));
826 screenbegin = dot = end = text;
827
828 // insert new file
829 ch = file_insert(fn, text, file_size(fn));
830
831 if (ch < 1) {
832 // start empty buf with dummy line
833 (void) char_insert(text, '\n');
834 ch= 1;
835 }
836 file_modified = FALSE;
837#ifdef CONFIG_FEATURE_VI_YANKMARK
838 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
839 free(reg[Ureg]); // free orig line reg- for 'U'
840 reg[Ureg]= 0;
841 }
842 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
843 free(reg[YDreg]); // free default yank/delete register
844 reg[YDreg]= 0;
845 }
846 for (li = 0; li < 28; li++) {
847 mark[li] = 0;
848 } // init the marks
849#endif /* CONFIG_FEATURE_VI_YANKMARK */
850 // how many lines in text[]?
851 li = count_lines(text, end - 1);
852 psb("\"%s\"%s"
853#ifdef CONFIG_FEATURE_VI_READONLY
854 "%s"
855#endif /* CONFIG_FEATURE_VI_READONLY */
856 " %dL, %dC", cfn,
857 (sr < 0 ? " [New file]" : ""),
858#ifdef CONFIG_FEATURE_VI_READONLY
859 ((vi_readonly || readonly) ? " [Read only]" : ""),
860#endif /* CONFIG_FEATURE_VI_READONLY */
861 li, ch);
862 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
863 if (b != -1 || e != -1) {
864 ni((Byte *) "No address allowed on this command");
865 goto vc1;
866 }
867 if (strlen((char *) args) > 0) {
868 // user wants a new filename
869 free(cfn);
870 cfn = (Byte *) bb_xstrdup((char *) args);
871 } else {
872 // user wants file status info
873 edit_status();
874 }
875 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
876 // print out values of all features
877 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
878 clear_to_eol(); // clear the line
879 cookmode();
880 show_help();
881 rawmode();
882 Hit_Return();
883 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
884 if (b < 0) { // no addr given- use defaults
885 q = begin_line(dot); // assume .,. for the range
886 r = end_line(dot);
887 }
888 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
889 clear_to_eol(); // clear the line
890 puts("\r");
891 for (; q <= r; q++) {
892 int c_is_no_print;
893
894 c = *q;
895 c_is_no_print = c > 127 && !Isprint(c);
896 if (c_is_no_print) {
897 c = '.';
898 standout_start();
899 }
900 if (c == '\n') {
901 write1("$\r");
902 } else if (c < ' ' || c == 127) {
903 putchar('^');
904 if(c == 127)
905 c = '?';
906 else
907 c += '@';
908 }
909 putchar(c);
910 if (c_is_no_print)
911 standout_end();
912 }
913#ifdef CONFIG_FEATURE_VI_SET
914 vc2:
915#endif /* CONFIG_FEATURE_VI_SET */
916 Hit_Return();
917 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
918 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
919 if (useforce) {
920 // force end of argv list
921 if (*cmd == 'q') {
922 optind = save_argc;
923 }
924 editing = 0;
925 goto vc1;
926 }
927 // don't exit if the file been modified
928 if (file_modified) {
929 psbs("No write since last change (:%s! overrides)",
930 (*cmd == 'q' ? "quit" : "next"));
931 goto vc1;
932 }
933 // are there other file to edit
934 if (*cmd == 'q' && optind < save_argc - 1) {
935 psbs("%d more file to edit", (save_argc - optind - 1));
936 goto vc1;
937 }
938 if (*cmd == 'n' && optind >= save_argc - 1) {
939 psbs("No more files to edit");
940 goto vc1;
941 }
942 editing = 0;
943 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
944 fn = args;
945 if (strlen((char *) fn) <= 0) {
946 psbs("No filename given");
947 goto vc1;
948 }
949 if (b < 0) { // no addr given- use defaults
950 q = begin_line(dot); // assume "dot"
951 }
952 // read after current line- unless user said ":0r foo"
953 if (b != 0)
954 q = next_line(q);
955#ifdef CONFIG_FEATURE_VI_READONLY
956 l= readonly; // remember current files' status
957#endif
958 ch = file_insert(fn, q, file_size(fn));
959#ifdef CONFIG_FEATURE_VI_READONLY
960 readonly= l;
961#endif
962 if (ch < 0)
963 goto vc1; // nothing was inserted
964 // how many lines in text[]?
965 li = count_lines(q, q + ch - 1);
966 psb("\"%s\""
967#ifdef CONFIG_FEATURE_VI_READONLY
968 "%s"
969#endif /* CONFIG_FEATURE_VI_READONLY */
970 " %dL, %dC", fn,
971#ifdef CONFIG_FEATURE_VI_READONLY
972 ((vi_readonly || readonly) ? " [Read only]" : ""),
973#endif /* CONFIG_FEATURE_VI_READONLY */
974 li, ch);
975 if (ch > 0) {
976 // if the insert is before "dot" then we need to update
977 if (q <= dot)
978 dot += ch;
979 file_modified = TRUE;
980 }
981 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
982 if (file_modified && ! useforce) {
983 psbs("No write since last change (:rewind! overrides)");
984 } else {
985 // reset the filenames to edit
986 optind = fn_start - 1;
987 editing = 0;
988 }
989#ifdef CONFIG_FEATURE_VI_SET
990 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
991 i = 0; // offset into args
992 if (strlen((char *) args) == 0) {
993 // print out values of all options
994 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
995 clear_to_eol(); // clear the line
996 printf("----------------------------------------\r\n");
997#ifdef CONFIG_FEATURE_VI_SETOPTS
998 if (!autoindent)
999 printf("no");
1000 printf("autoindent ");
1001 if (!err_method)
1002 printf("no");
1003 printf("flash ");
1004 if (!ignorecase)
1005 printf("no");
1006 printf("ignorecase ");
1007 if (!showmatch)
1008 printf("no");
1009 printf("showmatch ");
1010 printf("tabstop=%d ", tabstop);
1011#endif /* CONFIG_FEATURE_VI_SETOPTS */
1012 printf("\r\n");
1013 goto vc2;
1014 }
1015 if (strncasecmp((char *) args, "no", 2) == 0)
1016 i = 2; // ":set noautoindent"
1017#ifdef CONFIG_FEATURE_VI_SETOPTS
1018 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1019 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1020 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1021 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1022 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1023 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1024 if (ch > 0 && ch < columns - 1)
1025 tabstop = ch;
1026 }
1027#endif /* CONFIG_FEATURE_VI_SETOPTS */
1028#endif /* CONFIG_FEATURE_VI_SET */
1029#ifdef CONFIG_FEATURE_VI_SEARCH
1030 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1031 Byte *ls, *F, *R;
1032 int gflag;
1033
1034 // F points to the "find" pattern
1035 // R points to the "replace" pattern
1036 // replace the cmd line delimiters "/" with NULLs
1037 gflag = 0; // global replace flag
1038 c = orig_buf[1]; // what is the delimiter
1039 F = orig_buf + 2; // start of "find"
1040 R = (Byte *) strchr((char *) F, c); // middle delimiter
1041 if (!R) goto colon_s_fail;
1042 *R++ = '\0'; // terminate "find"
1043 buf1 = (Byte *) strchr((char *) R, c);
1044 if (!buf1) goto colon_s_fail;
1045 *buf1++ = '\0'; // terminate "replace"
1046 if (*buf1 == 'g') { // :s/foo/bar/g
1047 buf1++;
1048 gflag++; // turn on gflag
1049 }
1050 q = begin_line(q);
1051 if (b < 0) { // maybe :s/foo/bar/
1052 q = begin_line(dot); // start with cur line
1053 b = count_lines(text, q); // cur line number
1054 }
1055 if (e < 0)
1056 e = b; // maybe :.s/foo/bar/
1057 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1058 ls = q; // orig line start
1059 vc4:
1060 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1061 if (buf1 != NULL) {
1062 // we found the "find" pattern- delete it
1063 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1064 // inset the "replace" patern
1065 (void) string_insert(buf1, R); // insert the string
1066 // check for "global" :s/foo/bar/g
1067 if (gflag == 1) {
1068 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1069 q = buf1 + strlen((char *) R);
1070 goto vc4; // don't let q move past cur line
1071 }
1072 }
1073 }
1074 q = next_line(ls);
1075 }
1076#endif /* CONFIG_FEATURE_VI_SEARCH */
1077 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1078 psb("%s", vi_Version);
1079 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1080 (strncasecmp((char *) cmd, "wq", i) == 0) ||
1081 (strncasecmp((char *) cmd, "x", i) == 0)) {
1082 // is there a file name to write to?
1083 if (strlen((char *) args) > 0) {
1084 fn = args;
1085 }
1086#ifdef CONFIG_FEATURE_VI_READONLY
1087 if ((vi_readonly || readonly) && ! useforce) {
1088 psbs("\"%s\" File is read only", fn);
1089 goto vc3;
1090 }
1091#endif /* CONFIG_FEATURE_VI_READONLY */
1092 // how many lines in text[]?
1093 li = count_lines(q, r);
1094 ch = r - q + 1;
1095 // see if file exists- if not, its just a new file request
1096 if (useforce) {
1097 // if "fn" is not write-able, chmod u+w
1098 // sprintf(syscmd, "chmod u+w %s", fn);
1099 // system(syscmd);
1100 forced = TRUE;
1101 }
1102 l = file_write(fn, q, r);
1103 if (useforce && forced) {
1104 // chmod u-w
1105 // sprintf(syscmd, "chmod u-w %s", fn);
1106 // system(syscmd);
1107 forced = FALSE;
1108 }
1109 psb("\"%s\" %dL, %dC", fn, li, l);
1110 if (q == text && r == end - 1 && l == ch)
1111 file_modified = FALSE;
1112 if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) {
1113 editing = 0;
1114 }
1115#ifdef CONFIG_FEATURE_VI_READONLY
1116 vc3:;
1117#endif /* CONFIG_FEATURE_VI_READONLY */
1118#ifdef CONFIG_FEATURE_VI_YANKMARK
1119 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1120 if (b < 0) { // no addr given- use defaults
1121 q = begin_line(dot); // assume .,. for the range
1122 r = end_line(dot);
1123 }
1124 text_yank(q, r, YDreg);
1125 li = count_lines(q, r);
1126 psb("Yank %d lines (%d chars) into [%c]",
1127 li, strlen((char *) reg[YDreg]), what_reg());
1128#endif /* CONFIG_FEATURE_VI_YANKMARK */
1129 } else {
1130 // cmd unknown
1131 ni((Byte *) cmd);
1132 }
1133 vc1:
1134 dot = bound_dot(dot); // make sure "dot" is valid
1135 return;
1136#ifdef CONFIG_FEATURE_VI_SEARCH
1137colon_s_fail:
1138 psb(":s expression missing delimiters");
1139#endif
1140}
1141
1142static void Hit_Return(void)
1143{
1144 char c;
1145
1146 standout_start(); // start reverse video
1147 write1("[Hit return to continue]");
1148 standout_end(); // end reverse video
1149 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1150 ;
1151 redraw(TRUE); // force redraw all
1152}
1153#endif /* CONFIG_FEATURE_VI_COLON */
1154
1155//----- Synchronize the cursor to Dot --------------------------
1156static void sync_cursor(Byte * d, int *row, int *col)
1157{
1158 Byte *beg_cur, *end_cur; // begin and end of "d" line
1159 Byte *beg_scr, *end_scr; // begin and end of screen
1160 Byte *tp;
1161 int cnt, ro, co;
1162
1163 beg_cur = begin_line(d); // first char of cur line
1164 end_cur = end_line(d); // last char of cur line
1165
1166 beg_scr = end_scr = screenbegin; // first char of screen
1167 end_scr = end_screen(); // last char of screen
1168
1169 if (beg_cur < screenbegin) {
1170 // "d" is before top line on screen
1171 // how many lines do we have to move
1172 cnt = count_lines(beg_cur, screenbegin);
1173 sc1:
1174 screenbegin = beg_cur;
1175 if (cnt > (rows - 1) / 2) {
1176 // we moved too many lines. put "dot" in middle of screen
1177 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1178 screenbegin = prev_line(screenbegin);
1179 }
1180 }
1181 } else if (beg_cur > end_scr) {
1182 // "d" is after bottom line on screen
1183 // how many lines do we have to move
1184 cnt = count_lines(end_scr, beg_cur);
1185 if (cnt > (rows - 1) / 2)
1186 goto sc1; // too many lines
1187 for (ro = 0; ro < cnt - 1; ro++) {
1188 // move screen begin the same amount
1189 screenbegin = next_line(screenbegin);
1190 // now, move the end of screen
1191 end_scr = next_line(end_scr);
1192 end_scr = end_line(end_scr);
1193 }
1194 }
1195 // "d" is on screen- find out which row
1196 tp = screenbegin;
1197 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1198 if (tp == beg_cur)
1199 break;
1200 tp = next_line(tp);
1201 }
1202
1203 // find out what col "d" is on
1204 co = 0;
1205 do { // drive "co" to correct column
1206 if (*tp == '\n' || *tp == '\0')
1207 break;
1208 if (*tp == '\t') {
1209 // 7 - (co % 8 )
1210 co += ((tabstop - 1) - (co % tabstop));
1211 } else if (*tp < ' ' || *tp == 127) {
1212 co++; // display as ^X, use 2 columns
1213 }
1214 } while (tp++ < d && ++co);
1215
1216 // "co" is the column where "dot" is.
1217 // The screen has "columns" columns.
1218 // The currently displayed columns are 0+offset -- columns+ofset
1219 // |-------------------------------------------------------------|
1220 // ^ ^ ^
1221 // offset | |------- columns ----------------|
1222 //
1223 // If "co" is already in this range then we do not have to adjust offset
1224 // but, we do have to subtract the "offset" bias from "co".
1225 // If "co" is outside this range then we have to change "offset".
1226 // If the first char of a line is a tab the cursor will try to stay
1227 // in column 7, but we have to set offset to 0.
1228
1229 if (co < 0 + offset) {
1230 offset = co;
1231 }
1232 if (co >= columns + offset) {
1233 offset = co - columns + 1;
1234 }
1235 // if the first char of the line is a tab, and "dot" is sitting on it
1236 // force offset to 0.
1237 if (d == beg_cur && *d == '\t') {
1238 offset = 0;
1239 }
1240 co -= offset;
1241
1242 *row = ro;
1243 *col = co;
1244}
1245
1246//----- Text Movement Routines ---------------------------------
1247static Byte *begin_line(Byte * p) // return pointer to first char cur line
1248{
1249 while (p > text && p[-1] != '\n')
1250 p--; // go to cur line B-o-l
1251 return (p);
1252}
1253
1254static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1255{
1256 while (p < end - 1 && *p != '\n')
1257 p++; // go to cur line E-o-l
1258 return (p);
1259}
1260
1261static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1262{
1263 while (p < end - 1 && *p != '\n')
1264 p++; // go to cur line E-o-l
1265 // Try to stay off of the Newline
1266 if (*p == '\n' && (p - begin_line(p)) > 0)
1267 p--;
1268 return (p);
1269}
1270
1271static Byte *prev_line(Byte * p) // return pointer first char prev line
1272{
1273 p = begin_line(p); // goto begining of cur line
1274 if (p[-1] == '\n' && p > text)
1275 p--; // step to prev line
1276 p = begin_line(p); // goto begining of prev line
1277 return (p);
1278}
1279
1280static Byte *next_line(Byte * p) // return pointer first char next line
1281{
1282 p = end_line(p);
1283 if (*p == '\n' && p < end - 1)
1284 p++; // step to next line
1285 return (p);
1286}
1287
1288//----- Text Information Routines ------------------------------
1289static Byte *end_screen(void)
1290{
1291 Byte *q;
1292 int cnt;
1293
1294 // find new bottom line
1295 q = screenbegin;
1296 for (cnt = 0; cnt < rows - 2; cnt++)
1297 q = next_line(q);
1298 q = end_line(q);
1299 return (q);
1300}
1301
1302static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1303{
1304 Byte *q;
1305 int cnt;
1306
1307 if (stop < start) { // start and stop are backwards- reverse them
1308 q = start;
1309 start = stop;
1310 stop = q;
1311 }
1312 cnt = 0;
1313 stop = end_line(stop); // get to end of this line
1314 for (q = start; q <= stop && q <= end - 1; q++) {
1315 if (*q == '\n')
1316 cnt++;
1317 }
1318 return (cnt);
1319}
1320
1321static Byte *find_line(int li) // find begining of line #li
1322{
1323 Byte *q;
1324
1325 for (q = text; li > 1; li--) {
1326 q = next_line(q);
1327 }
1328 return (q);
1329}
1330
1331//----- Dot Movement Routines ----------------------------------
1332static void dot_left(void)
1333{
1334 if (dot > text && dot[-1] != '\n')
1335 dot--;
1336}
1337
1338static void dot_right(void)
1339{
1340 if (dot < end - 1 && *dot != '\n')
1341 dot++;
1342}
1343
1344static void dot_begin(void)
1345{
1346 dot = begin_line(dot); // return pointer to first char cur line
1347}
1348
1349static void dot_end(void)
1350{
1351 dot = end_line(dot); // return pointer to last char cur line
1352}
1353
1354static Byte *move_to_col(Byte * p, int l)
1355{
1356 int co;
1357
1358 p = begin_line(p);
1359 co = 0;
1360 do {
1361 if (*p == '\n' || *p == '\0')
1362 break;
1363 if (*p == '\t') {
1364 // 7 - (co % 8 )
1365 co += ((tabstop - 1) - (co % tabstop));
1366 } else if (*p < ' ' || *p == 127) {
1367 co++; // display as ^X, use 2 columns
1368 }
1369 } while (++co <= l && p++ < end);
1370 return (p);
1371}
1372
1373static void dot_next(void)
1374{
1375 dot = next_line(dot);
1376}
1377
1378static void dot_prev(void)
1379{
1380 dot = prev_line(dot);
1381}
1382
1383static void dot_scroll(int cnt, int dir)
1384{
1385 Byte *q;
1386
1387 for (; cnt > 0; cnt--) {
1388 if (dir < 0) {
1389 // scroll Backwards
1390 // ctrl-Y scroll up one line
1391 screenbegin = prev_line(screenbegin);
1392 } else {
1393 // scroll Forwards
1394 // ctrl-E scroll down one line
1395 screenbegin = next_line(screenbegin);
1396 }
1397 }
1398 // make sure "dot" stays on the screen so we dont scroll off
1399 if (dot < screenbegin)
1400 dot = screenbegin;
1401 q = end_screen(); // find new bottom line
1402 if (dot > q)
1403 dot = begin_line(q); // is dot is below bottom line?
1404 dot_skip_over_ws();
1405}
1406
1407static void dot_skip_over_ws(void)
1408{
1409 // skip WS
1410 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1411 dot++;
1412}
1413
1414static void dot_delete(void) // delete the char at 'dot'
1415{
1416 (void) text_hole_delete(dot, dot);
1417}
1418
1419static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1420{
1421 if (p >= end && end > text) {
1422 p = end - 1;
1423 indicate_error('1');
1424 }
1425 if (p < text) {
1426 p = text;
1427 indicate_error('2');
1428 }
1429 return (p);
1430}
1431
1432//----- Helper Utility Routines --------------------------------
1433
1434//----------------------------------------------------------------
1435//----- Char Routines --------------------------------------------
1436/* Chars that are part of a word-
1437 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1438 * Chars that are Not part of a word (stoppers)
1439 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1440 * Chars that are WhiteSpace
1441 * TAB NEWLINE VT FF RETURN SPACE
1442 * DO NOT COUNT NEWLINE AS WHITESPACE
1443 */
1444
1445static Byte *new_screen(int ro, int co)
1446{
1447 int li;
1448
1449 free(screen);
1450 screensize = ro * co + 8;
1451 screen = (Byte *) xmalloc(screensize);
1452 // initialize the new screen. assume this will be a empty file.
1453 screen_erase();
1454 // non-existent text[] lines start with a tilde (~).
1455 for (li = 1; li < ro - 1; li++) {
1456 screen[(li * co) + 0] = '~';
1457 }
1458 return (screen);
1459}
1460
1461static Byte *new_text(int size)
1462{
1463 if (size < 10240)
1464 size = 10240; // have a minimum size for new files
1465 free(text);
1466 text = (Byte *) xmalloc(size + 8);
1467 memset(text, '\0', size); // clear new text[]
1468 //text += 4; // leave some room for "oops"
1469 textend = text + size - 1;
1470 //textend -= 4; // leave some root for "oops"
1471 return (text);
1472}
1473
1474#ifdef CONFIG_FEATURE_VI_SEARCH
1475static int mycmp(Byte * s1, Byte * s2, int len)
1476{
1477 int i;
1478
1479 i = strncmp((char *) s1, (char *) s2, len);
1480#ifdef CONFIG_FEATURE_VI_SETOPTS
1481 if (ignorecase) {
1482 i = strncasecmp((char *) s1, (char *) s2, len);
1483 }
1484#endif /* CONFIG_FEATURE_VI_SETOPTS */
1485 return (i);
1486}
1487
1488static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1489{
1490#ifndef REGEX_SEARCH
1491 Byte *start, *stop;
1492 int len;
1493
1494 len = strlen((char *) pat);
1495 if (dir == FORWARD) {
1496 stop = end - 1; // assume range is p - end-1
1497 if (range == LIMITED)
1498 stop = next_line(p); // range is to next line
1499 for (start = p; start < stop; start++) {
1500 if (mycmp(start, pat, len) == 0) {
1501 return (start);
1502 }
1503 }
1504 } else if (dir == BACK) {
1505 stop = text; // assume range is text - p
1506 if (range == LIMITED)
1507 stop = prev_line(p); // range is to prev line
1508 for (start = p - len; start >= stop; start--) {
1509 if (mycmp(start, pat, len) == 0) {
1510 return (start);
1511 }
1512 }
1513 }
1514 // pattern not found
1515 return (NULL);
1516#else /*REGEX_SEARCH */
1517 char *q;
1518 struct re_pattern_buffer preg;
1519 int i;
1520 int size, range;
1521
1522 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1523 preg.translate = 0;
1524 preg.fastmap = 0;
1525 preg.buffer = 0;
1526 preg.allocated = 0;
1527
1528 // assume a LIMITED forward search
1529 q = next_line(p);
1530 q = end_line(q);
1531 q = end - 1;
1532 if (dir == BACK) {
1533 q = prev_line(p);
1534 q = text;
1535 }
1536 // count the number of chars to search over, forward or backward
1537 size = q - p;
1538 if (size < 0)
1539 size = p - q;
1540 // RANGE could be negative if we are searching backwards
1541 range = q - p;
1542
1543 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1544 if (q != 0) {
1545 // The pattern was not compiled
1546 psbs("bad search pattern: \"%s\": %s", pat, q);
1547 i = 0; // return p if pattern not compiled
1548 goto cs1;
1549 }
1550
1551 q = p;
1552 if (range < 0) {
1553 q = p - size;
1554 if (q < text)
1555 q = text;
1556 }
1557 // search for the compiled pattern, preg, in p[]
1558 // range < 0- search backward
1559 // range > 0- search forward
1560 // 0 < start < size
1561 // re_search() < 0 not found or error
1562 // re_search() > 0 index of found pattern
1563 // struct pattern char int int int struct reg
1564 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1565 i = re_search(&preg, q, size, 0, range, 0);
1566 if (i == -1) {
1567 p = 0;
1568 i = 0; // return NULL if pattern not found
1569 }
1570 cs1:
1571 if (dir == FORWARD) {
1572 p = p + i;
1573 } else {
1574 p = p - i;
1575 }
1576 return (p);
1577#endif /*REGEX_SEARCH */
1578}
1579#endif /* CONFIG_FEATURE_VI_SEARCH */
1580
1581static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1582{
1583 if (c == 22) { // Is this an ctrl-V?
1584 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1585 p--; // backup onto ^
1586 refresh(FALSE); // show the ^
1587 c = get_one_char();
1588 *p = c;
1589 p++;
1590 file_modified = TRUE; // has the file been modified
1591 } else if (c == 27) { // Is this an ESC?
1592 cmd_mode = 0;
1593 cmdcnt = 0;
1594 end_cmd_q(); // stop adding to q
1595 strcpy((char *) status_buffer, " "); // clear the status buffer
1596 if ((p[-1] != '\n') && (dot>text)) {
1597 p--;
1598 }
1599 } else if (c == erase_char) { // Is this a BS
1600 // 123456789
1601 if ((p[-1] != '\n') && (dot>text)) {
1602 p--;
1603 p = text_hole_delete(p, p); // shrink buffer 1 char
1604#ifdef CONFIG_FEATURE_VI_DOT_CMD
1605 // also rmove char from last_modifying_cmd
1606 if (last_modifying_cmd != 0 && strlen((char *) last_modifying_cmd) > 0) {
1607 Byte *q;
1608
1609 q = last_modifying_cmd;
1610 q[strlen((char *) q) - 1] = '\0'; // erase BS
1611 q[strlen((char *) q) - 1] = '\0'; // erase prev char
1612 }
1613#endif /* CONFIG_FEATURE_VI_DOT_CMD */
1614 }
1615 } else {
1616 // insert a char into text[]
1617 Byte *sp; // "save p"
1618
1619 if (c == 13)
1620 c = '\n'; // translate \r to \n
1621 sp = p; // remember addr of insert
1622 p = stupid_insert(p, c); // insert the char
1623#ifdef CONFIG_FEATURE_VI_SETOPTS
1624 if (showmatch && strchr(")]}", *sp) != NULL) {
1625 showmatching(sp);
1626 }
1627 if (autoindent && c == '\n') { // auto indent the new line
1628 Byte *q;
1629
1630 q = prev_line(p); // use prev line as templet
1631 for (; isblnk(*q); q++) {
1632 p = stupid_insert(p, *q); // insert the char
1633 }
1634 }
1635#endif /* CONFIG_FEATURE_VI_SETOPTS */
1636 }
1637 return (p);
1638}
1639
1640static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1641{
1642 p = text_hole_make(p, 1);
1643 if (p != 0) {
1644 *p = c;
1645 file_modified = TRUE; // has the file been modified
1646 p++;
1647 }
1648 return (p);
1649}
1650
1651static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1652{
1653 Byte *save_dot, *p, *q;
1654 int cnt;
1655
1656 save_dot = dot;
1657 p = q = dot;
1658
1659 if (strchr("cdy><", c)) {
1660 // these cmds operate on whole lines
1661 p = q = begin_line(p);
1662 for (cnt = 1; cnt < cmdcnt; cnt++) {
1663 q = next_line(q);
1664 }
1665 q = end_line(q);
1666 } else if (strchr("^%$0bBeEft", c)) {
1667 // These cmds operate on char positions
1668 do_cmd(c); // execute movement cmd
1669 q = dot;
1670 } else if (strchr("wW", c)) {
1671 do_cmd(c); // execute movement cmd
1672 // if we are at the next word's first char
1673 // step back one char
1674 // but check the possibilities when it is true
1675 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1676 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1677 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1678 dot--; // move back off of next word
1679 if (dot > text && *dot == '\n')
1680 dot--; // stay off NL
1681 q = dot;
1682 } else if (strchr("H-k{", c)) {
1683 // these operate on multi-lines backwards
1684 q = end_line(dot); // find NL
1685 do_cmd(c); // execute movement cmd
1686 dot_begin();
1687 p = dot;
1688 } else if (strchr("L+j}\r\n", c)) {
1689 // these operate on multi-lines forwards
1690 p = begin_line(dot);
1691 do_cmd(c); // execute movement cmd
1692 dot_end(); // find NL
1693 q = dot;
1694 } else {
1695 c = 27; // error- return an ESC char
1696 //break;
1697 }
1698 *start = p;
1699 *stop = q;
1700 if (q < p) {
1701 *start = q;
1702 *stop = p;
1703 }
1704 dot = save_dot;
1705 return (c);
1706}
1707
1708static int st_test(Byte * p, int type, int dir, Byte * tested)
1709{
1710 Byte c, c0, ci;
1711 int test, inc;
1712
1713 inc = dir;
1714 c = c0 = p[0];
1715 ci = p[inc];
1716 test = 0;
1717
1718 if (type == S_BEFORE_WS) {
1719 c = ci;
1720 test = ((!isspace(c)) || c == '\n');
1721 }
1722 if (type == S_TO_WS) {
1723 c = c0;
1724 test = ((!isspace(c)) || c == '\n');
1725 }
1726 if (type == S_OVER_WS) {
1727 c = c0;
1728 test = ((isspace(c)));
1729 }
1730 if (type == S_END_PUNCT) {
1731 c = ci;
1732 test = ((ispunct(c)));
1733 }
1734 if (type == S_END_ALNUM) {
1735 c = ci;
1736 test = ((isalnum(c)) || c == '_');
1737 }
1738 *tested = c;
1739 return (test);
1740}
1741
1742static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1743{
1744 Byte c;
1745
1746 while (st_test(p, type, dir, &c)) {
1747 // make sure we limit search to correct number of lines
1748 if (c == '\n' && --linecnt < 1)
1749 break;
1750 if (dir >= 0 && p >= end - 1)
1751 break;
1752 if (dir < 0 && p <= text)
1753 break;
1754 p += dir; // move to next char
1755 }
1756 return (p);
1757}
1758
1759// find matching char of pair () [] {}
1760static Byte *find_pair(Byte * p, Byte c)
1761{
1762 Byte match, *q;
1763 int dir, level;
1764
1765 match = ')';
1766 level = 1;
1767 dir = 1; // assume forward
1768 switch (c) {
1769 case '(':
1770 match = ')';
1771 break;
1772 case '[':
1773 match = ']';
1774 break;
1775 case '{':
1776 match = '}';
1777 break;
1778 case ')':
1779 match = '(';
1780 dir = -1;
1781 break;
1782 case ']':
1783 match = '[';
1784 dir = -1;
1785 break;
1786 case '}':
1787 match = '{';
1788 dir = -1;
1789 break;
1790 }
1791 for (q = p + dir; text <= q && q < end; q += dir) {
1792 // look for match, count levels of pairs (( ))
1793 if (*q == c)
1794 level++; // increase pair levels
1795 if (*q == match)
1796 level--; // reduce pair level
1797 if (level == 0)
1798 break; // found matching pair
1799 }
1800 if (level != 0)
1801 q = NULL; // indicate no match
1802 return (q);
1803}
1804
1805#ifdef CONFIG_FEATURE_VI_SETOPTS
1806// show the matching char of a pair, () [] {}
1807static void showmatching(Byte * p)
1808{
1809 Byte *q, *save_dot;
1810
1811 // we found half of a pair
1812 q = find_pair(p, *p); // get loc of matching char
1813 if (q == NULL) {
1814 indicate_error('3'); // no matching char
1815 } else {
1816 // "q" now points to matching pair
1817 save_dot = dot; // remember where we are
1818 dot = q; // go to new loc
1819 refresh(FALSE); // let the user see it
1820 (void) mysleep(40); // give user some time
1821 dot = save_dot; // go back to old loc
1822 refresh(FALSE);
1823 }
1824}
1825#endif /* CONFIG_FEATURE_VI_SETOPTS */
1826
1827// open a hole in text[]
1828static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1829{
1830 Byte *src, *dest;
1831 int cnt;
1832
1833 if (size <= 0)
1834 goto thm0;
1835 src = p;
1836 dest = p + size;
1837 cnt = end - src; // the rest of buffer
1838 if (memmove(dest, src, cnt) != dest) {
1839 psbs("can't create room for new characters");
1840 }
1841 memset(p, ' ', size); // clear new hole
1842 end = end + size; // adjust the new END
1843 file_modified = TRUE; // has the file been modified
1844 thm0:
1845 return (p);
1846}
1847
1848// close a hole in text[]
1849static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1850{
1851 Byte *src, *dest;
1852 int cnt, hole_size;
1853
1854 // move forwards, from beginning
1855 // assume p <= q
1856 src = q + 1;
1857 dest = p;
1858 if (q < p) { // they are backward- swap them
1859 src = p + 1;
1860 dest = q;
1861 }
1862 hole_size = q - p + 1;
1863 cnt = end - src;
1864 if (src < text || src > end)
1865 goto thd0;
1866 if (dest < text || dest >= end)
1867 goto thd0;
1868 if (src >= end)
1869 goto thd_atend; // just delete the end of the buffer
1870 if (memmove(dest, src, cnt) != dest) {
1871 psbs("can't delete the character");
1872 }
1873 thd_atend:
1874 end = end - hole_size; // adjust the new END
1875 if (dest >= end)
1876 dest = end - 1; // make sure dest in below end-1
1877 if (end <= text)
1878 dest = end = text; // keep pointers valid
1879 file_modified = TRUE; // has the file been modified
1880 thd0:
1881 return (dest);
1882}
1883
1884// copy text into register, then delete text.
1885// if dist <= 0, do not include, or go past, a NewLine
1886//
1887static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1888{
1889 Byte *p;
1890
1891 // make sure start <= stop
1892 if (start > stop) {
1893 // they are backwards, reverse them
1894 p = start;
1895 start = stop;
1896 stop = p;
1897 }
1898 if (dist <= 0) {
1899 // we can not cross NL boundaries
1900 p = start;
1901 if (*p == '\n')
1902 return (p);
1903 // dont go past a NewLine
1904 for (; p + 1 <= stop; p++) {
1905 if (p[1] == '\n') {
1906 stop = p; // "stop" just before NewLine
1907 break;
1908 }
1909 }
1910 }
1911 p = start;
1912#ifdef CONFIG_FEATURE_VI_YANKMARK
1913 text_yank(start, stop, YDreg);
1914#endif /* CONFIG_FEATURE_VI_YANKMARK */
1915 if (yf == YANKDEL) {
1916 p = text_hole_delete(start, stop);
1917 } // delete lines
1918 return (p);
1919}
1920
1921static void show_help(void)
1922{
1923 puts("These features are available:"
1924#ifdef CONFIG_FEATURE_VI_SEARCH
1925 "\n\tPattern searches with / and ?"
1926#endif /* CONFIG_FEATURE_VI_SEARCH */
1927#ifdef CONFIG_FEATURE_VI_DOT_CMD
1928 "\n\tLast command repeat with \'.\'"
1929#endif /* CONFIG_FEATURE_VI_DOT_CMD */
1930#ifdef CONFIG_FEATURE_VI_YANKMARK
1931 "\n\tLine marking with 'x"
1932 "\n\tNamed buffers with \"x"
1933#endif /* CONFIG_FEATURE_VI_YANKMARK */
1934#ifdef CONFIG_FEATURE_VI_READONLY
1935 "\n\tReadonly if vi is called as \"view\""
1936 "\n\tReadonly with -R command line arg"
1937#endif /* CONFIG_FEATURE_VI_READONLY */
1938#ifdef CONFIG_FEATURE_VI_SET
1939 "\n\tSome colon mode commands with \':\'"
1940#endif /* CONFIG_FEATURE_VI_SET */
1941#ifdef CONFIG_FEATURE_VI_SETOPTS
1942 "\n\tSettable options with \":set\""
1943#endif /* CONFIG_FEATURE_VI_SETOPTS */
1944#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1945 "\n\tSignal catching- ^C"
1946 "\n\tJob suspend and resume with ^Z"
1947#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1948#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1949 "\n\tAdapt to window re-sizes"
1950#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1951 );
1952}
1953
1954static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1955{
1956 Byte c, b[2];
1957
1958 b[1] = '\0';
1959 strcpy((char *) buf, ""); // init buf
1960 if (strlen((char *) s) <= 0)
1961 s = (Byte *) "(NULL)";
1962 for (; *s > '\0'; s++) {
1963 int c_is_no_print;
1964
1965 c = *s;
1966 c_is_no_print = c > 127 && !Isprint(c);
1967 if (c_is_no_print) {
1968 strcat((char *) buf, SOn);
1969 c = '.';
1970 }
1971 if (c < ' ' || c == 127) {
1972 strcat((char *) buf, "^");
1973 if(c == 127)
1974 c = '?';
1975 else
1976 c += '@';
1977 }
1978 b[0] = c;
1979 strcat((char *) buf, (char *) b);
1980 if (c_is_no_print)
1981 strcat((char *) buf, SOs);
1982 if (*s == '\n') {
1983 strcat((char *) buf, "$");
1984 }
1985 }
1986}
1987
1988#ifdef CONFIG_FEATURE_VI_DOT_CMD
1989static void start_new_cmd_q(Byte c)
1990{
1991 // release old cmd
1992 free(last_modifying_cmd);
1993 // get buffer for new cmd
1994 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1995 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1996 // if there is a current cmd count put it in the buffer first
1997 if (cmdcnt > 0)
1998 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
1999 // save char c onto queue
2000 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2001 adding2q = 1;
2002 return;
2003}
2004
2005static void end_cmd_q(void)
2006{
2007#ifdef CONFIG_FEATURE_VI_YANKMARK
2008 YDreg = 26; // go back to default Yank/Delete reg
2009#endif /* CONFIG_FEATURE_VI_YANKMARK */
2010 adding2q = 0;
2011 return;
2012}
2013#endif /* CONFIG_FEATURE_VI_DOT_CMD */
2014
2015#if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME)
2016static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2017{
2018 int cnt, i;
2019
2020 i = strlen((char *) s);
2021 p = text_hole_make(p, i);
2022 strncpy((char *) p, (char *) s, i);
2023 for (cnt = 0; *s != '\0'; s++) {
2024 if (*s == '\n')
2025 cnt++;
2026 }
2027#ifdef CONFIG_FEATURE_VI_YANKMARK
2028 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2029#endif /* CONFIG_FEATURE_VI_YANKMARK */
2030 return (p);
2031}
2032#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2033
2034#ifdef CONFIG_FEATURE_VI_YANKMARK
2035static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2036{
2037 Byte *t;
2038 int cnt;
2039
2040 if (q < p) { // they are backwards- reverse them
2041 t = q;
2042 q = p;
2043 p = t;
2044 }
2045 cnt = q - p + 1;
2046 t = reg[dest];
2047 free(t); // if already a yank register, free it
2048 t = (Byte *) xmalloc(cnt + 1); // get a new register
2049 memset(t, '\0', cnt + 1); // clear new text[]
2050 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2051 reg[dest] = t;
2052 return (p);
2053}
2054
2055static Byte what_reg(void)
2056{
2057 Byte c;
2058 int i;
2059
2060 i = 0;
2061 c = 'D'; // default to D-reg
2062 if (0 <= YDreg && YDreg <= 25)
2063 c = 'a' + (Byte) YDreg;
2064 if (YDreg == 26)
2065 c = 'D';
2066 if (YDreg == 27)
2067 c = 'U';
2068 return (c);
2069}
2070
2071static void check_context(Byte cmd)
2072{
2073 // A context is defined to be "modifying text"
2074 // Any modifying command establishes a new context.
2075
2076 if (dot < context_start || dot > context_end) {
2077 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2078 // we are trying to modify text[]- make this the current context
2079 mark[27] = mark[26]; // move cur to prev
2080 mark[26] = dot; // move local to cur
2081 context_start = prev_line(prev_line(dot));
2082 context_end = next_line(next_line(dot));
2083 //loiter= start_loiter= now;
2084 }
2085 }
2086 return;
2087}
2088
2089static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2090{
2091 Byte *tmp;
2092
2093 // the current context is in mark[26]
2094 // the previous context is in mark[27]
2095 // only swap context if other context is valid
2096 if (text <= mark[27] && mark[27] <= end - 1) {
2097 tmp = mark[27];
2098 mark[27] = mark[26];
2099 mark[26] = tmp;
2100 p = mark[26]; // where we are going- previous context
2101 context_start = prev_line(prev_line(prev_line(p)));
2102 context_end = next_line(next_line(next_line(p)));
2103 }
2104 return (p);
2105}
2106#endif /* CONFIG_FEATURE_VI_YANKMARK */
2107
2108static int isblnk(Byte c) // is the char a blank or tab
2109{
2110 return (c == ' ' || c == '\t');
2111}
2112
2113//----- Set terminal attributes --------------------------------
2114static void rawmode(void)
2115{
2116 tcgetattr(0, &term_orig);
2117 term_vi = term_orig;
2118 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2119 term_vi.c_iflag &= (~IXON & ~ICRNL);
2120 term_vi.c_oflag &= (~ONLCR);
2121 term_vi.c_cc[VMIN] = 1;
2122 term_vi.c_cc[VTIME] = 0;
2123 erase_char = term_vi.c_cc[VERASE];
2124 tcsetattr(0, TCSANOW, &term_vi);
2125}
2126
2127static void cookmode(void)
2128{
2129 fflush(stdout);
2130 tcsetattr(0, TCSANOW, &term_orig);
2131}
2132
2133//----- Come here when we get a window resize signal ---------
2134#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2135static void winch_sig(int sig)
2136{
2137 signal(SIGWINCH, winch_sig);
2138#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2139 window_size_get(0);
2140#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2141 new_screen(rows, columns); // get memory for virtual screen
2142 redraw(TRUE); // re-draw the screen
2143}
2144
2145//----- Come here when we get a continue signal -------------------
2146static void cont_sig(int sig)
2147{
2148 rawmode(); // terminal to "raw"
2149 *status_buffer = '\0'; // clear the status buffer
2150 redraw(TRUE); // re-draw the screen
2151
2152 signal(SIGTSTP, suspend_sig);
2153 signal(SIGCONT, SIG_DFL);
2154 kill(my_pid, SIGCONT);
2155}
2156
2157//----- Come here when we get a Suspend signal -------------------
2158static void suspend_sig(int sig)
2159{
2160 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2161 clear_to_eol(); // Erase to end of line
2162 cookmode(); // terminal to "cooked"
2163
2164 signal(SIGCONT, cont_sig);
2165 signal(SIGTSTP, SIG_DFL);
2166 kill(my_pid, SIGTSTP);
2167}
2168
2169//----- Come here when we get a signal ---------------------------
2170static void catch_sig(int sig)
2171{
2172 signal(SIGHUP, catch_sig);
2173 signal(SIGINT, catch_sig);
2174 signal(SIGTERM, catch_sig);
2175 signal(SIGALRM, catch_sig);
2176 if(sig)
2177 longjmp(restart, sig);
2178}
2179
2180//----- Come here when we get a core dump signal -----------------
2181static void core_sig(int sig)
2182{
2183 signal(SIGQUIT, core_sig);
2184 signal(SIGILL, core_sig);
2185 signal(SIGTRAP, core_sig);
2186 signal(SIGIOT, core_sig);
2187 signal(SIGABRT, core_sig);
2188 signal(SIGFPE, core_sig);
2189 signal(SIGBUS, core_sig);
2190 signal(SIGSEGV, core_sig);
2191#ifdef SIGSYS
2192 signal(SIGSYS, core_sig);
2193#endif
2194
2195 if(sig) { // signaled
2196 dot = bound_dot(dot); // make sure "dot" is valid
2197
2198 longjmp(restart, sig);
2199 }
2200}
2201#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2202
2203static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2204{
2205 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2206 fflush(stdout);
2207 FD_ZERO(&rfds);
2208 FD_SET(0, &rfds);
2209 tv.tv_sec = 0;
2210 tv.tv_usec = hund * 10000;
2211 select(1, &rfds, NULL, NULL, &tv);
2212 return (FD_ISSET(0, &rfds));
2213}
2214
2215static Byte readbuffer[BUFSIZ];
2216static int readed_for_parse;
2217
2218//----- IO Routines --------------------------------------------
2219static Byte readit(void) // read (maybe cursor) key from stdin
2220{
2221 Byte c;
2222 int n;
2223 struct esc_cmds {
2224 const char *seq;
2225 Byte val;
2226 };
2227
2228 static const struct esc_cmds esccmds[] = {
2229 {"OA", (Byte) VI_K_UP}, // cursor key Up
2230 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2231 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2232 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2233 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2234 {"OF", (Byte) VI_K_END}, // Cursor Key End
2235 {"[A", (Byte) VI_K_UP}, // cursor key Up
2236 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2237 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2238 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2239 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2240 {"[F", (Byte) VI_K_END}, // Cursor Key End
2241 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2242 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2243 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2244 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2245 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2246 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2247 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2248 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2249 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2250 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2251 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2252 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2253 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2254 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2255 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2256 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2257 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2258 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2259 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2260 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2261 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2262 };
2263
2264#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2265
2266 (void) alarm(0); // turn alarm OFF while we wait for input
2267 fflush(stdout);
2268 n = readed_for_parse;
2269 // get input from User- are there already input chars in Q?
2270 if (n <= 0) {
2271 ri0:
2272 // the Q is empty, wait for a typed char
2273 n = read(0, readbuffer, BUFSIZ - 1);
2274 if (n < 0) {
2275 if (errno == EINTR)
2276 goto ri0; // interrupted sys call
2277 if (errno == EBADF)
2278 editing = 0;
2279 if (errno == EFAULT)
2280 editing = 0;
2281 if (errno == EINVAL)
2282 editing = 0;
2283 if (errno == EIO)
2284 editing = 0;
2285 errno = 0;
2286 }
2287 if(n <= 0)
2288 return 0; // error
2289 if (readbuffer[0] == 27) {
2290 // This is an ESC char. Is this Esc sequence?
2291 // Could be bare Esc key. See if there are any
2292 // more chars to read after the ESC. This would
2293 // be a Function or Cursor Key sequence.
2294 FD_ZERO(&rfds);
2295 FD_SET(0, &rfds);
2296 tv.tv_sec = 0;
2297 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2298
2299 // keep reading while there are input chars and room in buffer
2300 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2301 // read the rest of the ESC string
2302 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2303 if (r > 0) {
2304 n += r;
2305 }
2306 }
2307 }
2308 readed_for_parse = n;
2309 }
2310 c = readbuffer[0];
2311 if(c == 27 && n > 1) {
2312 // Maybe cursor or function key?
2313 const struct esc_cmds *eindex;
2314
2315 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2316 int cnt = strlen(eindex->seq);
2317
2318 if(n <= cnt)
2319 continue;
2320 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2321 continue;
2322 // is a Cursor key- put derived value back into Q
2323 c = eindex->val;
2324 // for squeeze out the ESC sequence
2325 n = cnt + 1;
2326 break;
2327 }
2328 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2329 /* defined ESC sequence not found, set only one ESC */
2330 n = 1;
2331 }
2332 } else {
2333 n = 1;
2334 }
2335 // remove key sequence from Q
2336 readed_for_parse -= n;
2337 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2338 (void) alarm(3); // we are done waiting for input, turn alarm ON
2339 return (c);
2340}
2341
2342//----- IO Routines --------------------------------------------
2343static Byte get_one_char()
2344{
2345 static Byte c;
2346
2347#ifdef CONFIG_FEATURE_VI_DOT_CMD
2348 // ! adding2q && ioq == 0 read()
2349 // ! adding2q && ioq != 0 *ioq
2350 // adding2q *last_modifying_cmd= read()
2351 if (!adding2q) {
2352 // we are not adding to the q.
2353 // but, we may be reading from a q
2354 if (ioq == 0) {
2355 // there is no current q, read from STDIN
2356 c = readit(); // get the users input
2357 } else {
2358 // there is a queue to get chars from first
2359 c = *ioq++;
2360 if (c == '\0') {
2361 // the end of the q, read from STDIN
2362 free(ioq_start);
2363 ioq_start = ioq = 0;
2364 c = readit(); // get the users input
2365 }
2366 }
2367 } else {
2368 // adding STDIN chars to q
2369 c = readit(); // get the users input
2370 if (last_modifying_cmd != 0) {
2371 int len = strlen((char *) last_modifying_cmd);
2372 if (len + 1 >= BUFSIZ) {
2373 psbs("last_modifying_cmd overrun");
2374 } else {
2375 // add new char to q
2376 last_modifying_cmd[len] = c;
2377 }
2378 }
2379 }
2380#else /* CONFIG_FEATURE_VI_DOT_CMD */
2381 c = readit(); // get the users input
2382#endif /* CONFIG_FEATURE_VI_DOT_CMD */
2383 return (c); // return the char, where ever it came from
2384}
2385
2386static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2387{
2388 Byte buf[BUFSIZ];
2389 Byte c;
2390 int i;
2391 static Byte *obufp = NULL;
2392
2393 strcpy((char *) buf, (char *) prompt);
2394 *status_buffer = '\0'; // clear the status buffer
2395 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2396 clear_to_eol(); // clear the line
2397 write1(prompt); // write out the :, /, or ? prompt
2398
2399 for (i = strlen((char *) buf); i < BUFSIZ;) {
2400 c = get_one_char(); // read user input
2401 if (c == '\n' || c == '\r' || c == 27)
2402 break; // is this end of input
2403 if (c == erase_char) { // user wants to erase prev char
2404 i--; // backup to prev char
2405 buf[i] = '\0'; // erase the char
2406 buf[i + 1] = '\0'; // null terminate buffer
2407 write1("\b \b"); // erase char on screen
2408 if (i <= 0) { // user backs up before b-o-l, exit
2409 break;
2410 }
2411 } else {
2412 buf[i] = c; // save char in buffer
2413 buf[i + 1] = '\0'; // make sure buffer is null terminated
2414 putchar(c); // echo the char back to user
2415 i++;
2416 }
2417 }
2418 refresh(FALSE);
2419 free(obufp);
2420 obufp = (Byte *) bb_xstrdup((char *) buf);
2421 return (obufp);
2422}
2423
2424static int file_size(const Byte * fn) // what is the byte size of "fn"
2425{
2426 struct stat st_buf;
2427 int cnt, sr;
2428
2429 if (fn == 0 || strlen(fn) <= 0)
2430 return (-1);
2431 cnt = -1;
2432 sr = stat((char *) fn, &st_buf); // see if file exists
2433 if (sr >= 0) {
2434 cnt = (int) st_buf.st_size;
2435 }
2436 return (cnt);
2437}
2438
2439static int file_insert(Byte * fn, Byte * p, int size)
2440{
2441 int fd, cnt;
2442
2443 cnt = -1;
2444#ifdef CONFIG_FEATURE_VI_READONLY
2445 readonly = FALSE;
2446#endif /* CONFIG_FEATURE_VI_READONLY */
2447 if (fn == 0 || strlen((char*) fn) <= 0) {
2448 psbs("No filename given");
2449 goto fi0;
2450 }
2451 if (size == 0) {
2452 // OK- this is just a no-op
2453 cnt = 0;
2454 goto fi0;
2455 }
2456 if (size < 0) {
2457 psbs("Trying to insert a negative number (%d) of characters", size);
2458 goto fi0;
2459 }
2460 if (p < text || p > end) {
2461 psbs("Trying to insert file outside of memory");
2462 goto fi0;
2463 }
2464
2465 // see if we can open the file
2466#ifdef CONFIG_FEATURE_VI_READONLY
2467 if (vi_readonly) goto fi1; // do not try write-mode
2468#endif
2469 fd = open((char *) fn, O_RDWR); // assume read & write
2470 if (fd < 0) {
2471 // could not open for writing- maybe file is read only
2472#ifdef CONFIG_FEATURE_VI_READONLY
2473 fi1:
2474#endif
2475 fd = open((char *) fn, O_RDONLY); // try read-only
2476 if (fd < 0) {
2477 psbs("\"%s\" %s", fn, "could not open file");
2478 goto fi0;
2479 }
2480#ifdef CONFIG_FEATURE_VI_READONLY
2481 // got the file- read-only
2482 readonly = TRUE;
2483#endif /* CONFIG_FEATURE_VI_READONLY */
2484 }
2485 p = text_hole_make(p, size);
2486 cnt = read(fd, p, size);
2487 close(fd);
2488 if (cnt < 0) {
2489 cnt = -1;
2490 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2491 psbs("could not read file \"%s\"", fn);
2492 } else if (cnt < size) {
2493 // There was a partial read, shrink unused space text[]
2494 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2495 psbs("could not read all of file \"%s\"", fn);
2496 }
2497 if (cnt >= size)
2498 file_modified = TRUE;
2499 fi0:
2500 return (cnt);
2501}
2502
2503static int file_write(Byte * fn, Byte * first, Byte * last)
2504{
2505 int fd, cnt, charcnt;
2506
2507 if (fn == 0) {
2508 psbs("No current filename");
2509 return (-1);
2510 }
2511 charcnt = 0;
2512 // FIXIT- use the correct umask()
2513 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2514 if (fd < 0)
2515 return (-1);
2516 cnt = last - first + 1;
2517 charcnt = write(fd, first, cnt);
2518 if (charcnt == cnt) {
2519 // good write
2520 //file_modified= FALSE; // the file has not been modified
2521 } else {
2522 charcnt = 0;
2523 }
2524 close(fd);
2525 return (charcnt);
2526}
2527
2528//----- Terminal Drawing ---------------------------------------
2529// The terminal is made up of 'rows' line of 'columns' columns.
2530// classically this would be 24 x 80.
2531// screen coordinates
2532// 0,0 ... 0,79
2533// 1,0 ... 1,79
2534// . ... .
2535// . ... .
2536// 22,0 ... 22,79
2537// 23,0 ... 23,79 status line
2538//
2539
2540//----- Move the cursor to row x col (count from 0, not 1) -------
2541static void place_cursor(int row, int col, int opti)
2542{
2543 char cm1[BUFSIZ];
2544 char *cm;
2545#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2546 char cm2[BUFSIZ];
2547 Byte *screenp;
2548 // char cm3[BUFSIZ];
2549 int Rrow= last_row;
2550#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2551
2552 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2553
2554 if (row < 0) row = 0;
2555 if (row >= rows) row = rows - 1;
2556 if (col < 0) col = 0;
2557 if (col >= columns) col = columns - 1;
2558
2559 //----- 1. Try the standard terminal ESC sequence
2560 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2561 cm= cm1;
2562 if (! opti) goto pc0;
2563
2564#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2565 //----- find the minimum # of chars to move cursor -------------
2566 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2567 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2568
2569 // move to the correct row
2570 while (row < Rrow) {
2571 // the cursor has to move up
2572 strcat(cm2, CMup);
2573 Rrow--;
2574 }
2575 while (row > Rrow) {
2576 // the cursor has to move down
2577 strcat(cm2, CMdown);
2578 Rrow++;
2579 }
2580
2581 // now move to the correct column
2582 strcat(cm2, "\r"); // start at col 0
2583 // just send out orignal source char to get to correct place
2584 screenp = &screen[row * columns]; // start of screen line
2585 strncat(cm2, screenp, col);
2586
2587 //----- 3. Try some other way of moving cursor
2588 //---------------------------------------------
2589
2590 // pick the shortest cursor motion to send out
2591 cm= cm1;
2592 if (strlen(cm2) < strlen(cm)) {
2593 cm= cm2;
2594 } /* else if (strlen(cm3) < strlen(cm)) {
2595 cm= cm3;
2596 } */
2597#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2598 pc0:
2599 write1(cm); // move the cursor
2600}
2601
2602//----- Erase from cursor to end of line -----------------------
2603static void clear_to_eol()
2604{
2605 write1(Ceol); // Erase from cursor to end of line
2606}
2607
2608//----- Erase from cursor to end of screen -----------------------
2609static void clear_to_eos()
2610{
2611 write1(Ceos); // Erase from cursor to end of screen
2612}
2613
2614//----- Start standout mode ------------------------------------
2615static void standout_start() // send "start reverse video" sequence
2616{
2617 write1(SOs); // Start reverse video mode
2618}
2619
2620//----- End standout mode --------------------------------------
2621static void standout_end() // send "end reverse video" sequence
2622{
2623 write1(SOn); // End reverse video mode
2624}
2625
2626//----- Flash the screen --------------------------------------
2627static void flash(int h)
2628{
2629 standout_start(); // send "start reverse video" sequence
2630 redraw(TRUE);
2631 (void) mysleep(h);
2632 standout_end(); // send "end reverse video" sequence
2633 redraw(TRUE);
2634}
2635
2636static void Indicate_Error(void)
2637{
2638#ifdef CONFIG_FEATURE_VI_CRASHME
2639 if (crashme > 0)
2640 return; // generate a random command
2641#endif /* CONFIG_FEATURE_VI_CRASHME */
2642 if (!err_method) {
2643 write1(bell); // send out a bell character
2644 } else {
2645 flash(10);
2646 }
2647}
2648
2649//----- Screen[] Routines --------------------------------------
2650//----- Erase the Screen[] memory ------------------------------
2651static void screen_erase()
2652{
2653 memset(screen, ' ', screensize); // clear new screen
2654}
2655
2656//----- Draw the status line at bottom of the screen -------------
2657static void show_status_line(void)
2658{
2659 static int last_cksum;
2660 int l, cnt, cksum;
2661
2662 edit_status();
2663 cnt = strlen((char *) status_buffer);
2664 for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); }
2665 // don't write the status line unless it changes
2666 if (cnt > 0 && last_cksum != cksum) {
2667 last_cksum= cksum; // remember if we have seen this line
2668 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2669 write1(status_buffer);
2670 clear_to_eol();
2671 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2672 }
2673 fflush(stdout);
2674}
2675
2676//----- format the status buffer, the bottom line of screen ------
2677// print status buffer, with STANDOUT mode
2678static void psbs(const char *format, ...)
2679{
2680 va_list args;
2681
2682 va_start(args, format);
2683 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2684 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
2685 args);
2686 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2687 va_end(args);
2688
2689 return;
2690}
2691
2692// print status buffer
2693static void psb(const char *format, ...)
2694{
2695 va_list args;
2696
2697 va_start(args, format);
2698 vsprintf((char *) status_buffer, format, args);
2699 va_end(args);
2700 return;
2701}
2702
2703static void ni(Byte * s) // display messages
2704{
2705 Byte buf[BUFSIZ];
2706
2707 print_literal(buf, s);
2708 psbs("\'%s\' is not implemented", buf);
2709}
2710
2711static void edit_status(void) // show file status on status line
2712{
2713 int cur, tot, percent;
2714
2715 cur = count_lines(text, dot);
2716 tot = count_lines(text, end - 1);
2717 // current line percent
2718 // ------------- ~~ ----------
2719 // total lines 100
2720 if (tot > 0) {
2721 percent = (100 * cur) / tot;
2722 } else {
2723 cur = tot = 0;
2724 percent = 100;
2725 }
2726 psb("\"%s\""
2727#ifdef CONFIG_FEATURE_VI_READONLY
2728 "%s"
2729#endif /* CONFIG_FEATURE_VI_READONLY */
2730 "%s line %d of %d --%d%%--",
2731 (cfn != 0 ? (char *) cfn : "No file"),
2732#ifdef CONFIG_FEATURE_VI_READONLY
2733 ((vi_readonly || readonly) ? " [Read only]" : ""),
2734#endif /* CONFIG_FEATURE_VI_READONLY */
2735 (file_modified ? " [modified]" : ""),
2736 cur, tot, percent);
2737}
2738
2739//----- Force refresh of all Lines -----------------------------
2740static void redraw(int full_screen)
2741{
2742 place_cursor(0, 0, FALSE); // put cursor in correct place
2743 clear_to_eos(); // tel terminal to erase display
2744 screen_erase(); // erase the internal screen buffer
2745 refresh(full_screen); // this will redraw the entire display
2746}
2747
2748//----- Format a text[] line into a buffer ---------------------
2749static void format_line(Byte *dest, Byte *src, int li)
2750{
2751 int co;
2752 Byte c;
2753
2754 for (co= 0; co < MAX_SCR_COLS; co++) {
2755 c= ' '; // assume blank
2756 if (li > 0 && co == 0) {
2757 c = '~'; // not first line, assume Tilde
2758 }
2759 // are there chars in text[] and have we gone past the end
2760 if (text < end && src < end) {
2761 c = *src++;
2762 }
2763 if (c == '\n')
2764 break;
2765 if (c > 127 && !Isprint(c)) {
2766 c = '.';
2767 }
2768 if (c < ' ' || c == 127) {
2769 if (c == '\t') {
2770 c = ' ';
2771 // co % 8 != 7
2772 for (; (co % tabstop) != (tabstop - 1); co++) {
2773 dest[co] = c;
2774 }
2775 } else {
2776 dest[co++] = '^';
2777 if(c == 127)
2778 c = '?';
2779 else
2780 c += '@'; // make it visible
2781 }
2782 }
2783 // the co++ is done here so that the column will
2784 // not be overwritten when we blank-out the rest of line
2785 dest[co] = c;
2786 if (src >= end)
2787 break;
2788 }
2789}
2790
2791//----- Refresh the changed screen lines -----------------------
2792// Copy the source line from text[] into the buffer and note
2793// if the current screenline is different from the new buffer.
2794// If they differ then that line needs redrawing on the terminal.
2795//
2796static void refresh(int full_screen)
2797{
2798 static int old_offset;
2799 int li, changed;
2800 Byte buf[MAX_SCR_COLS];
2801 Byte *tp, *sp; // pointer into text[] and screen[]
2802#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2803 int last_li= -2; // last line that changed- for optimizing cursor movement
2804#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2805
2806#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2807 window_size_get(0);
2808#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
2809 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2810 tp = screenbegin; // index into text[] of top line
2811
2812 // compare text[] to screen[] and mark screen[] lines that need updating
2813 for (li = 0; li < rows - 1; li++) {
2814 int cs, ce; // column start & end
2815 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2816 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2817 // format current text line into buf
2818 format_line(buf, tp, li);
2819
2820 // skip to the end of the current text[] line
2821 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2822
2823 // see if there are any changes between vitual screen and buf
2824 changed = FALSE; // assume no change
2825 cs= 0;
2826 ce= columns-1;
2827 sp = &screen[li * columns]; // start of screen line
2828 if (full_screen) {
2829 // force re-draw of every single column from 0 - columns-1
2830 goto re0;
2831 }
2832 // compare newly formatted buffer with virtual screen
2833 // look forward for first difference between buf and screen
2834 for ( ; cs <= ce; cs++) {
2835 if (buf[cs + offset] != sp[cs]) {
2836 changed = TRUE; // mark for redraw
2837 break;
2838 }
2839 }
2840
2841 // look backward for last difference between buf and screen
2842 for ( ; ce >= cs; ce--) {
2843 if (buf[ce + offset] != sp[ce]) {
2844 changed = TRUE; // mark for redraw
2845 break;
2846 }
2847 }
2848 // now, cs is index of first diff, and ce is index of last diff
2849
2850 // if horz offset has changed, force a redraw
2851 if (offset != old_offset) {
2852 re0:
2853 changed = TRUE;
2854 }
2855
2856 // make a sanity check of columns indexes
2857 if (cs < 0) cs= 0;
2858 if (ce > columns-1) ce= columns-1;
2859 if (cs > ce) { cs= 0; ce= columns-1; }
2860 // is there a change between vitual screen and buf
2861 if (changed) {
2862 // copy changed part of buffer to virtual screen
2863 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2864
2865 // move cursor to column of first change
2866 if (offset != old_offset) {
2867 // opti_cur_move is still too stupid
2868 // to handle offsets correctly
2869 place_cursor(li, cs, FALSE);
2870 } else {
2871#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2872 // if this just the next line
2873 // try to optimize cursor movement
2874 // otherwise, use standard ESC sequence
2875 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2876 last_li= li;
2877#else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2878 place_cursor(li, cs, FALSE); // use standard ESC sequence
2879#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2880 }
2881
2882 // write line out to terminal
2883 {
2884 int nic = ce-cs+1;
2885 char *out = sp+cs;
2886
2887 while(nic-- > 0) {
2888 putchar(*out);
2889 out++;
2890 }
2891 }
2892#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2893 last_row = li;
2894#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2895 }
2896 }
2897
2898#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2899 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2900 last_row = crow;
2901#else
2902 place_cursor(crow, ccol, FALSE);
2903#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2904
2905 if (offset != old_offset)
2906 old_offset = offset;
2907}
2908
2909//---------------------------------------------------------------------
2910//----- the Ascii Chart -----------------------------------------------
2911//
2912// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2913// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2914// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2915// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2916// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2917// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2918// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2919// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2920// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2921// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2922// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2923// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2924// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2925// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2926// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2927// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2928//---------------------------------------------------------------------
2929
2930//----- Execute a Vi Command -----------------------------------
2931static void do_cmd(Byte c)
2932{
2933 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2934 int cnt, i, j, dir, yf;
2935
2936 c1 = c; // quiet the compiler
2937 cnt = yf = dir = 0; // quiet the compiler
2938 p = q = save_dot = msg = buf; // quiet the compiler
2939 memset(buf, '\0', 9); // clear buf
2940
2941 /* if this is a cursor key, skip these checks */
2942 switch (c) {
2943 case VI_K_UP:
2944 case VI_K_DOWN:
2945 case VI_K_LEFT:
2946 case VI_K_RIGHT:
2947 case VI_K_HOME:
2948 case VI_K_END:
2949 case VI_K_PAGEUP:
2950 case VI_K_PAGEDOWN:
2951 goto key_cmd_mode;
2952 }
2953
2954 if (cmd_mode == 2) {
2955 // flip-flop Insert/Replace mode
2956 if (c == VI_K_INSERT) goto dc_i;
2957 // we are 'R'eplacing the current *dot with new char
2958 if (*dot == '\n') {
2959 // don't Replace past E-o-l
2960 cmd_mode = 1; // convert to insert
2961 } else {
2962 if (1 <= c || Isprint(c)) {
2963 if (c != 27)
2964 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2965 dot = char_insert(dot, c); // insert new char
2966 }
2967 goto dc1;
2968 }
2969 }
2970 if (cmd_mode == 1) {
2971 // hitting "Insert" twice means "R" replace mode
2972 if (c == VI_K_INSERT) goto dc5;
2973 // insert the char c at "dot"
2974 if (1 <= c || Isprint(c)) {
2975 dot = char_insert(dot, c);
2976 }
2977 goto dc1;
2978 }
2979
2980key_cmd_mode:
2981 switch (c) {
2982 //case 0x01: // soh
2983 //case 0x09: // ht
2984 //case 0x0b: // vt
2985 //case 0x0e: // so
2986 //case 0x0f: // si
2987 //case 0x10: // dle
2988 //case 0x11: // dc1
2989 //case 0x13: // dc3
2990#ifdef CONFIG_FEATURE_VI_CRASHME
2991 case 0x14: // dc4 ctrl-T
2992 crashme = (crashme == 0) ? 1 : 0;
2993 break;
2994#endif /* CONFIG_FEATURE_VI_CRASHME */
2995 //case 0x16: // syn
2996 //case 0x17: // etb
2997 //case 0x18: // can
2998 //case 0x1c: // fs
2999 //case 0x1d: // gs
3000 //case 0x1e: // rs
3001 //case 0x1f: // us
3002 //case '!': // !-
3003 //case '#': // #-
3004 //case '&': // &-
3005 //case '(': // (-
3006 //case ')': // )-
3007 //case '*': // *-
3008 //case ',': // ,-
3009 //case '=': // =-
3010 //case '@': // @-
3011 //case 'F': // F-
3012 //case 'K': // K-
3013 //case 'Q': // Q-
3014 //case 'S': // S-
3015 //case 'T': // T-
3016 //case 'V': // V-
3017 //case '[': // [-
3018 //case '\\': // \-
3019 //case ']': // ]-
3020 //case '_': // _-
3021 //case '`': // `-
3022 //case 'g': // g-
3023 //case 'u': // u- FIXME- there is no undo
3024 //case 'v': // v-
3025 default: // unrecognised command
3026 buf[0] = c;
3027 buf[1] = '\0';
3028 if (c < ' ') {
3029 buf[0] = '^';
3030 buf[1] = c + '@';
3031 buf[2] = '\0';
3032 }
3033 ni((Byte *) buf);
3034 end_cmd_q(); // stop adding to q
3035 case 0x00: // nul- ignore
3036 break;
3037 case 2: // ctrl-B scroll up full screen
3038 case VI_K_PAGEUP: // Cursor Key Page Up
3039 dot_scroll(rows - 2, -1);
3040 break;
3041#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
3042 case 0x03: // ctrl-C interrupt
3043 longjmp(restart, 1);
3044 break;
3045 case 26: // ctrl-Z suspend
3046 suspend_sig(SIGTSTP);
3047 break;
3048#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
3049 case 4: // ctrl-D scroll down half screen
3050 dot_scroll((rows - 2) / 2, 1);
3051 break;
3052 case 5: // ctrl-E scroll down one line
3053 dot_scroll(1, 1);
3054 break;
3055 case 6: // ctrl-F scroll down full screen
3056 case VI_K_PAGEDOWN: // Cursor Key Page Down
3057 dot_scroll(rows - 2, 1);
3058 break;
3059 case 7: // ctrl-G show current status
3060 edit_status();
3061 break;
3062 case 'h': // h- move left
3063 case VI_K_LEFT: // cursor key Left
3064 case 8: // ctrl-H- move left (This may be ERASE char)
3065 case 127: // DEL- move left (This may be ERASE char)
3066 if (cmdcnt-- > 1) {
3067 do_cmd(c);
3068 } // repeat cnt
3069 dot_left();
3070 break;
3071 case 10: // Newline ^J
3072 case 'j': // j- goto next line, same col
3073 case VI_K_DOWN: // cursor key Down
3074 if (cmdcnt-- > 1) {
3075 do_cmd(c);
3076 } // repeat cnt
3077 dot_next(); // go to next B-o-l
3078 dot = move_to_col(dot, ccol + offset); // try stay in same col
3079 break;
3080 case 12: // ctrl-L force redraw whole screen
3081 case 18: // ctrl-R force redraw
3082 place_cursor(0, 0, FALSE); // put cursor in correct place
3083 clear_to_eos(); // tel terminal to erase display
3084 (void) mysleep(10);
3085 screen_erase(); // erase the internal screen buffer
3086 refresh(TRUE); // this will redraw the entire display
3087 break;
3088 case 13: // Carriage Return ^M
3089 case '+': // +- goto next line
3090 if (cmdcnt-- > 1) {
3091 do_cmd(c);
3092 } // repeat cnt
3093 dot_next();
3094 dot_skip_over_ws();
3095 break;
3096 case 21: // ctrl-U scroll up half screen
3097 dot_scroll((rows - 2) / 2, -1);
3098 break;
3099 case 25: // ctrl-Y scroll up one line
3100 dot_scroll(1, -1);
3101 break;
3102 case 27: // esc
3103 if (cmd_mode == 0)
3104 indicate_error(c);
3105 cmd_mode = 0; // stop insrting
3106 end_cmd_q();
3107 *status_buffer = '\0'; // clear status buffer
3108 break;
3109 case ' ': // move right
3110 case 'l': // move right
3111 case VI_K_RIGHT: // Cursor Key Right
3112 if (cmdcnt-- > 1) {
3113 do_cmd(c);
3114 } // repeat cnt
3115 dot_right();
3116 break;
3117#ifdef CONFIG_FEATURE_VI_YANKMARK
3118 case '"': // "- name a register to use for Delete/Yank
3119 c1 = get_one_char();
3120 c1 = tolower(c1);
3121 if (islower(c1)) {
3122 YDreg = c1 - 'a';
3123 } else {
3124 indicate_error(c);
3125 }
3126 break;
3127 case '\'': // '- goto a specific mark
3128 c1 = get_one_char();
3129 c1 = tolower(c1);
3130 if (islower(c1)) {
3131 c1 = c1 - 'a';
3132 // get the b-o-l
3133 q = mark[(int) c1];
3134 if (text <= q && q < end) {
3135 dot = q;
3136 dot_begin(); // go to B-o-l
3137 dot_skip_over_ws();
3138 }
3139 } else if (c1 == '\'') { // goto previous context
3140 dot = swap_context(dot); // swap current and previous context
3141 dot_begin(); // go to B-o-l
3142 dot_skip_over_ws();
3143 } else {
3144 indicate_error(c);
3145 }
3146 break;
3147 case 'm': // m- Mark a line
3148 // this is really stupid. If there are any inserts or deletes
3149 // between text[0] and dot then this mark will not point to the
3150 // correct location! It could be off by many lines!
3151 // Well..., at least its quick and dirty.
3152 c1 = get_one_char();
3153 c1 = tolower(c1);
3154 if (islower(c1)) {
3155 c1 = c1 - 'a';
3156 // remember the line
3157 mark[(int) c1] = dot;
3158 } else {
3159 indicate_error(c);
3160 }
3161 break;
3162 case 'P': // P- Put register before
3163 case 'p': // p- put register after
3164 p = reg[YDreg];
3165 if (p == 0) {
3166 psbs("Nothing in register %c", what_reg());
3167 break;
3168 }
3169 // are we putting whole lines or strings
3170 if (strchr((char *) p, '\n') != NULL) {
3171 if (c == 'P') {
3172 dot_begin(); // putting lines- Put above
3173 }
3174 if (c == 'p') {
3175 // are we putting after very last line?
3176 if (end_line(dot) == (end - 1)) {
3177 dot = end; // force dot to end of text[]
3178 } else {
3179 dot_next(); // next line, then put before
3180 }
3181 }
3182 } else {
3183 if (c == 'p')
3184 dot_right(); // move to right, can move to NL
3185 }
3186 dot = string_insert(dot, p); // insert the string
3187 end_cmd_q(); // stop adding to q
3188 break;
3189 case 'U': // U- Undo; replace current line with original version
3190 if (reg[Ureg] != 0) {
3191 p = begin_line(dot);
3192 q = end_line(dot);
3193 p = text_hole_delete(p, q); // delete cur line
3194 p = string_insert(p, reg[Ureg]); // insert orig line
3195 dot = p;
3196 dot_skip_over_ws();
3197 }
3198 break;
3199#endif /* CONFIG_FEATURE_VI_YANKMARK */
3200 case '$': // $- goto end of line
3201 case VI_K_END: // Cursor Key End
3202 if (cmdcnt-- > 1) {
3203 do_cmd(c);
3204 } // repeat cnt
3205 dot = end_line(dot);
3206 break;
3207 case '%': // %- find matching char of pair () [] {}
3208 for (q = dot; q < end && *q != '\n'; q++) {
3209 if (strchr("()[]{}", *q) != NULL) {
3210 // we found half of a pair
3211 p = find_pair(q, *q);
3212 if (p == NULL) {
3213 indicate_error(c);
3214 } else {
3215 dot = p;
3216 }
3217 break;
3218 }
3219 }
3220 if (*q == '\n')
3221 indicate_error(c);
3222 break;
3223 case 'f': // f- forward to a user specified char
3224 last_forward_char = get_one_char(); // get the search char
3225 //
3226 // dont separate these two commands. 'f' depends on ';'
3227 //
3228 //**** fall thru to ... 'i'
3229 case ';': // ;- look at rest of line for last forward char
3230 if (cmdcnt-- > 1) {
3231 do_cmd(';');
3232 } // repeat cnt
3233 if (last_forward_char == 0) break;
3234 q = dot + 1;
3235 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3236 q++;
3237 }
3238 if (*q == last_forward_char)
3239 dot = q;
3240 break;
3241 case '-': // -- goto prev line
3242 if (cmdcnt-- > 1) {
3243 do_cmd(c);
3244 } // repeat cnt
3245 dot_prev();
3246 dot_skip_over_ws();
3247 break;
3248#ifdef CONFIG_FEATURE_VI_DOT_CMD
3249 case '.': // .- repeat the last modifying command
3250 // Stuff the last_modifying_cmd back into stdin
3251 // and let it be re-executed.
3252 if (last_modifying_cmd != 0) {
3253 ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3254 }
3255 break;
3256#endif /* CONFIG_FEATURE_VI_DOT_CMD */
3257#ifdef CONFIG_FEATURE_VI_SEARCH
3258 case '?': // /- search for a pattern
3259 case '/': // /- search for a pattern
3260 buf[0] = c;
3261 buf[1] = '\0';
3262 q = get_input_line(buf); // get input line- use "status line"
3263 if (strlen((char *) q) == 1)
3264 goto dc3; // if no pat re-use old pat
3265 if (strlen((char *) q) > 1) { // new pat- save it and find
3266 // there is a new pat
3267 free(last_search_pattern);
3268 last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3269 goto dc3; // now find the pattern
3270 }
3271 // user changed mind and erased the "/"- do nothing
3272 break;
3273 case 'N': // N- backward search for last pattern
3274 if (cmdcnt-- > 1) {
3275 do_cmd(c);
3276 } // repeat cnt
3277 dir = BACK; // assume BACKWARD search
3278 p = dot - 1;
3279 if (last_search_pattern[0] == '?') {
3280 dir = FORWARD;
3281 p = dot + 1;
3282 }
3283 goto dc4; // now search for pattern
3284 break;
3285 case 'n': // n- repeat search for last pattern
3286 // search rest of text[] starting at next char
3287 // if search fails return orignal "p" not the "p+1" address
3288 if (cmdcnt-- > 1) {
3289 do_cmd(c);
3290 } // repeat cnt
3291 dc3:
3292 if (last_search_pattern == 0) {
3293 msg = (Byte *) "No previous regular expression";
3294 goto dc2;
3295 }
3296 if (last_search_pattern[0] == '/') {
3297 dir = FORWARD; // assume FORWARD search
3298 p = dot + 1;
3299 }
3300 if (last_search_pattern[0] == '?') {
3301 dir = BACK;
3302 p = dot - 1;
3303 }
3304 dc4:
3305 q = char_search(p, last_search_pattern + 1, dir, FULL);
3306 if (q != NULL) {
3307 dot = q; // good search, update "dot"
3308 msg = (Byte *) "";
3309 goto dc2;
3310 }
3311 // no pattern found between "dot" and "end"- continue at top
3312 p = text;
3313 if (dir == BACK) {
3314 p = end - 1;
3315 }
3316 q = char_search(p, last_search_pattern + 1, dir, FULL);
3317 if (q != NULL) { // found something
3318 dot = q; // found new pattern- goto it
3319 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3320 if (dir == BACK) {
3321 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3322 }
3323 } else {
3324 msg = (Byte *) "Pattern not found";
3325 }
3326 dc2:
3327 psbs("%s", msg);
3328 break;
3329 case '{': // {- move backward paragraph
3330 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3331 if (q != NULL) { // found blank line
3332 dot = next_line(q); // move to next blank line
3333 }
3334 break;
3335 case '}': // }- move forward paragraph
3336 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3337 if (q != NULL) { // found blank line
3338 dot = next_line(q); // move to next blank line
3339 }
3340 break;
3341#endif /* CONFIG_FEATURE_VI_SEARCH */
3342 case '0': // 0- goto begining of line
3343 case '1': // 1-
3344 case '2': // 2-
3345 case '3': // 3-
3346 case '4': // 4-
3347 case '5': // 5-
3348 case '6': // 6-
3349 case '7': // 7-
3350 case '8': // 8-
3351 case '9': // 9-
3352 if (c == '0' && cmdcnt < 1) {
3353 dot_begin(); // this was a standalone zero
3354 } else {
3355 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3356 }
3357 break;
3358 case ':': // :- the colon mode commands
3359 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3360#ifdef CONFIG_FEATURE_VI_COLON
3361 colon(p); // execute the command
3362#else /* CONFIG_FEATURE_VI_COLON */
3363 if (*p == ':')
3364 p++; // move past the ':'
3365 cnt = strlen((char *) p);
3366 if (cnt <= 0)
3367 break;
3368 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3369 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3370 if (file_modified && p[1] != '!') {
3371 psbs("No write since last change (:quit! overrides)");
3372 } else {
3373 editing = 0;
3374 }
3375 } else if (strncasecmp((char *) p, "write", cnt) == 0 ||
3376 strncasecmp((char *) p, "wq", cnt) == 0 ||
3377 strncasecmp((char *) p, "x", cnt) == 0) {
3378 cnt = file_write(cfn, text, end - 1);
3379 file_modified = FALSE;
3380 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3381 if (p[0] == 'x' || p[1] == 'q') {
3382 editing = 0;
3383 }
3384 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3385 edit_status(); // show current file status
3386 } else if (sscanf((char *) p, "%d", &j) > 0) {
3387 dot = find_line(j); // go to line # j
3388 dot_skip_over_ws();
3389 } else { // unrecognised cmd
3390 ni((Byte *) p);
3391 }
3392#endif /* CONFIG_FEATURE_VI_COLON */
3393 break;
3394 case '<': // <- Left shift something
3395 case '>': // >- Right shift something
3396 cnt = count_lines(text, dot); // remember what line we are on
3397 c1 = get_one_char(); // get the type of thing to delete
3398 find_range(&p, &q, c1);
3399 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3400 p = begin_line(p);
3401 q = end_line(q);
3402 i = count_lines(p, q); // # of lines we are shifting
3403 for ( ; i > 0; i--, p = next_line(p)) {
3404 if (c == '<') {
3405 // shift left- remove tab or 8 spaces
3406 if (*p == '\t') {
3407 // shrink buffer 1 char
3408 (void) text_hole_delete(p, p);
3409 } else if (*p == ' ') {
3410 // we should be calculating columns, not just SPACE
3411 for (j = 0; *p == ' ' && j < tabstop; j++) {
3412 (void) text_hole_delete(p, p);
3413 }
3414 }
3415 } else if (c == '>') {
3416 // shift right -- add tab or 8 spaces
3417 (void) char_insert(p, '\t');
3418 }
3419 }
3420 dot = find_line(cnt); // what line were we on
3421 dot_skip_over_ws();
3422 end_cmd_q(); // stop adding to q
3423 break;
3424 case 'A': // A- append at e-o-l
3425 dot_end(); // go to e-o-l
3426 //**** fall thru to ... 'a'
3427 case 'a': // a- append after current char
3428 if (*dot != '\n')
3429 dot++;
3430 goto dc_i;
3431 break;
3432 case 'B': // B- back a blank-delimited Word
3433 case 'E': // E- end of a blank-delimited word
3434 case 'W': // W- forward a blank-delimited word
3435 if (cmdcnt-- > 1) {
3436 do_cmd(c);
3437 } // repeat cnt
3438 dir = FORWARD;
3439 if (c == 'B')
3440 dir = BACK;
3441 if (c == 'W' || isspace(dot[dir])) {
3442 dot = skip_thing(dot, 1, dir, S_TO_WS);
3443 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3444 }
3445 if (c != 'W')
3446 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3447 break;
3448 case 'C': // C- Change to e-o-l
3449 case 'D': // D- delete to e-o-l
3450 save_dot = dot;
3451 dot = dollar_line(dot); // move to before NL
3452 // copy text into a register and delete
3453 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3454 if (c == 'C')
3455 goto dc_i; // start inserting
3456#ifdef CONFIG_FEATURE_VI_DOT_CMD
3457 if (c == 'D')
3458 end_cmd_q(); // stop adding to q
3459#endif /* CONFIG_FEATURE_VI_DOT_CMD */
3460 break;
3461 case 'G': // G- goto to a line number (default= E-O-F)
3462 dot = end - 1; // assume E-O-F
3463 if (cmdcnt > 0) {
3464 dot = find_line(cmdcnt); // what line is #cmdcnt
3465 }
3466 dot_skip_over_ws();
3467 break;
3468 case 'H': // H- goto top line on screen
3469 dot = screenbegin;
3470 if (cmdcnt > (rows - 1)) {
3471 cmdcnt = (rows - 1);
3472 }
3473 if (cmdcnt-- > 1) {
3474 do_cmd('+');
3475 } // repeat cnt
3476 dot_skip_over_ws();
3477 break;
3478 case 'I': // I- insert before first non-blank
3479 dot_begin(); // 0
3480 dot_skip_over_ws();
3481 //**** fall thru to ... 'i'
3482 case 'i': // i- insert before current char
3483 case VI_K_INSERT: // Cursor Key Insert
3484 dc_i:
3485 cmd_mode = 1; // start insrting
3486 psb("-- Insert --");
3487 break;
3488 case 'J': // J- join current and next lines together
3489 if (cmdcnt-- > 2) {
3490 do_cmd(c);
3491 } // repeat cnt
3492 dot_end(); // move to NL
3493 if (dot < end - 1) { // make sure not last char in text[]
3494 *dot++ = ' '; // replace NL with space
3495 while (isblnk(*dot)) { // delete leading WS
3496 dot_delete();
3497 }
3498 }
3499 end_cmd_q(); // stop adding to q
3500 break;
3501 case 'L': // L- goto bottom line on screen
3502 dot = end_screen();
3503 if (cmdcnt > (rows - 1)) {
3504 cmdcnt = (rows - 1);
3505 }
3506 if (cmdcnt-- > 1) {
3507 do_cmd('-');
3508 } // repeat cnt
3509 dot_begin();
3510 dot_skip_over_ws();
3511 break;
3512 case 'M': // M- goto middle line on screen
3513 dot = screenbegin;
3514 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3515 dot = next_line(dot);
3516 break;
3517 case 'O': // O- open a empty line above
3518 // 0i\n ESC -i
3519 p = begin_line(dot);
3520 if (p[-1] == '\n') {
3521 dot_prev();
3522 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3523 dot_end();
3524 dot = char_insert(dot, '\n');
3525 } else {
3526 dot_begin(); // 0
3527 dot = char_insert(dot, '\n'); // i\n ESC
3528 dot_prev(); // -
3529 }
3530 goto dc_i;
3531 break;
3532 case 'R': // R- continuous Replace char
3533 dc5:
3534 cmd_mode = 2;
3535 psb("-- Replace --");
3536 break;
3537 case 'X': // X- delete char before dot
3538 case 'x': // x- delete the current char
3539 case 's': // s- substitute the current char
3540 if (cmdcnt-- > 1) {
3541 do_cmd(c);
3542 } // repeat cnt
3543 dir = 0;
3544 if (c == 'X')
3545 dir = -1;
3546 if (dot[dir] != '\n') {
3547 if (c == 'X')
3548 dot--; // delete prev char
3549 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3550 }
3551 if (c == 's')
3552 goto dc_i; // start insrting
3553 end_cmd_q(); // stop adding to q
3554 break;
3555 case 'Z': // Z- if modified, {write}; exit
3556 // ZZ means to save file (if necessary), then exit
3557 c1 = get_one_char();
3558 if (c1 != 'Z') {
3559 indicate_error(c);
3560 break;
3561 }
3562 if (file_modified
3563#ifdef CONFIG_FEATURE_VI_READONLY
3564 && ! vi_readonly
3565 && ! readonly
3566#endif /* CONFIG_FEATURE_VI_READONLY */
3567 ) {
3568 cnt = file_write(cfn, text, end - 1);
3569 if (cnt == (end - 1 - text + 1)) {
3570 editing = 0;
3571 }
3572 } else {
3573 editing = 0;
3574 }
3575 break;
3576 case '^': // ^- move to first non-blank on line
3577 dot_begin();
3578 dot_skip_over_ws();
3579 break;
3580 case 'b': // b- back a word
3581 case 'e': // e- end of word
3582 if (cmdcnt-- > 1) {
3583 do_cmd(c);
3584 } // repeat cnt
3585 dir = FORWARD;
3586 if (c == 'b')
3587 dir = BACK;
3588 if ((dot + dir) < text || (dot + dir) > end - 1)
3589 break;
3590 dot += dir;
3591 if (isspace(*dot)) {
3592 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3593 }
3594 if (isalnum(*dot) || *dot == '_') {
3595 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3596 } else if (ispunct(*dot)) {
3597 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3598 }
3599 break;
3600 case 'c': // c- change something
3601 case 'd': // d- delete something
3602#ifdef CONFIG_FEATURE_VI_YANKMARK
3603 case 'y': // y- yank something
3604 case 'Y': // Y- Yank a line
3605#endif /* CONFIG_FEATURE_VI_YANKMARK */
3606 yf = YANKDEL; // assume either "c" or "d"
3607#ifdef CONFIG_FEATURE_VI_YANKMARK
3608 if (c == 'y' || c == 'Y')
3609 yf = YANKONLY;
3610#endif /* CONFIG_FEATURE_VI_YANKMARK */
3611 c1 = 'y';
3612 if (c != 'Y')
3613 c1 = get_one_char(); // get the type of thing to delete
3614 find_range(&p, &q, c1);
3615 if (c1 == 27) { // ESC- user changed mind and wants out
3616 c = c1 = 27; // Escape- do nothing
3617 } else if (strchr("wW", c1)) {
3618 if (c == 'c') {
3619 // don't include trailing WS as part of word
3620 while (isblnk(*q)) {
3621 if (q <= text || q[-1] == '\n')
3622 break;
3623 q--;
3624 }
3625 }
3626 dot = yank_delete(p, q, 0, yf); // delete word
3627 } else if (strchr("^0bBeEft$", c1)) {
3628 // single line copy text into a register and delete
3629 dot = yank_delete(p, q, 0, yf); // delete word
3630 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3631 // multiple line copy text into a register and delete
3632 dot = yank_delete(p, q, 1, yf); // delete lines
3633 if (c == 'c') {
3634 dot = char_insert(dot, '\n');
3635 // on the last line of file don't move to prev line
3636 if (dot != (end-1)) {
3637 dot_prev();
3638 }
3639 } else if (c == 'd') {
3640 dot_begin();
3641 dot_skip_over_ws();
3642 }
3643 } else {
3644 // could not recognize object
3645 c = c1 = 27; // error-
3646 indicate_error(c);
3647 }
3648 if (c1 != 27) {
3649 // if CHANGING, not deleting, start inserting after the delete
3650 if (c == 'c') {
3651 strcpy((char *) buf, "Change");
3652 goto dc_i; // start inserting
3653 }
3654 if (c == 'd') {
3655 strcpy((char *) buf, "Delete");
3656 }
3657#ifdef CONFIG_FEATURE_VI_YANKMARK
3658 if (c == 'y' || c == 'Y') {
3659 strcpy((char *) buf, "Yank");
3660 }
3661 p = reg[YDreg];
3662 q = p + strlen((char *) p);
3663 for (cnt = 0; p <= q; p++) {
3664 if (*p == '\n')
3665 cnt++;
3666 }
3667 psb("%s %d lines (%d chars) using [%c]",
3668 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3669#endif /* CONFIG_FEATURE_VI_YANKMARK */
3670 end_cmd_q(); // stop adding to q
3671 }
3672 break;
3673 case 'k': // k- goto prev line, same col
3674 case VI_K_UP: // cursor key Up
3675 if (cmdcnt-- > 1) {
3676 do_cmd(c);
3677 } // repeat cnt
3678 dot_prev();
3679 dot = move_to_col(dot, ccol + offset); // try stay in same col
3680 break;
3681 case 'r': // r- replace the current char with user input
3682 c1 = get_one_char(); // get the replacement char
3683 if (*dot != '\n') {
3684 *dot = c1;
3685 file_modified = TRUE; // has the file been modified
3686 }
3687 end_cmd_q(); // stop adding to q
3688 break;
3689 case 't': // t- move to char prior to next x
3690 last_forward_char = get_one_char();
3691 do_cmd(';');
3692 if (*dot == last_forward_char)
3693 dot_left();
3694 last_forward_char= 0;
3695 break;
3696 case 'w': // w- forward a word
3697 if (cmdcnt-- > 1) {
3698 do_cmd(c);
3699 } // repeat cnt
3700 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3701 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3702 } else if (ispunct(*dot)) { // we are on PUNCT
3703 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3704 }
3705 if (dot < end - 1)
3706 dot++; // move over word
3707 if (isspace(*dot)) {
3708 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3709 }
3710 break;
3711 case 'z': // z-
3712 c1 = get_one_char(); // get the replacement char
3713 cnt = 0;
3714 if (c1 == '.')
3715 cnt = (rows - 2) / 2; // put dot at center
3716 if (c1 == '-')
3717 cnt = rows - 2; // put dot at bottom
3718 screenbegin = begin_line(dot); // start dot at top
3719 dot_scroll(cnt, -1);
3720 break;
3721 case '|': // |- move to column "cmdcnt"
3722 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3723 break;
3724 case '~': // ~- flip the case of letters a-z -> A-Z
3725 if (cmdcnt-- > 1) {
3726 do_cmd(c);
3727 } // repeat cnt
3728 if (islower(*dot)) {
3729 *dot = toupper(*dot);
3730 file_modified = TRUE; // has the file been modified
3731 } else if (isupper(*dot)) {
3732 *dot = tolower(*dot);
3733 file_modified = TRUE; // has the file been modified
3734 }
3735 dot_right();
3736 end_cmd_q(); // stop adding to q
3737 break;
3738 //----- The Cursor and Function Keys -----------------------------
3739 case VI_K_HOME: // Cursor Key Home
3740 dot_begin();
3741 break;
3742 // The Fn keys could point to do_macro which could translate them
3743 case VI_K_FUN1: // Function Key F1
3744 case VI_K_FUN2: // Function Key F2
3745 case VI_K_FUN3: // Function Key F3
3746 case VI_K_FUN4: // Function Key F4
3747 case VI_K_FUN5: // Function Key F5
3748 case VI_K_FUN6: // Function Key F6
3749 case VI_K_FUN7: // Function Key F7
3750 case VI_K_FUN8: // Function Key F8
3751 case VI_K_FUN9: // Function Key F9
3752 case VI_K_FUN10: // Function Key F10
3753 case VI_K_FUN11: // Function Key F11
3754 case VI_K_FUN12: // Function Key F12
3755 break;
3756 }
3757
3758 dc1:
3759 // if text[] just became empty, add back an empty line
3760 if (end == text) {
3761 (void) char_insert(text, '\n'); // start empty buf with dummy line
3762 dot = text;
3763 }
3764 // it is OK for dot to exactly equal to end, otherwise check dot validity
3765 if (dot != end) {
3766 dot = bound_dot(dot); // make sure "dot" is valid
3767 }
3768#ifdef CONFIG_FEATURE_VI_YANKMARK
3769 check_context(c); // update the current context
3770#endif /* CONFIG_FEATURE_VI_YANKMARK */
3771
3772 if (!isdigit(c))
3773 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3774 cnt = dot - begin_line(dot);
3775 // Try to stay off of the Newline
3776 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3777 dot--;
3778}
3779
3780#ifdef CONFIG_FEATURE_VI_CRASHME
3781static int totalcmds = 0;
3782static int Mp = 85; // Movement command Probability
3783static int Np = 90; // Non-movement command Probability
3784static int Dp = 96; // Delete command Probability
3785static int Ip = 97; // Insert command Probability
3786static int Yp = 98; // Yank command Probability
3787static int Pp = 99; // Put command Probability
3788static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3789char chars[20] = "\t012345 abcdABCD-=.$";
3790char *words[20] = { "this", "is", "a", "test",
3791 "broadcast", "the", "emergency", "of",
3792 "system", "quick", "brown", "fox",
3793 "jumped", "over", "lazy", "dogs",
3794 "back", "January", "Febuary", "March"
3795};
3796char *lines[20] = {
3797 "You should have received a copy of the GNU General Public License\n",
3798 "char c, cm, *cmd, *cmd1;\n",
3799 "generate a command by percentages\n",
3800 "Numbers may be typed as a prefix to some commands.\n",
3801 "Quit, discarding changes!\n",
3802 "Forced write, if permission originally not valid.\n",
3803 "In general, any ex or ed command (such as substitute or delete).\n",
3804 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3805 "Please get w/ me and I will go over it with you.\n",
3806 "The following is a list of scheduled, committed changes.\n",
3807 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3808 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3809 "Any question about transactions please contact Sterling Huxley.\n",
3810 "I will try to get back to you by Friday, December 31.\n",
3811 "This Change will be implemented on Friday.\n",
3812 "Let me know if you have problems accessing this;\n",
3813 "Sterling Huxley recently added you to the access list.\n",
3814 "Would you like to go to lunch?\n",
3815 "The last command will be automatically run.\n",
3816 "This is too much english for a computer geek.\n",
3817};
3818char *multilines[20] = {
3819 "You should have received a copy of the GNU General Public License\n",
3820 "char c, cm, *cmd, *cmd1;\n",
3821 "generate a command by percentages\n",
3822 "Numbers may be typed as a prefix to some commands.\n",
3823 "Quit, discarding changes!\n",
3824 "Forced write, if permission originally not valid.\n",
3825 "In general, any ex or ed command (such as substitute or delete).\n",
3826 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3827 "Please get w/ me and I will go over it with you.\n",
3828 "The following is a list of scheduled, committed changes.\n",
3829 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3830 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3831 "Any question about transactions please contact Sterling Huxley.\n",
3832 "I will try to get back to you by Friday, December 31.\n",
3833 "This Change will be implemented on Friday.\n",
3834 "Let me know if you have problems accessing this;\n",
3835 "Sterling Huxley recently added you to the access list.\n",
3836 "Would you like to go to lunch?\n",
3837 "The last command will be automatically run.\n",
3838 "This is too much english for a computer geek.\n",
3839};
3840
3841// create a random command to execute
3842static void crash_dummy()
3843{
3844 static int sleeptime; // how long to pause between commands
3845 char c, cm, *cmd, *cmd1;
3846 int i, cnt, thing, rbi, startrbi, percent;
3847
3848 // "dot" movement commands
3849 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3850
3851 // is there already a command running?
3852 if (readed_for_parse > 0)
3853 goto cd1;
3854 cd0:
3855 startrbi = rbi = 0;
3856 sleeptime = 0; // how long to pause between commands
3857 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3858 // generate a command by percentages
3859 percent = (int) lrand48() % 100; // get a number from 0-99
3860 if (percent < Mp) { // Movement commands
3861 // available commands
3862 cmd = cmd1;
3863 M++;
3864 } else if (percent < Np) { // non-movement commands
3865 cmd = "mz<>\'\""; // available commands
3866 N++;
3867 } else if (percent < Dp) { // Delete commands
3868 cmd = "dx"; // available commands
3869 D++;
3870 } else if (percent < Ip) { // Inset commands
3871 cmd = "iIaAsrJ"; // available commands
3872 I++;
3873 } else if (percent < Yp) { // Yank commands
3874 cmd = "yY"; // available commands
3875 Y++;
3876 } else if (percent < Pp) { // Put commands
3877 cmd = "pP"; // available commands
3878 P++;
3879 } else {
3880 // We do not know how to handle this command, try again
3881 U++;
3882 goto cd0;
3883 }
3884 // randomly pick one of the available cmds from "cmd[]"
3885 i = (int) lrand48() % strlen(cmd);
3886 cm = cmd[i];
3887 if (strchr(":\024", cm))
3888 goto cd0; // dont allow colon or ctrl-T commands
3889 readbuffer[rbi++] = cm; // put cmd into input buffer
3890
3891 // now we have the command-
3892 // there are 1, 2, and multi char commands
3893 // find out which and generate the rest of command as necessary
3894 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3895 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3896 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3897 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3898 }
3899 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3900 c = cmd1[thing];
3901 readbuffer[rbi++] = c; // add movement to input buffer
3902 }
3903 if (strchr("iIaAsc", cm)) { // multi-char commands
3904 if (cm == 'c') {
3905 // change some thing
3906 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3907 c = cmd1[thing];
3908 readbuffer[rbi++] = c; // add movement to input buffer
3909 }
3910 thing = (int) lrand48() % 4; // what thing to insert
3911 cnt = (int) lrand48() % 10; // how many to insert
3912 for (i = 0; i < cnt; i++) {
3913 if (thing == 0) { // insert chars
3914 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3915 } else if (thing == 1) { // insert words
3916 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3917 strcat((char *) readbuffer, " ");
3918 sleeptime = 0; // how fast to type
3919 } else if (thing == 2) { // insert lines
3920 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3921 sleeptime = 0; // how fast to type
3922 } else { // insert multi-lines
3923 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3924 sleeptime = 0; // how fast to type
3925 }
3926 }
3927 strcat((char *) readbuffer, "\033");
3928 }
3929 readed_for_parse = strlen(readbuffer);
3930 cd1:
3931 totalcmds++;
3932 if (sleeptime > 0)
3933 (void) mysleep(sleeptime); // sleep 1/100 sec
3934}
3935
3936// test to see if there are any errors
3937static void crash_test()
3938{
3939 static time_t oldtim;
3940 time_t tim;
3941 char d[2], msg[BUFSIZ];
3942
3943 msg[0] = '\0';
3944 if (end < text) {
3945 strcat((char *) msg, "end<text ");
3946 }
3947 if (end > textend) {
3948 strcat((char *) msg, "end>textend ");
3949 }
3950 if (dot < text) {
3951 strcat((char *) msg, "dot<text ");
3952 }
3953 if (dot > end) {
3954 strcat((char *) msg, "dot>end ");
3955 }
3956 if (screenbegin < text) {
3957 strcat((char *) msg, "screenbegin<text ");
3958 }
3959 if (screenbegin > end - 1) {
3960 strcat((char *) msg, "screenbegin>end-1 ");
3961 }
3962
3963 if (strlen(msg) > 0) {
3964 alarm(0);
3965 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3966 totalcmds, last_input_char, msg, SOs, SOn);
3967 fflush(stdout);
3968 while (read(0, d, 1) > 0) {
3969 if (d[0] == '\n' || d[0] == '\r')
3970 break;
3971 }
3972 alarm(3);
3973 }
3974 tim = (time_t) time((time_t *) 0);
3975 if (tim >= (oldtim + 3)) {
3976 sprintf((char *) status_buffer,
3977 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3978 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3979 oldtim = tim;
3980 }
3981 return;
3982}
3983#endif /* CONFIG_FEATURE_VI_CRASHME */