aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-rw-r--r--editors/Config.src60
-rw-r--r--editors/Kbuild.src5
-rw-r--r--editors/awk.c125
-rw-r--r--editors/cmp.c11
-rw-r--r--editors/diff.c27
-rw-r--r--editors/ed.c12
-rw-r--r--editors/patch_bbox.c4
-rw-r--r--editors/sed.c256
8 files changed, 285 insertions, 215 deletions
diff --git a/editors/Config.src b/editors/Config.src
index af1e1de5e..c6e9d92af 100644
--- a/editors/Config.src
+++ b/editors/Config.src
@@ -7,66 +7,6 @@ menu "Editors"
7 7
8INSERT 8INSERT
9 9
10config AWK
11 bool "awk"
12 default y
13 help
14 Awk is used as a pattern scanning and processing language. This is
15 the BusyBox implementation of that programming language.
16
17config FEATURE_AWK_LIBM
18 bool "Enable math functions (requires libm)"
19 default y
20 depends on AWK
21 help
22 Enable math functions of the Awk programming language.
23 NOTE: This will require libm to be present for linking.
24
25config CMP
26 bool "cmp"
27 default y
28 help
29 cmp is used to compare two files and returns the result
30 to standard output.
31
32config DIFF
33 bool "diff"
34 default y
35 help
36 diff compares two files or directories and outputs the
37 differences between them in a form that can be given to
38 the patch command.
39
40config FEATURE_DIFF_LONG_OPTIONS
41 bool "Enable long options"
42 default y
43 depends on DIFF && LONG_OPTS
44 help
45 Enable use of long options.
46
47config FEATURE_DIFF_DIR
48 bool "Enable directory support"
49 default y
50 depends on DIFF
51 help
52 This option enables support for directory and subdirectory
53 comparison.
54
55config ED
56 bool "ed"
57 default y
58 help
59 The original 1970's Unix text editor, from the days of teletypes.
60 Small, simple, evil. Part of SUSv3. If you're not already using
61 this, you don't need it.
62
63config SED
64 bool "sed"
65 default y
66 help
67 sed is used to perform text transformations on a file
68 or input from a pipeline.
69
70config FEATURE_ALLOW_EXEC 10config FEATURE_ALLOW_EXEC
71 bool "Allow vi and awk to execute shell commands" 11 bool "Allow vi and awk to execute shell commands"
72 default y 12 default y
diff --git a/editors/Kbuild.src b/editors/Kbuild.src
index 8888cba12..6b4fb7470 100644
--- a/editors/Kbuild.src
+++ b/editors/Kbuild.src
@@ -7,8 +7,3 @@
7lib-y:= 7lib-y:=
8 8
9INSERT 9INSERT
10lib-$(CONFIG_AWK) += awk.o
11lib-$(CONFIG_CMP) += cmp.o
12lib-$(CONFIG_DIFF) += diff.o
13lib-$(CONFIG_ED) += ed.o
14lib-$(CONFIG_SED) += sed.o
diff --git a/editors/awk.c b/editors/awk.c
index 77784dfc1..d0e3781e7 100644
--- a/editors/awk.c
+++ b/editors/awk.c
@@ -7,12 +7,45 @@
7 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 */ 8 */
9 9
10//config:config AWK
11//config: bool "awk"
12//config: default y
13//config: help
14//config: Awk is used as a pattern scanning and processing language. This is
15//config: the BusyBox implementation of that programming language.
16//config:
17//config:config FEATURE_AWK_LIBM
18//config: bool "Enable math functions (requires libm)"
19//config: default y
20//config: depends on AWK
21//config: help
22//config: Enable math functions of the Awk programming language.
23//config: NOTE: This will require libm to be present for linking.
24//config:
25//config:config FEATURE_AWK_GNU_EXTENSIONS
26//config: bool "Enable a few GNU extensions"
27//config: default y
28//config: depends on AWK
29//config: help
30//config: Enable a few features from gawk:
31//config: * command line option -e AWK_PROGRAM
32//config: * simultaneous use of -f and -e on the command line.
33//config: This enables the use of awk library files.
34//config: Ex: awk -f mylib.awk -e '{print myfunction($1);}' ...
35
36//applet:IF_AWK(APPLET_NOEXEC(awk, awk, BB_DIR_USR_BIN, BB_SUID_DROP, awk))
37
38//kbuild:lib-$(CONFIG_AWK) += awk.o
39
10//usage:#define awk_trivial_usage 40//usage:#define awk_trivial_usage
11//usage: "[OPTIONS] [AWK_PROGRAM] [FILE]..." 41//usage: "[OPTIONS] [AWK_PROGRAM] [FILE]..."
12//usage:#define awk_full_usage "\n\n" 42//usage:#define awk_full_usage "\n\n"
13//usage: " -v VAR=VAL Set variable" 43//usage: " -v VAR=VAL Set variable"
14//usage: "\n -F SEP Use SEP as field separator" 44//usage: "\n -F SEP Use SEP as field separator"
15//usage: "\n -f FILE Read program from FILE" 45//usage: "\n -f FILE Read program from FILE"
46//usage: IF_FEATURE_AWK_GNU_EXTENSIONS(
47//usage: "\n -e AWK_PROGRAM"
48//usage: )
16 49
17#include "libbb.h" 50#include "libbb.h"
18#include "xregex.h" 51#include "xregex.h"
@@ -38,6 +71,25 @@
38#endif 71#endif
39 72
40 73
74#define OPTSTR_AWK \
75 "F:v:f:" \
76 IF_FEATURE_AWK_GNU_EXTENSIONS("e:") \
77 "W:"
78#define OPTCOMPLSTR_AWK \
79 "v::f::" \
80 IF_FEATURE_AWK_GNU_EXTENSIONS("e::")
81enum {
82 OPTBIT_F, /* define field separator */
83 OPTBIT_v, /* define variable */
84 OPTBIT_f, /* pull in awk program from file */
85 IF_FEATURE_AWK_GNU_EXTENSIONS(OPTBIT_e,) /* -e AWK_PROGRAM */
86 OPTBIT_W, /* -W ignored */
87 OPT_F = 1 << OPTBIT_F,
88 OPT_v = 1 << OPTBIT_v,
89 OPT_f = 1 << OPTBIT_f,
90 OPT_e = IF_FEATURE_AWK_GNU_EXTENSIONS((1 << OPTBIT_e)) + 0,
91 OPT_W = 1 << OPTBIT_W
92};
41 93
42#define MAXVARFMT 240 94#define MAXVARFMT 240
43#define MINNVBLOCK 64 95#define MINNVBLOCK 64
@@ -2784,8 +2836,16 @@ static var *evaluate(node *op, var *res)
2784 break; 2836 break;
2785 2837
2786 case F_le: 2838 case F_le:
2787 if (!op1) 2839 debug_printf_eval("length: L.s:'%s'\n", L.s);
2840 if (!op1) {
2788 L.s = getvar_s(intvar[F0]); 2841 L.s = getvar_s(intvar[F0]);
2842 debug_printf_eval("length: L.s='%s'\n", L.s);
2843 }
2844 else if (L.v->type & VF_ARRAY) {
2845 R_d = L.v->x.array->nel;
2846 debug_printf_eval("length: array_len:%d\n", L.v->x.array->nel);
2847 break;
2848 }
2789 R_d = strlen(L.s); 2849 R_d = strlen(L.s);
2790 break; 2850 break;
2791 2851
@@ -3079,6 +3139,9 @@ int awk_main(int argc, char **argv)
3079 char *opt_F; 3139 char *opt_F;
3080 llist_t *list_v = NULL; 3140 llist_t *list_v = NULL;
3081 llist_t *list_f = NULL; 3141 llist_t *list_f = NULL;
3142#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
3143 llist_t *list_e = NULL;
3144#endif
3082 int i, j; 3145 int i, j;
3083 var *v; 3146 var *v;
3084 var tv; 3147 var tv;
@@ -3137,47 +3200,51 @@ int awk_main(int argc, char **argv)
3137 *s1 = '='; 3200 *s1 = '=';
3138 } 3201 }
3139 } 3202 }
3140 opt_complementary = "v::f::"; /* -v and -f can occur multiple times */ 3203 opt_complementary = OPTCOMPLSTR_AWK;
3141 opt = getopt32(argv, "F:v:f:W:", &opt_F, &list_v, &list_f, NULL); 3204 opt = getopt32(argv, OPTSTR_AWK, &opt_F, &list_v, &list_f, IF_FEATURE_AWK_GNU_EXTENSIONS(&list_e,) NULL);
3142 argv += optind; 3205 argv += optind;
3143 argc -= optind; 3206 argc -= optind;
3144 if (opt & 0x1) { /* -F */ 3207 if (opt & OPT_W)
3208 bb_error_msg("warning: option -W is ignored");
3209 if (opt & OPT_F) {
3145 unescape_string_in_place(opt_F); 3210 unescape_string_in_place(opt_F);
3146 setvar_s(intvar[FS], opt_F); 3211 setvar_s(intvar[FS], opt_F);
3147 } 3212 }
3148 while (list_v) { /* -v */ 3213 while (list_v) {
3149 if (!is_assignment(llist_pop(&list_v))) 3214 if (!is_assignment(llist_pop(&list_v)))
3150 bb_show_usage(); 3215 bb_show_usage();
3151 } 3216 }
3152 if (list_f) { /* -f */ 3217 while (list_f) {
3153 do { 3218 char *s = NULL;
3154 char *s = NULL; 3219 FILE *from_file;
3155 FILE *from_file; 3220
3156 3221 g_progname = llist_pop(&list_f);
3157 g_progname = llist_pop(&list_f); 3222 from_file = xfopen_stdin(g_progname);
3158 from_file = xfopen_stdin(g_progname); 3223 /* one byte is reserved for some trick in next_token */
3159 /* one byte is reserved for some trick in next_token */ 3224 for (i = j = 1; j > 0; i += j) {
3160 for (i = j = 1; j > 0; i += j) { 3225 s = xrealloc(s, i + 4096);
3161 s = xrealloc(s, i + 4096); 3226 j = fread(s + i, 1, 4094, from_file);
3162 j = fread(s + i, 1, 4094, from_file); 3227 }
3163 } 3228 s[i] = '\0';
3164 s[i] = '\0'; 3229 fclose(from_file);
3165 fclose(from_file); 3230 parse_program(s + 1);
3166 parse_program(s + 1); 3231 free(s);
3167 free(s); 3232 }
3168 } while (list_f); 3233 g_progname = "cmd. line";
3169 argc++; 3234#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
3170 } else { // no -f: take program from 1st parameter 3235 while (list_e) {
3171 if (!argc) 3236 parse_program(llist_pop(&list_e));
3237 }
3238#endif
3239 if (!(opt & (OPT_f | OPT_e))) {
3240 if (!*argv)
3172 bb_show_usage(); 3241 bb_show_usage();
3173 g_progname = "cmd. line";
3174 parse_program(*argv++); 3242 parse_program(*argv++);
3243 argc--;
3175 } 3244 }
3176 if (opt & 0x8) // -W
3177 bb_error_msg("warning: option -W is ignored");
3178 3245
3179 /* fill in ARGV array */ 3246 /* fill in ARGV array */
3180 setvar_i(intvar[ARGC], argc); 3247 setvar_i(intvar[ARGC], argc + 1);
3181 setari_u(intvar[ARGV], 0, "awk"); 3248 setari_u(intvar[ARGV], 0, "awk");
3182 i = 0; 3249 i = 0;
3183 while (*argv) 3250 while (*argv)
diff --git a/editors/cmp.c b/editors/cmp.c
index fbe6b9753..a4af6f480 100644
--- a/editors/cmp.c
+++ b/editors/cmp.c
@@ -10,6 +10,17 @@
10/* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */ 10/* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */
11/* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */ 11/* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */
12 12
13//config:config CMP
14//config: bool "cmp"
15//config: default y
16//config: help
17//config: cmp is used to compare two files and returns the result
18//config: to standard output.
19
20//kbuild:lib-$(CONFIG_CMP) += cmp.o
21
22//applet:IF_CMP(APPLET(cmp, BB_DIR_USR_BIN, BB_SUID_DROP))
23
13//usage:#define cmp_trivial_usage 24//usage:#define cmp_trivial_usage
14//usage: "[-l] [-s] FILE1 [FILE2" IF_DESKTOP(" [SKIP1 [SKIP2]]") "]" 25//usage: "[-l] [-s] FILE1 [FILE2" IF_DESKTOP(" [SKIP1 [SKIP2]]") "]"
15//usage:#define cmp_full_usage "\n\n" 26//usage:#define cmp_full_usage "\n\n"
diff --git a/editors/diff.c b/editors/diff.c
index b08ded3a1..a78a0ee28 100644
--- a/editors/diff.c
+++ b/editors/diff.c
@@ -76,6 +76,33 @@
76 * 6n words for files of length n. 76 * 6n words for files of length n.
77 */ 77 */
78 78
79//config:config DIFF
80//config: bool "diff"
81//config: default y
82//config: help
83//config: diff compares two files or directories and outputs the
84//config: differences between them in a form that can be given to
85//config: the patch command.
86//config:
87//config:config FEATURE_DIFF_LONG_OPTIONS
88//config: bool "Enable long options"
89//config: default y
90//config: depends on DIFF && LONG_OPTS
91//config: help
92//config: Enable use of long options.
93//config:
94//config:config FEATURE_DIFF_DIR
95//config: bool "Enable directory support"
96//config: default y
97//config: depends on DIFF
98//config: help
99//config: This option enables support for directory and subdirectory
100//config: comparison.
101
102//kbuild:lib-$(CONFIG_DIFF) += diff.o
103
104//applet:IF_DIFF(APPLET(diff, BB_DIR_USR_BIN, BB_SUID_DROP))
105
79//usage:#define diff_trivial_usage 106//usage:#define diff_trivial_usage
80//usage: "[-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2" 107//usage: "[-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
81//usage:#define diff_full_usage "\n\n" 108//usage:#define diff_full_usage "\n\n"
diff --git a/editors/ed.c b/editors/ed.c
index dbb51306c..3087fb0b9 100644
--- a/editors/ed.c
+++ b/editors/ed.c
@@ -7,6 +7,18 @@
7 * The "ed" built-in command (much simplified) 7 * The "ed" built-in command (much simplified)
8 */ 8 */
9 9
10//config:config ED
11//config: bool "ed"
12//config: default y
13//config: help
14//config: The original 1970's Unix text editor, from the days of teletypes.
15//config: Small, simple, evil. Part of SUSv3. If you're not already using
16//config: this, you don't need it.
17
18//kbuild:lib-$(CONFIG_ED) += ed.o
19
20//applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
21
10//usage:#define ed_trivial_usage "" 22//usage:#define ed_trivial_usage ""
11//usage:#define ed_full_usage "" 23//usage:#define ed_full_usage ""
12 24
diff --git a/editors/patch_bbox.c b/editors/patch_bbox.c
index 78aa5fde8..aae7b7987 100644
--- a/editors/patch_bbox.c
+++ b/editors/patch_bbox.c
@@ -188,8 +188,8 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
188 unsigned src_last_line = 1; 188 unsigned src_last_line = 1;
189 unsigned dst_last_line = 1; 189 unsigned dst_last_line = 1;
190 190
191 if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3) 191 if ((sscanf(patch_line, "@@ -%u,%u +%u,%u", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
192 && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2) 192 && (sscanf(patch_line, "@@ -%u +%u,%u", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
193 ) { 193 ) {
194 /* No more hunks for this file */ 194 /* No more hunks for this file */
195 break; 195 break;
diff --git a/editors/sed.c b/editors/sed.c
index 3a0d917aa..e18e48ab5 100644
--- a/editors/sed.c
+++ b/editors/sed.c
@@ -23,9 +23,6 @@
23 * resulting sed_cmd_t structures are appended to a linked list 23 * resulting sed_cmd_t structures are appended to a linked list
24 * (G.sed_cmd_head/G.sed_cmd_tail). 24 * (G.sed_cmd_head/G.sed_cmd_tail).
25 * 25 *
26 * add_input_file() adds a FILE* to the list of input files. We need to
27 * know all input sources ahead of time to find the last line for the $ match.
28 *
29 * process_files() does actual sedding, reading data lines from each input FILE* 26 * process_files() does actual sedding, reading data lines from each input FILE*
30 * (which could be stdin) and applying the sed command list (sed_cmd_head) to 27 * (which could be stdin) and applying the sed command list (sed_cmd_head) to
31 * each of the resulting lines. 28 * each of the resulting lines.
@@ -58,16 +55,27 @@
58 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html 55 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
59 */ 56 */
60 57
58//config:config SED
59//config: bool "sed"
60//config: default y
61//config: help
62//config: sed is used to perform text transformations on a file
63//config: or input from a pipeline.
64
65//kbuild:lib-$(CONFIG_SED) += sed.o
66
67//applet:IF_SED(APPLET(sed, BB_DIR_BIN, BB_SUID_DROP))
68
61//usage:#define sed_trivial_usage 69//usage:#define sed_trivial_usage
62//usage: "[-inr] [-f FILE]... [-e CMD]... [FILE]...\n" 70//usage: "[-inrE] [-f FILE]... [-e CMD]... [FILE]...\n"
63//usage: "or: sed [-inr] CMD [FILE]..." 71//usage: "or: sed [-inrE] CMD [FILE]..."
64//usage:#define sed_full_usage "\n\n" 72//usage:#define sed_full_usage "\n\n"
65//usage: " -e CMD Add CMD to sed commands to be executed" 73//usage: " -e CMD Add CMD to sed commands to be executed"
66//usage: "\n -f FILE Add FILE contents to sed commands to be executed" 74//usage: "\n -f FILE Add FILE contents to sed commands to be executed"
67//usage: "\n -i[SFX] Edit files in-place (otherwise sends to stdout)" 75//usage: "\n -i[SFX] Edit files in-place (otherwise sends to stdout)"
68//usage: "\n Optionally back files up, appending SFX" 76//usage: "\n Optionally back files up, appending SFX"
69//usage: "\n -n Suppress automatic printing of pattern space" 77//usage: "\n -n Suppress automatic printing of pattern space"
70//usage: "\n -r Use extended regex syntax" 78//usage: "\n -r,-E Use extended regex syntax"
71//usage: "\n" 79//usage: "\n"
72//usage: "\nIf no -e or -f, the first non-option argument is the sed command string." 80//usage: "\nIf no -e or -f, the first non-option argument is the sed command string."
73//usage: "\nRemaining arguments are input files (stdin if none)." 81//usage: "\nRemaining arguments are input files (stdin if none)."
@@ -124,12 +132,15 @@ static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v";
124struct globals { 132struct globals {
125 /* options */ 133 /* options */
126 int be_quiet, regex_type; 134 int be_quiet, regex_type;
135
127 FILE *nonstdout; 136 FILE *nonstdout;
128 char *outname, *hold_space; 137 char *outname, *hold_space;
138 smallint exitcode;
129 139
130 /* List of input files */ 140 /* list of input files */
131 int input_file_count, current_input_file; 141 int current_input_file, last_input_file;
132 FILE **input_file_list; 142 char **input_file_list;
143 FILE *current_fp;
133 144
134 regmatch_t regmatch[10]; 145 regmatch_t regmatch[10];
135 regex_t *previous_regex_ptr; 146 regex_t *previous_regex_ptr;
@@ -137,7 +148,7 @@ struct globals {
137 /* linked list of sed commands */ 148 /* linked list of sed commands */
138 sed_cmd_t *sed_cmd_head, **sed_cmd_tail; 149 sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
139 150
140 /* Linked list of append lines */ 151 /* linked list of append lines */
141 llist_t *append_head; 152 llist_t *append_head;
142 153
143 char *add_cmd_line; 154 char *add_cmd_line;
@@ -189,8 +200,8 @@ static void sed_free_and_close_stuff(void)
189 200
190 free(G.hold_space); 201 free(G.hold_space);
191 202
192 while (G.current_input_file < G.input_file_count) 203 if (G.current_fp)
193 fclose(G.input_file_list[G.current_input_file++]); 204 fclose(G.current_fp);
194} 205}
195#else 206#else
196void sed_free_and_close_stuff(void); 207void sed_free_and_close_stuff(void);
@@ -370,7 +381,7 @@ static int parse_subst_cmd(sed_cmd_t *sed_cmd, const char *substr)
370 381
371 /* 382 /*
372 * A substitution command should look something like this: 383 * A substitution command should look something like this:
373 * s/match/replace/ #gIpw 384 * s/match/replace/ #giIpw
374 * || | ||| 385 * || | |||
375 * mandatory optional 386 * mandatory optional
376 */ 387 */
@@ -418,6 +429,7 @@ static int parse_subst_cmd(sed_cmd_t *sed_cmd, const char *substr)
418 break; 429 break;
419 } 430 }
420 /* Ignore case (gnu exension) */ 431 /* Ignore case (gnu exension) */
432 case 'i':
421 case 'I': 433 case 'I':
422 cflags |= REG_ICASE; 434 cflags |= REG_ICASE;
423 break; 435 break;
@@ -848,46 +860,100 @@ static sed_cmd_t *branch_to(char *label)
848 860
849static void append(char *s) 861static void append(char *s)
850{ 862{
851 llist_add_to_end(&G.append_head, xstrdup(s)); 863 llist_add_to_end(&G.append_head, s);
852} 864}
853 865
854static void flush_append(void) 866/* Output line of text. */
867/* Note:
868 * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
869 * Without them, we had this:
870 * echo -n thingy >z1
871 * echo -n again >z2
872 * >znull
873 * sed "s/i/z/" z1 z2 znull | hexdump -vC
874 * output:
875 * gnu sed 4.1.5:
876 * 00000000 74 68 7a 6e 67 79 0a 61 67 61 7a 6e |thzngy.agazn|
877 * bbox:
878 * 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn|
879 */
880enum {
881 NO_EOL_CHAR = 1,
882 LAST_IS_NUL = 2,
883};
884static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
885{
886 char lpc = *last_puts_char;
887
888 /* Need to insert a '\n' between two files because first file's
889 * last line wasn't terminated? */
890 if (lpc != '\n' && lpc != '\0') {
891 fputc('\n', file);
892 lpc = '\n';
893 }
894 fputs(s, file);
895
896 /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
897 if (s[0])
898 lpc = 'x';
899
900 /* had trailing '\0' and it was last char of file? */
901 if (last_gets_char == LAST_IS_NUL) {
902 fputc('\0', file);
903 lpc = 'x'; /* */
904 } else
905 /* had trailing '\n' or '\0'? */
906 if (last_gets_char != NO_EOL_CHAR) {
907 fputc(last_gets_char, file);
908 lpc = last_gets_char;
909 }
910
911 if (ferror(file)) {
912 xfunc_error_retval = 4; /* It's what gnu sed exits with... */
913 bb_error_msg_and_die(bb_msg_write_error);
914 }
915 *last_puts_char = lpc;
916}
917
918static void flush_append(char *last_puts_char, char last_gets_char)
855{ 919{
856 char *data; 920 char *data;
857 921
858 /* Output appended lines. */ 922 /* Output appended lines. */
859 while ((data = (char *)llist_pop(&G.append_head))) { 923 while ((data = (char *)llist_pop(&G.append_head))) {
860 fprintf(G.nonstdout, "%s\n", data); 924 puts_maybe_newline(data, G.nonstdout, last_puts_char, last_gets_char);
861 free(data); 925 free(data);
862 } 926 }
863} 927}
864 928
865static void add_input_file(FILE *file)
866{
867 G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
868 G.input_file_list[G.input_file_count++] = file;
869}
870
871/* Get next line of input from G.input_file_list, flushing append buffer and 929/* Get next line of input from G.input_file_list, flushing append buffer and
872 * noting if we ran out of files without a newline on the last line we read. 930 * noting if we ran out of files without a newline on the last line we read.
873 */ 931 */
874enum { 932static char *get_next_line(char *gets_char, char *last_puts_char, char last_gets_char)
875 NO_EOL_CHAR = 1,
876 LAST_IS_NUL = 2,
877};
878static char *get_next_line(char *gets_char)
879{ 933{
880 char *temp = NULL; 934 char *temp = NULL;
881 int len; 935 int len;
882 char gc; 936 char gc;
883 937
884 flush_append(); 938 flush_append(last_puts_char, last_gets_char);
885 939
886 /* will be returned if last line in the file 940 /* will be returned if last line in the file
887 * doesn't end with either '\n' or '\0' */ 941 * doesn't end with either '\n' or '\0' */
888 gc = NO_EOL_CHAR; 942 gc = NO_EOL_CHAR;
889 while (G.current_input_file < G.input_file_count) { 943 for (; G.current_input_file <= G.last_input_file; G.current_input_file++) {
890 FILE *fp = G.input_file_list[G.current_input_file]; 944 FILE *fp = G.current_fp;
945 if (!fp) {
946 const char *path = G.input_file_list[G.current_input_file];
947 fp = stdin;
948 if (path != bb_msg_standard_input) {
949 fp = fopen_or_warn(path, "r");
950 if (!fp) {
951 G.exitcode = EXIT_FAILURE;
952 continue;
953 }
954 }
955 G.current_fp = fp;
956 }
891 /* Read line up to a newline or NUL byte, inclusive, 957 /* Read line up to a newline or NUL byte, inclusive,
892 * return malloc'ed char[]. length of the chunk read 958 * return malloc'ed char[]. length of the chunk read
893 * is stored in len. NULL if EOF/error */ 959 * is stored in len. NULL if EOF/error */
@@ -918,61 +984,13 @@ static char *get_next_line(char *gets_char)
918 * (note: *no* newline after "b bang"!) */ 984 * (note: *no* newline after "b bang"!) */
919 } 985 }
920 /* Close this file and advance to next one */ 986 /* Close this file and advance to next one */
921 fclose(fp); 987 fclose_if_not_stdin(fp);
922 G.current_input_file++; 988 G.current_fp = NULL;
923 } 989 }
924 *gets_char = gc; 990 *gets_char = gc;
925 return temp; 991 return temp;
926} 992}
927 993
928/* Output line of text. */
929/* Note:
930 * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
931 * Without them, we had this:
932 * echo -n thingy >z1
933 * echo -n again >z2
934 * >znull
935 * sed "s/i/z/" z1 z2 znull | hexdump -vC
936 * output:
937 * gnu sed 4.1.5:
938 * 00000000 74 68 7a 6e 67 79 0a 61 67 61 7a 6e |thzngy.agazn|
939 * bbox:
940 * 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn|
941 */
942static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
943{
944 char lpc = *last_puts_char;
945
946 /* Need to insert a '\n' between two files because first file's
947 * last line wasn't terminated? */
948 if (lpc != '\n' && lpc != '\0') {
949 fputc('\n', file);
950 lpc = '\n';
951 }
952 fputs(s, file);
953
954 /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
955 if (s[0])
956 lpc = 'x';
957
958 /* had trailing '\0' and it was last char of file? */
959 if (last_gets_char == LAST_IS_NUL) {
960 fputc('\0', file);
961 lpc = 'x'; /* */
962 } else
963 /* had trailing '\n' or '\0'? */
964 if (last_gets_char != NO_EOL_CHAR) {
965 fputc(last_gets_char, file);
966 lpc = last_gets_char;
967 }
968
969 if (ferror(file)) {
970 xfunc_error_retval = 4; /* It's what gnu sed exits with... */
971 bb_error_msg_and_die(bb_msg_write_error);
972 }
973 *last_puts_char = lpc;
974}
975
976#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n)) 994#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n))
977 995
978static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space) 996static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space)
@@ -995,7 +1013,7 @@ static void process_files(void)
995 int substituted; 1013 int substituted;
996 1014
997 /* Prime the pump */ 1015 /* Prime the pump */
998 next_line = get_next_line(&next_gets_char); 1016 next_line = get_next_line(&next_gets_char, &last_puts_char, '\n' /*last_gets_char*/);
999 1017
1000 /* Go through every line in each file */ 1018 /* Go through every line in each file */
1001 again: 1019 again:
@@ -1009,7 +1027,7 @@ static void process_files(void)
1009 1027
1010 /* Read one line in advance so we can act on the last line, 1028 /* Read one line in advance so we can act on the last line,
1011 * the '$' address */ 1029 * the '$' address */
1012 next_line = get_next_line(&next_gets_char); 1030 next_line = get_next_line(&next_gets_char, &last_puts_char, last_gets_char);
1013 linenum++; 1031 linenum++;
1014 1032
1015 /* For every line, go through all the commands */ 1033 /* For every line, go through all the commands */
@@ -1181,7 +1199,7 @@ static void process_files(void)
1181 1199
1182 /* Append line to linked list to be printed later */ 1200 /* Append line to linked list to be printed later */
1183 case 'a': 1201 case 'a':
1184 append(sed_cmd->string); 1202 append(xstrdup(sed_cmd->string));
1185 break; 1203 break;
1186 1204
1187 /* Insert text before this line */ 1205 /* Insert text before this line */
@@ -1203,11 +1221,10 @@ static void process_files(void)
1203 rfile = fopen_for_read(sed_cmd->string); 1221 rfile = fopen_for_read(sed_cmd->string);
1204 if (rfile) { 1222 if (rfile) {
1205 char *line; 1223 char *line;
1206
1207 while ((line = xmalloc_fgetline(rfile)) 1224 while ((line = xmalloc_fgetline(rfile))
1208 != NULL) 1225 != NULL)
1209 append(line); 1226 append(line);
1210 xprint_and_close_file(rfile); 1227 fclose(rfile);
1211 } 1228 }
1212 1229
1213 break; 1230 break;
@@ -1228,7 +1245,7 @@ static void process_files(void)
1228 free(pattern_space); 1245 free(pattern_space);
1229 pattern_space = next_line; 1246 pattern_space = next_line;
1230 last_gets_char = next_gets_char; 1247 last_gets_char = next_gets_char;
1231 next_line = get_next_line(&next_gets_char); 1248 next_line = get_next_line(&next_gets_char, &last_puts_char, last_gets_char);
1232 substituted = 0; 1249 substituted = 0;
1233 linenum++; 1250 linenum++;
1234 break; 1251 break;
@@ -1264,7 +1281,7 @@ static void process_files(void)
1264 pattern_space[len] = '\n'; 1281 pattern_space[len] = '\n';
1265 strcpy(pattern_space + len+1, next_line); 1282 strcpy(pattern_space + len+1, next_line);
1266 last_gets_char = next_gets_char; 1283 last_gets_char = next_gets_char;
1267 next_line = get_next_line(&next_gets_char); 1284 next_line = get_next_line(&next_gets_char, &last_puts_char, last_gets_char);
1268 linenum++; 1285 linenum++;
1269 break; 1286 break;
1270 } 1287 }
@@ -1368,7 +1385,7 @@ static void process_files(void)
1368 1385
1369 /* Delete and such jump here. */ 1386 /* Delete and such jump here. */
1370 discard_line: 1387 discard_line:
1371 flush_append(); 1388 flush_append(&last_puts_char, last_gets_char);
1372 free(pattern_space); 1389 free(pattern_space);
1373 1390
1374 goto again; 1391 goto again;
@@ -1413,8 +1430,6 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
1413 "file\0" Required_argument "f"; 1430 "file\0" Required_argument "f";
1414#endif 1431#endif
1415 1432
1416 int status = EXIT_SUCCESS;
1417
1418 INIT_G(); 1433 INIT_G();
1419 1434
1420 /* destroy command strings on exit */ 1435 /* destroy command strings on exit */
@@ -1435,15 +1450,21 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
1435 IF_LONG_OPTS(applet_long_options = sed_longopts); 1450 IF_LONG_OPTS(applet_long_options = sed_longopts);
1436 1451
1437 /* -i must be first, to match OPT_in_place definition */ 1452 /* -i must be first, to match OPT_in_place definition */
1438 opt = getopt32(argv, "i::rne:f:", &opt_i, &opt_e, &opt_f, 1453 /* -E is a synonym of -r:
1454 * GNU sed 4.2.1 mentions it in neither --help
1455 * nor manpage, but does recognize it.
1456 */
1457 opt = getopt32(argv, "i::rEne:f:", &opt_i, &opt_e, &opt_f,
1439 &G.be_quiet); /* counter for -n */ 1458 &G.be_quiet); /* counter for -n */
1440 //argc -= optind; 1459 //argc -= optind;
1441 argv += optind; 1460 argv += optind;
1442 if (opt & OPT_in_place) { // -i 1461 if (opt & OPT_in_place) { // -i
1443 atexit(cleanup_outname); 1462 atexit(cleanup_outname);
1444 } 1463 }
1445 if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r 1464 if (opt & (2|4))
1446 //if (opt & 0x4) G.be_quiet++; // -n 1465 G.regex_type |= REG_EXTENDED; // -r or -E
1466 //if (opt & 8)
1467 // G.be_quiet++; // -n (implemented with a counter instead)
1447 while (opt_e) { // -e 1468 while (opt_e) { // -e
1448 add_cmd_block(llist_pop(&opt_e)); 1469 add_cmd_block(llist_pop(&opt_e));
1449 } 1470 }
@@ -1458,7 +1479,7 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
1458 fclose(cmdfile); 1479 fclose(cmdfile);
1459 } 1480 }
1460 /* if we didn't get a pattern from -e or -f, use argv[0] */ 1481 /* if we didn't get a pattern from -e or -f, use argv[0] */
1461 if (!(opt & 0x18)) { 1482 if (!(opt & 0x30)) {
1462 if (!*argv) 1483 if (!*argv)
1463 bb_show_usage(); 1484 bb_show_usage();
1464 add_cmd_block(*argv++); 1485 add_cmd_block(*argv++);
@@ -1472,42 +1493,38 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
1472 /* argv[0..(argc-1)] should be names of file to process. If no 1493 /* argv[0..(argc-1)] should be names of file to process. If no
1473 * files were specified or '-' was specified, take input from stdin. 1494 * files were specified or '-' was specified, take input from stdin.
1474 * Otherwise, we process all the files specified. */ 1495 * Otherwise, we process all the files specified. */
1475 if (argv[0] == NULL) { 1496 G.input_file_list = argv;
1497 if (!argv[0]) {
1476 if (opt & OPT_in_place) 1498 if (opt & OPT_in_place)
1477 bb_error_msg_and_die(bb_msg_requires_arg, "-i"); 1499 bb_error_msg_and_die(bb_msg_requires_arg, "-i");
1478 add_input_file(stdin); 1500 argv[0] = (char*)bb_msg_standard_input;
1501 /* G.last_input_file = 0; - already is */
1479 } else { 1502 } else {
1480 int i; 1503 goto start;
1481 1504
1482 for (i = 0; argv[i]; i++) { 1505 for (; *argv; argv++) {
1483 struct stat statbuf; 1506 struct stat statbuf;
1484 int nonstdoutfd; 1507 int nonstdoutfd;
1485 FILE *file;
1486 sed_cmd_t *sed_cmd; 1508 sed_cmd_t *sed_cmd;
1487 1509
1488 if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) { 1510 G.last_input_file++;
1489 add_input_file(stdin); 1511 start:
1490 process_files();
1491 continue;
1492 }
1493 file = fopen_or_warn(argv[i], "r");
1494 if (!file) {
1495 status = EXIT_FAILURE;
1496 continue;
1497 }
1498 add_input_file(file);
1499 if (!(opt & OPT_in_place)) { 1512 if (!(opt & OPT_in_place)) {
1513 if (LONE_DASH(*argv)) {
1514 *argv = (char*)bb_msg_standard_input;
1515 process_files();
1516 }
1500 continue; 1517 continue;
1501 } 1518 }
1502 1519
1503 /* -i: process each FILE separately: */ 1520 /* -i: process each FILE separately: */
1504 1521
1505 G.outname = xasprintf("%sXXXXXX", argv[i]); 1522 G.outname = xasprintf("%sXXXXXX", *argv);
1506 nonstdoutfd = xmkstemp(G.outname); 1523 nonstdoutfd = xmkstemp(G.outname);
1507 G.nonstdout = xfdopen_for_write(nonstdoutfd); 1524 G.nonstdout = xfdopen_for_write(nonstdoutfd);
1508 1525
1509 /* Set permissions/owner of output file */ 1526 /* Set permissions/owner of output file */
1510 fstat(fileno(file), &statbuf); 1527 stat(*argv, &statbuf);
1511 /* chmod'ing AFTER chown would preserve suid/sgid bits, 1528 /* chmod'ing AFTER chown would preserve suid/sgid bits,
1512 * but GNU sed 4.2.1 does not preserve them either */ 1529 * but GNU sed 4.2.1 does not preserve them either */
1513 fchmod(nonstdoutfd, statbuf.st_mode); 1530 fchmod(nonstdoutfd, statbuf.st_mode);
@@ -1518,12 +1535,12 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
1518 G.nonstdout = stdout; 1535 G.nonstdout = stdout;
1519 1536
1520 if (opt_i) { 1537 if (opt_i) {
1521 char *backupname = xasprintf("%s%s", argv[i], opt_i); 1538 char *backupname = xasprintf("%s%s", *argv, opt_i);
1522 xrename(argv[i], backupname); 1539 xrename(*argv, backupname);
1523 free(backupname); 1540 free(backupname);
1524 } 1541 }
1525 /* else unlink(argv[i]); - rename below does this */ 1542 /* else unlink(*argv); - rename below does this */
1526 xrename(G.outname, argv[i]); //TODO: rollback backup on error? 1543 xrename(G.outname, *argv); //TODO: rollback backup on error?
1527 free(G.outname); 1544 free(G.outname);
1528 G.outname = NULL; 1545 G.outname = NULL;
1529 1546
@@ -1533,12 +1550,13 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
1533 } 1550 }
1534 } 1551 }
1535 /* Here, to handle "sed 'cmds' nonexistent_file" case we did: 1552 /* Here, to handle "sed 'cmds' nonexistent_file" case we did:
1536 * if (G.current_input_file >= G.input_file_count) 1553 * if (G.current_input_file[G.current_input_file] == NULL)
1537 * return status; 1554 * return G.exitcode;
1538 * but it's not needed since process_files() works correctly 1555 * but it's not needed since process_files() works correctly
1539 * in this case too. */ 1556 * in this case too. */
1540 } 1557 }
1558
1541 process_files(); 1559 process_files();
1542 1560
1543 return status; 1561 return G.exitcode;
1544} 1562}