aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2024-12-30 14:45:15 +0000
committerRon Yorston <rmy@pobox.com>2024-12-30 14:45:15 +0000
commit370ce52cca8e7e8a341d5e238a79fd67f6048bb6 (patch)
tree48c78d906a4467bb618946d96574a5bc21cacfba
parentdc3ee8e0523a4f651f3012ae1f5a181548187709 (diff)
parent14f57f5357cb674b88e7cdaff6267bf9d84c6b80 (diff)
downloadbusybox-w32-370ce52cca8e7e8a341d5e238a79fd67f6048bb6.tar.gz
busybox-w32-370ce52cca8e7e8a341d5e238a79fd67f6048bb6.tar.bz2
busybox-w32-370ce52cca8e7e8a341d5e238a79fd67f6048bb6.zip
Merge branch 'busybox' into mergemerge
-rw-r--r--coreutils/cut.c455
-rw-r--r--libbb/dump.c6
-rw-r--r--libbb/getopt32.c17
-rw-r--r--runit/chpst.c3
-rw-r--r--shell/hush.c3
-rwxr-xr-xtestsuite/cut.tests140
-rwxr-xr-xtestsuite/hexdump.tests6
7 files changed, 422 insertions, 208 deletions
diff --git a/coreutils/cut.c b/coreutils/cut.c
index f7b501a46..d81f36bcd 100644
--- a/coreutils/cut.c
+++ b/coreutils/cut.c
@@ -27,21 +27,34 @@
27//kbuild:lib-$(CONFIG_CUT) += cut.o 27//kbuild:lib-$(CONFIG_CUT) += cut.o
28 28
29//usage:#define cut_trivial_usage 29//usage:#define cut_trivial_usage
30//usage: "[OPTIONS] [FILE]..." 30//usage: "{-b|c LIST | -f"IF_FEATURE_CUT_REGEX("|F")" LIST [-d SEP] [-s]} [-D] [-O SEP] [FILE]..."
31// --output-delimiter SEP is too long to fit into 80 char-wide help ----------------^^^^^^^^
31//usage:#define cut_full_usage "\n\n" 32//usage:#define cut_full_usage "\n\n"
32//usage: "Print selected fields from FILEs to stdout\n" 33//usage: "Print selected fields from FILEs to stdout\n"
33//usage: "\n -b LIST Output only bytes from LIST" 34//usage: "\n -b LIST Output only bytes from LIST"
34//usage: "\n -c LIST Output only characters from LIST" 35//usage: "\n -c LIST Output only characters from LIST"
35//usage: "\n -d SEP Field delimiter for input (default -f TAB, -F run of whitespace)" 36//usage: IF_FEATURE_CUT_REGEX(
36//usage: "\n -O SEP Field delimeter for output (default = -d for -f, one space for -F)" 37//usage: "\n -d SEP Input field delimiter (default -f TAB, -F run of whitespace)"
37//usage: "\n -D Don't sort/collate sections or match -fF lines without delimeter" 38//usage: ) IF_NOT_FEATURE_CUT_REGEX(
39//usage: "\n -d SEP Input field delimiter (default TAB)"
40//usage: )
38//usage: "\n -f LIST Print only these fields (-d is single char)" 41//usage: "\n -f LIST Print only these fields (-d is single char)"
39//usage: IF_FEATURE_CUT_REGEX( 42//usage: IF_FEATURE_CUT_REGEX(
40//usage: "\n -F LIST Print only these fields (-d is regex)" 43//usage: "\n -F LIST Print only these fields (-d is regex)"
41//usage: ) 44//usage: )
42//usage: "\n -s Output only lines containing delimiter" 45//usage: "\n -s Drop lines with no delimiter (else print them in full)"
46//usage: "\n -D Don't sort ranges; line without delimiters has one field"
47//usage: IF_LONG_OPTS(
48//usage: "\n --output-delimiter SEP Output field delimeter"
49//usage: ) IF_NOT_LONG_OPTS(
50//usage: IF_FEATURE_CUT_REGEX(
51//usage: "\n -O SEP Output field delimeter (default = -d for -f, one space for -F)"
52//usage: ) IF_NOT_FEATURE_CUT_REGEX(
53//usage: "\n -O SEP Output field delimeter (default = -d)"
54//usage: )
55//usage: )
43//usage: "\n -n Ignored" 56//usage: "\n -n Ignored"
44//(manpage:-n with -b: don't split multibyte characters) 57//(manpage:-n with -b: don't split multibyte characters)
45//usage: 58//usage:
46//usage:#define cut_example_usage 59//usage:#define cut_example_usage
47//usage: "$ echo \"Hello world\" | cut -f 1 -d ' '\n" 60//usage: "$ echo \"Hello world\" | cut -f 1 -d ' '\n"
@@ -53,11 +66,6 @@
53 66
54#if ENABLE_FEATURE_CUT_REGEX 67#if ENABLE_FEATURE_CUT_REGEX
55#include "xregex.h" 68#include "xregex.h"
56#else
57#define regex_t int
58typedef struct { int rm_eo, rm_so; } regmatch_t;
59#define xregcomp(x, ...) *(x) = 0
60#define regexec(...) 0
61#endif 69#endif
62 70
63/* This is a NOEXEC applet. Be very careful! */ 71/* This is a NOEXEC applet. Be very careful! */
@@ -65,265 +73,346 @@ typedef struct { int rm_eo, rm_so; } regmatch_t;
65 73
66/* option vars */ 74/* option vars */
67#define OPT_STR "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n" 75#define OPT_STR "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n"
68#define CUT_OPT_BYTE_FLGS (1 << 0) 76#define OPT_BYTE (1 << 0)
69#define CUT_OPT_CHAR_FLGS (1 << 1) 77#define OPT_CHAR (1 << 1)
70#define CUT_OPT_FIELDS_FLGS (1 << 2) 78#define OPT_FIELDS (1 << 2)
71#define CUT_OPT_DELIM_FLGS (1 << 3) 79#define OPT_DELIM (1 << 3)
72#define CUT_OPT_ODELIM_FLGS (1 << 4) 80#define OPT_ODELIM (1 << 4)
73#define CUT_OPT_SUPPRESS_FLGS (1 << 5) 81#define OPT_SUPPRESS (1 << 5)
74#define CUT_OPT_NOSORT_FLGS (1 << 6) 82#define OPT_NOSORT (1 << 6)
75#define CUT_OPT_REGEX_FLGS ((1 << 7) * ENABLE_FEATURE_CUT_REGEX) 83#define OPT_REGEX ((1 << 7) * ENABLE_FEATURE_CUT_REGEX)
76 84
77struct cut_list { 85#define opt_REGEX (option_mask32 & OPT_REGEX)
78 int startpos; 86
79 int endpos; 87struct cut_range {
88 unsigned startpos;
89 unsigned endpos;
80}; 90};
81 91
82static int cmpfunc(const void *a, const void *b) 92static int cmpfunc(const void *a, const void *b)
83{ 93{
84 return (((struct cut_list *) a)->startpos - 94 const struct cut_range *aa = a;
85 ((struct cut_list *) b)->startpos); 95 const struct cut_range *bb = b;
96 return aa->startpos - bb->startpos;
86} 97}
87 98
99#define END_OF_LIST(list_elem) ((list_elem).startpos == UINT_MAX)
100#define NOT_END_OF_LIST(list_elem) ((list_elem).startpos != UINT_MAX)
101
88static void cut_file(FILE *file, const char *delim, const char *odelim, 102static void cut_file(FILE *file, const char *delim, const char *odelim,
89 const struct cut_list *cut_lists, unsigned nlists) 103 const struct cut_range *cut_list)
90{ 104{
91 char *line; 105 char *line;
92 unsigned linenum = 0; /* keep these zero-based to be consistent */ 106 unsigned linenum = 0; /* keep these zero-based to be consistent */
93 regex_t reg; 107 int first_print = 1;
94 int spos, shoe = option_mask32 & CUT_OPT_REGEX_FLGS;
95
96 if (shoe) xregcomp(&reg, delim, REG_EXTENDED);
97 108
98 /* go through every line in the file */ 109 /* go through every line in the file */
99 while ((line = xmalloc_fgetline(file)) != NULL) { 110 while ((line = xmalloc_fgetline(file)) != NULL) {
100 111
101 /* set up a list so we can keep track of what's been printed */ 112 /* set up a list so we can keep track of what's been printed */
102 int linelen = strlen(line); 113 unsigned linelen = strlen(line);
103 char *printed = xzalloc(linelen + 1);
104 char *orig_line = line;
105 unsigned cl_pos = 0; 114 unsigned cl_pos = 0;
106 115
107 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */ 116 /* Cut based on chars/bytes XXX: only works when sizeof(char) == byte */
108 if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) { 117 if (option_mask32 & (OPT_CHAR | OPT_BYTE)) {
118 char *printed = xzalloc(linelen + 1);
119 int need_odelim = 0;
120
109 /* print the chars specified in each cut list */ 121 /* print the chars specified in each cut list */
110 for (; cl_pos < nlists; cl_pos++) { 122 for (; NOT_END_OF_LIST(cut_list[cl_pos]); cl_pos++) {
111 for (spos = cut_lists[cl_pos].startpos; spos < linelen;) { 123 unsigned spos = cut_list[cl_pos].startpos;
124 while (spos < linelen) {
112 if (!printed[spos]) { 125 if (!printed[spos]) {
113 printed[spos] = 'X'; 126 printed[spos] = 'X';
127 if (need_odelim && spos != 0 && !printed[spos-1]) {
128 need_odelim = 0;
129 fputs_stdout(odelim);
130 }
114 putchar(line[spos]); 131 putchar(line[spos]);
115 } 132 }
116 if (++spos > cut_lists[cl_pos].endpos) { 133 spos++;
134 if (spos > cut_list[cl_pos].endpos) {
135 /* will print OSEP (if not empty) */
136 need_odelim = (odelim && odelim[0]);
117 break; 137 break;
118 } 138 }
119 } 139 }
120 } 140 }
121 } else if (*delim == '\n') { /* cut by lines */ 141 free(printed);
122 spos = cut_lists[cl_pos].startpos; 142 /* Cut by lines */
143 } else if (!opt_REGEX && *delim == '\n') {
144 unsigned spos = cut_list[cl_pos].startpos;
123 145
124 /* get out if we have no more lists to process or if the lines 146 linenum++;
147 /* get out if we have no more ranges to process or if the lines
125 * are lower than what we're interested in */ 148 * are lower than what we're interested in */
126 if (((int)linenum < spos) || (cl_pos >= nlists)) 149 if (linenum <= spos || END_OF_LIST(cut_list[cl_pos]))
127 goto next_line; 150 goto next_line;
128 151
129 /* if the line we're looking for is lower than the one we were 152 /* if the line we're looking for is lower than the one we were
130 * passed, it means we displayed it already, so move on */ 153 * passed, it means we displayed it already, so move on */
131 while (spos < (int)linenum) { 154 while (++spos < linenum) {
132 spos++;
133 /* go to the next list if we're at the end of this one */ 155 /* go to the next list if we're at the end of this one */
134 if (spos > cut_lists[cl_pos].endpos) { 156 if (spos > cut_list[cl_pos].endpos) {
135 cl_pos++; 157 cl_pos++;
136 /* get out if there's no more lists to process */ 158 /* get out if there's no more ranges to process */
137 if (cl_pos >= nlists) 159 if (END_OF_LIST(cut_list[cl_pos]))
138 goto next_line; 160 goto next_line;
139 spos = cut_lists[cl_pos].startpos; 161 spos = cut_list[cl_pos].startpos;
140 /* get out if the current line is lower than the one 162 /* get out if the current line is lower than the one
141 * we just became interested in */ 163 * we just became interested in */
142 if ((int)linenum < spos) 164 if (linenum <= spos)
143 goto next_line; 165 goto next_line;
144 } 166 }
145 } 167 }
146 168
147 /* If we made it here, it means we've found the line we're 169 /* If we made it here, it means we've found the line we're
148 * looking for, so print it */ 170 * looking for, so print it */
149 puts(line); 171 if (first_print) {
172 first_print = 0;
173 fputs_stdout(line);
174 } else
175 printf("%s%s", odelim, line);
150 goto next_line; 176 goto next_line;
151 } else { /* cut by fields */ 177 /* Cut by fields */
152 unsigned uu = 0, start = 0, end = 0, out = 0; 178 } else {
153 int dcount = 0; 179 unsigned next = 0, start = 0, end = 0;
180 unsigned dcount = 0; /* we saw Nth delimiter (0 - didn't see any yet) */
181
182 /* Blank line? Check -s (later check for -s does not catch empty lines) */
183 if (linelen == 0) {
184 if (option_mask32 & OPT_SUPPRESS)
185 goto next_line;
186 }
187
188 if (!odelim)
189 odelim = "\t";
190 first_print = 1;
154 191
155#if ENABLE_PLATFORM_MINGW32
156 /* An empty line can't contain a delimiter */
157 if (linelen == 0 && (option_mask32 & CUT_OPT_SUPPRESS_FLGS))
158 goto next_line;
159#endif
160 /* Loop through bytes, finding next delimiter */ 192 /* Loop through bytes, finding next delimiter */
161 for (;;) { 193 for (;;) {
162 /* End of current range? */ 194 /* End of current range? */
163 if (end == linelen || dcount > cut_lists[cl_pos].endpos) { 195 if (end == linelen || dcount > cut_list[cl_pos].endpos) {
164 if (++cl_pos >= nlists) break; 196 end_of_range:
165 if (option_mask32 & CUT_OPT_NOSORT_FLGS) 197 cl_pos++;
166 start = dcount = uu = 0; 198 if (END_OF_LIST(cut_list[cl_pos]))
167 end = 0; 199 break;
200 if (option_mask32 & OPT_NOSORT)
201 start = dcount = next = 0;
202 end = 0; /* (why?) */
203 //bb_error_msg("End of current range");
168 } 204 }
169 /* End of current line? */ 205 /* End of current line? */
170 if (uu == linelen) { 206 if (next == linelen) {
171 /* If we've seen no delimiters, check -s */ 207 end = linelen; /* print up to end */
172 if (!cl_pos && !dcount && !shoe) { 208 /* If we've seen no delimiters, and no -D, check -s */
173 if (option_mask32 & CUT_OPT_SUPPRESS_FLGS) 209 if (!(option_mask32 & OPT_NOSORT) && cl_pos == 0 && dcount == 0) {
210 if (option_mask32 & OPT_SUPPRESS)
174 goto next_line; 211 goto next_line;
175 } else if (dcount < cut_lists[cl_pos].startpos) 212 /* else: will print entire line */
176 start = linelen; 213 } else if (dcount < cut_list[cl_pos].startpos) {
177 end = linelen; 214 /* echo 1.2 | cut -d. -f1,3: prints "1", not "1." */
215 //break;
216 /* ^^^ this fails a case with -D:
217 * echo 1 2 | cut -DF 1,3,2:
218 * do not end line processing when didn't find field#3
219 */
220 //if (option_mask32 & OPT_NOSORT) - no, just do it always
221 goto end_of_range;
222 }
223 //bb_error_msg("End of current line: s:%d e:%d", start, end);
178 } else { 224 } else {
179 /* Find next delimiter */ 225 /* Find next delimiter */
180 if (shoe) { 226#if ENABLE_FEATURE_CUT_REGEX
181 regmatch_t rr = {-1, -1}; 227 if (opt_REGEX) {
182 228 regmatch_t rr;
183 if (!regexec(&reg, line+uu, 1, &rr, REG_NOTBOL|REG_NOTEOL)) { 229 regex_t *reg = (void*) delim;
184 end = uu + rr.rm_so; 230
185 uu += rr.rm_eo; 231 if (regexec(reg, line + next, 1, &rr, REG_NOTBOL|REG_NOTEOL) != 0) {
186 } else { 232 /* not found, go to "end of line" logic */
187 uu = linelen; 233 next = linelen;
188 continue; 234 continue;
189 } 235 }
190 } else if (line[end = uu++] != *delim) 236 end = next + rr.rm_so;
191 continue; 237 next += (rr.rm_eo ? rr.rm_eo : 1);
192 238 /* ^^^ advancing by at least 1 prevents infinite loops */
193 /* Got delimiter. Loop if not yet within range. */ 239 /* testcase: echo "no at sign" | cut -d'@*' -F 1- */
194 if (dcount++ < cut_lists[cl_pos].startpos) { 240 } else
195 start = uu; 241#endif
242 {
243 end = next++;
244 if (line[end] != *delim)
245 continue;
246 }
247 /* Got delimiter */
248 dcount++;
249 if (dcount <= cut_list[cl_pos].startpos) {
250 /* Not yet within range - loop */
251 start = next;
196 continue; 252 continue;
197 } 253 }
254 /* -F N-M preserves intermediate delimiters: */
255 //printf "1 2 3 4 5 6 7\n" | toybox cut -O: -F2,4-6,7
256 //2:4 5 6:7
257 if (opt_REGEX && dcount <= cut_list[cl_pos].endpos)
258 continue;
259// NB: toybox does the above for -f too, but it's a compatibility bug:
260//printf "1 2 3 4 5 6 7 8\n" | toybox cut -d' ' -O: -f2,4-6,7
261//2:4 5 6:7 // WRONG!
262//printf "1 2 3 4 5 6 7 8\n" | cut -d' ' --output-delimiter=: -f2,4-6,7
263//2:4:5:6:7 // GNU coreutils 9.1
198 } 264 }
199 if (end != start || !shoe) 265#if ENABLE_FEATURE_CUT_REGEX
200 printf("%s%.*s", out++ ? odelim : "", end-start, line + start); 266 if (end != start || !opt_REGEX)
201 start = uu; 267#endif
202 if (!dcount) 268 {
203 break; 269 if (first_print) {
204 } 270 first_print = 0;
271 printf("%.*s", end - start, line + start);
272 } else
273 printf("%s%.*s", odelim, end - start, line + start);
274 }
275 start = next;
276 //if (dcount == 0)
277 // break; - why?
278 } /* byte loop */
205 } 279 }
206 /* if we printed anything, finish with newline */ 280 /* if we printed anything, finish with newline */
207 putchar('\n'); 281 putchar('\n');
208 next_line: 282 next_line:
209 linenum++; 283 free(line);
210 free(printed); 284 } /* while (got line) */
211 free(orig_line); 285
212 } 286 /* For -d$'\n' --output-delimiter=^, the overall output is still terminated with \n, not ^ */
287 if (!opt_REGEX && *delim == '\n' && !first_print)
288 putchar('\n');
213} 289}
214 290
215int cut_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 291int cut_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
216int cut_main(int argc UNUSED_PARAM, char **argv) 292int cut_main(int argc UNUSED_PARAM, char **argv)
217{ 293{
218 /* growable array holding a series of lists */ 294 /* growable array holding a series of ranges */
219 struct cut_list *cut_lists = NULL; 295 struct cut_range *cut_list = NULL;
220 unsigned nlists = 0; /* number of elements in above list */ 296 unsigned nranges = 0; /* number of elements in above list */
221 char *sopt, *ltok; 297 char *LIST, *ltok;
222 const char *delim = NULL; 298 const char *delim = NULL;
223 const char *odelim = NULL; 299 const char *odelim = NULL;
224 unsigned opt; 300 unsigned opt;
301#if ENABLE_FEATURE_CUT_REGEX
302 regex_t reg;
303#endif
304#if ENABLE_LONG_OPTS
305 static const char cut_longopts[] ALIGN1 =
306 "output-delimiter\0" Required_argument "O"
307 ;
308#endif
225 309
226#define ARG "bcf"IF_FEATURE_CUT_REGEX("F") 310#define ARG "bcf"IF_FEATURE_CUT_REGEX("F")
227 opt = getopt32(argv, "^" 311#if ENABLE_LONG_OPTS
312 opt = getopt32long
313#else
314 opt = getopt32
315#endif
316 (argv, "^"
228 OPT_STR // = "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n" 317 OPT_STR // = "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n"
229 "\0" "b--"ARG":c--"ARG":f--"ARG IF_FEATURE_CUT_REGEX("F--"ARG), 318 "\0" "b:c:f:" IF_FEATURE_CUT_REGEX("F:") /* one of -bcfF is required */
230 &sopt, &sopt, &sopt, &delim, &odelim IF_FEATURE_CUT_REGEX(, &sopt) 319 "b--"ARG":c--"ARG":f--"ARG IF_FEATURE_CUT_REGEX(":F--"ARG), /* they are mutually exclusive */
231 ); 320 IF_LONG_OPTS(cut_longopts,)
232 if (!delim || !*delim) 321 &LIST, &LIST, &LIST, &delim, &odelim IF_FEATURE_CUT_REGEX(, &LIST)
233 delim = (opt & CUT_OPT_REGEX_FLGS) ? "[[:space:]]+" : "\t"; 322 );
234 if (!odelim) odelim = (opt & CUT_OPT_REGEX_FLGS) ? " " : delim; 323 if (!odelim)
324 odelim = (opt & OPT_REGEX) ? " " : delim;
325 if (!delim)
326 delim = (opt & OPT_REGEX) ? "[[:space:]]+" : "\t";
235 327
236// argc -= optind; 328// argc -= optind;
237 argv += optind; 329 argv += optind;
238 if (!(opt & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS | CUT_OPT_REGEX_FLGS))) 330 //if (!(opt & (OPT_BYTE | OPT_CHAR | OPT_FIELDS | OPT_REGEX)))
239 bb_simple_error_msg_and_die("expected a list of bytes, characters, or fields"); 331 // bb_simple_error_msg_and_die("expected a list of bytes, characters, or fields");
240 332 //^^^ handled by getopt32
241 /* non-field (char or byte) cutting has some special handling */ 333
242 if (!(opt & (CUT_OPT_FIELDS_FLGS|CUT_OPT_REGEX_FLGS))) { 334 /* non-field (char or byte) cutting has some special handling */
243 static const char _op_on_field[] ALIGN1 = " only when operating on fields"; 335 if (!(opt & (OPT_FIELDS|OPT_REGEX))) {
244 336 static const char requires_f[] ALIGN1 = " requires -f"
245 if (opt & CUT_OPT_SUPPRESS_FLGS) { 337 IF_FEATURE_CUT_REGEX(" or -F");
246 bb_error_msg_and_die 338 if (opt & OPT_SUPPRESS)
247 ("suppressing non-delimited lines makes sense%s", _op_on_field); 339 bb_error_msg_and_die("-s%s", requires_f);
248 } 340 if (opt & OPT_DELIM)
249 if (opt & CUT_OPT_DELIM_FLGS) { 341 bb_error_msg_and_die("-d DELIM%s", requires_f);
250 bb_error_msg_and_die
251 ("a delimiter may be specified%s", _op_on_field);
252 }
253 } 342 }
254 343
255 /* 344 /*
256 * parse list and put values into startpos and endpos. 345 * parse list and put values into startpos and endpos.
257 * valid list formats: N, N-, N-M, -M 346 * valid range formats: N, N-, N-M, -M
258 * more than one list can be separated by commas 347 * more than one range can be separated by commas
259 */ 348 */
260 { 349 /* take apart the ranges, one by one (separated with commas) */
350 while ((ltok = strsep(&LIST, ",")) != NULL) {
261 char *ntok; 351 char *ntok;
262 int s = 0, e = 0; 352 int s, e;
263 353
264 /* take apart the lists, one by one (they are separated with commas) */ 354 /* it's actually legal to pass an empty list */
265 while ((ltok = strsep(&sopt, ",")) != NULL) { 355 //if (!ltok[0])
356 // continue;
357 //^^^ testcase?
358
359 /* get the start pos */
360 ntok = strsep(&ltok, "-");
361 if (!ntok[0]) {
362 if (!ltok) /* testcase: -f '' */
363 bb_show_usage();
364 if (!ltok[0]) /* testcase: -f - */
365 bb_show_usage();
366 s = 0; /* "-M" means "1-M" */
367 } else {
368 /* "N" or "N-[M]" */
369 /* arrays are zero based, while the user expects
370 * the first field/char on the line to be char #1 */
371 s = xatoi_positive(ntok) - 1;
372 }
266 373
267 /* it's actually legal to pass an empty list */ 374 /* get the end pos */
268 if (!ltok[0]) 375 if (!ltok) {
269 continue; 376 e = s; /* "N" means "N-N" */
377 } else if (!ltok[0]) {
378 /* "N-" means "until the end of the line" */
379 e = INT_MAX;
380 } else {
381 /* again, arrays are zero based, fields are 1 based */
382 e = xatoi_positive(ltok) - 1;
383 }
270 384
271 /* get the start pos */ 385 if (s < 0 || e < s)
272 ntok = strsep(&ltok, "-"); 386 bb_error_msg_and_die("invalid range %s-%s", ntok, ltok ?: ntok);
273 if (!ntok[0]) {
274 s = 0;
275 } else {
276 s = xatoi_positive(ntok);
277 /* account for the fact that arrays are zero based, while
278 * the user expects the first char on the line to be char #1 */
279#if !ENABLE_PLATFORM_MINGW32
280 if (s != 0)
281 s--;
282#else
283 s--;
284#endif
285 }
286 387
287 /* get the end pos */ 388 /* add the new range */
288 if (ltok == NULL) { 389 cut_list = xrealloc_vector(cut_list, 4, nranges);
289 e = s; 390 /* NB: s is always >= 0 */
290 } else if (!ltok[0]) { 391 cut_list[nranges].startpos = s;
291 e = INT_MAX; 392 cut_list[nranges].endpos = e;
292 } else { 393 nranges++;
293 e = xatoi_positive(ltok); 394 }
294 /* if the user specified no end position, 395 cut_list[nranges].startpos = UINT_MAX; /* end indicator */
295 * that means "til the end of the line" */
296#if !ENABLE_PLATFORM_MINGW32
297 if (!*ltok)
298 e = INT_MAX;
299 else if (e < s)
300 bb_error_msg_and_die("%d<%d", e, s);
301#endif
302 e--; /* again, arrays are zero based, lines are 1 based */
303 }
304#if ENABLE_PLATFORM_MINGW32
305 if (s < 0 || e < s)
306 bb_error_msg_and_die("invalid range %s-%s", ntok, ltok ?: ntok);
307#endif
308 396
309 /* add the new list */ 397 /* make sure we got some cut positions out of all that */
310 cut_lists = xrealloc_vector(cut_lists, 4, nlists); 398 //if (nranges == 0)
311 /* NB: startpos is always >= 0 */ 399 // bb_simple_error_msg_and_die("missing list of positions");
312 cut_lists[nlists].startpos = s; 400 //^^^ this is impossible since one of -bcfF is required,
313 cut_lists[nlists].endpos = e; 401 // they populate LIST with non-NULL string and when it is parsed,
314 nlists++; 402 // cut_list[] gets at least one element.
315 }
316 403
317 /* make sure we got some cut positions out of all that */ 404 /* now that the lists are parsed, we need to sort them to make life
318 if (nlists == 0) 405 * easier on us when it comes time to print the chars / fields / lines
319 bb_simple_error_msg_and_die("missing list of positions"); 406 */
407 if (!(opt & OPT_NOSORT))
408 qsort(cut_list, nranges, sizeof(cut_list[0]), cmpfunc);
320 409
321 /* now that the lists are parsed, we need to sort them to make life 410#if ENABLE_FEATURE_CUT_REGEX
322 * easier on us when it comes time to print the chars / fields / lines 411 if (opt & OPT_REGEX) {
323 */ 412 xregcomp(&reg, delim, REG_EXTENDED);
324 if (!(opt & CUT_OPT_NOSORT_FLGS)) 413 delim = (void*) &reg;
325 qsort(cut_lists, nlists, sizeof(cut_lists[0]), cmpfunc);
326 } 414 }
415#endif
327 416
328 { 417 {
329 exitcode_t retval = EXIT_SUCCESS; 418 exitcode_t retval = EXIT_SUCCESS;
@@ -337,12 +426,12 @@ int cut_main(int argc UNUSED_PARAM, char **argv)
337 retval = EXIT_FAILURE; 426 retval = EXIT_FAILURE;
338 continue; 427 continue;
339 } 428 }
340 cut_file(file, delim, odelim, cut_lists, nlists); 429 cut_file(file, delim, odelim, cut_list);
341 fclose_if_not_stdin(file); 430 fclose_if_not_stdin(file);
342 } while (*++argv); 431 } while (*++argv);
343 432
344 if (ENABLE_FEATURE_CLEAN_UP) 433 if (ENABLE_FEATURE_CLEAN_UP)
345 free(cut_lists); 434 free(cut_list);
346 fflush_stdout_and_exit(retval); 435 fflush_stdout_and_exit(retval);
347 } 436 }
348} 437}
diff --git a/libbb/dump.c b/libbb/dump.c
index ffc46f6a7..aa57eca8c 100644
--- a/libbb/dump.c
+++ b/libbb/dump.c
@@ -204,9 +204,11 @@ static NOINLINE void rewrite(priv_dumper_t *dumper, FS *fs)
204 if (!e) 204 if (!e)
205 goto DO_BAD_CONV_CHAR; 205 goto DO_BAD_CONV_CHAR;
206 pr->flags = F_INT; 206 pr->flags = F_INT;
207 if (e > int_convs + 1) /* not d or i? */
208 pr->flags = F_UINT;
209 byte_count_str = "\010\004\002\001"; 207 byte_count_str = "\010\004\002\001";
208 if (e > int_convs + 1) { /* not d or i? */
209 pr->flags = F_UINT;
210 byte_count_str++;
211 }
210 goto DO_BYTE_COUNT; 212 goto DO_BYTE_COUNT;
211 } else 213 } else
212 if (strchr(int_convs, *p1)) { /* %d etc */ 214 if (strchr(int_convs, *p1)) { /* %d etc */
diff --git a/libbb/getopt32.c b/libbb/getopt32.c
index 56040e150..76d29d5eb 100644
--- a/libbb/getopt32.c
+++ b/libbb/getopt32.c
@@ -93,7 +93,7 @@ getopt32(char **argv, const char *applet_opts, ...)
93 93
94 "!" If the first character in the applet_opts string is a '!', 94 "!" If the first character in the applet_opts string is a '!',
95 report bad options, missing required options, 95 report bad options, missing required options,
96 inconsistent options with all-ones return value (instead of abort. 96 inconsistent options with all-ones return value instead of aborting.
97 97
98 "+" If the first character in the applet_opts string is a plus, 98 "+" If the first character in the applet_opts string is a plus,
99 then option processing will stop as soon as a non-option is 99 then option processing will stop as soon as a non-option is
@@ -265,7 +265,7 @@ Special characters:
265 for "long options only" cases, such as tar --exclude=PATTERN, 265 for "long options only" cases, such as tar --exclude=PATTERN,
266 wget --header=HDR cases. 266 wget --header=HDR cases.
267 267
268 "a?b" A "?" between an option and a group of options means that 268 "a?bc" A "?" between an option and a group of options means that
269 at least one of them is required to occur if the first option 269 at least one of them is required to occur if the first option
270 occurs in preceding command line arguments. 270 occurs in preceding command line arguments.
271 271
@@ -348,9 +348,6 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options,
348 unsigned trigger; 348 unsigned trigger;
349 int min_arg = 0; 349 int min_arg = 0;
350 int max_arg = -1; 350 int max_arg = -1;
351 int spec_flgs = 0;
352
353#define SHOW_USAGE_IF_ERROR 1
354 351
355 on_off = complementary; 352 on_off = complementary;
356 memset(on_off, 0, sizeof(complementary)); 353 memset(on_off, 0, sizeof(complementary));
@@ -449,9 +446,7 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options,
449 continue; 446 continue;
450 c = s[1]; 447 c = s[1];
451 if (*s == '?') { 448 if (*s == '?') {
452 if (c < '0' || c > '9') { 449 if (c >= '0' && c <= '9') {
453 spec_flgs |= SHOW_USAGE_IF_ERROR;
454 } else {
455 max_arg = c - '0'; 450 max_arg = c - '0';
456 s++; 451 s++;
457 } 452 }
@@ -465,8 +460,10 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options,
465 continue; 460 continue;
466 } 461 }
467 if (*s == '=') { 462 if (*s == '=') {
468 min_arg = max_arg = c - '0'; 463 if (c >= '0' && c <= '9') {
469 s++; 464 min_arg = max_arg = c - '0';
465 s++;
466 }
470 continue; 467 continue;
471 } 468 }
472 for (on_off = complementary; on_off->opt_char; on_off++) 469 for (on_off = complementary; on_off->opt_char; on_off++)
diff --git a/runit/chpst.c b/runit/chpst.c
index 2be1a5775..4e3d613b7 100644
--- a/runit/chpst.c
+++ b/runit/chpst.c
@@ -466,7 +466,8 @@ int chpst_main(int argc UNUSED_PARAM, char **argv)
466 /* nice should be done before xsetuid */ 466 /* nice should be done before xsetuid */
467 if (opt & OPT_n) { 467 if (opt & OPT_n) {
468 errno = 0; 468 errno = 0;
469 if (nice(xatoi(nicestr)) == -1) 469 nice(xatoi(nicestr));
470 if (errno)
470 bb_simple_perror_msg_and_die("nice"); 471 bb_simple_perror_msg_and_die("nice");
471 } 472 }
472 473
diff --git a/shell/hush.c b/shell/hush.c
index 04dda0734..4a97293cc 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -10361,6 +10361,9 @@ int hush_main(int argc, char **argv)
10361 _exit(0); 10361 _exit(0);
10362 } 10362 }
10363 G.argv0_for_re_execing = argv[0]; 10363 G.argv0_for_re_execing = argv[0];
10364 if (G.argv0_for_re_execing[0] == '-')
10365 /* reexeced hush should never be a login shell */
10366 G.argv0_for_re_execing++;
10364#endif 10367#endif
10365#if ENABLE_HUSH_TRAP 10368#if ENABLE_HUSH_TRAP
10366# if ENABLE_HUSH_FUNCTIONS 10369# if ENABLE_HUSH_FUNCTIONS
diff --git a/testsuite/cut.tests b/testsuite/cut.tests
index a31f41f7f..21cfea809 100755
--- a/testsuite/cut.tests
+++ b/testsuite/cut.tests
@@ -23,14 +23,30 @@ the quick brown fox jumps over the lazy dog
23 23
24testing "cut -b a,a,a" "cut -b 3,3,3 input" "e\np\ne\n" "$abc" "" 24testing "cut -b a,a,a" "cut -b 3,3,3 input" "e\np\ne\n" "$abc" ""
25 25
26testing "cut -b overlaps" "cut -b 1-3,2-5,7-9,9-10 input" \ 26testing "cut -b overlaps" \
27 "one:to:th\nalphabeta\nthe qick \n" "$abc" "" 27 "cut -b 1-3,2-5,7-9,9-10 input" \
28testing "-b encapsulated" "cut -b 3-8,4-6 input" "e:two:\npha:be\ne quic\n" \ 28 "\
29 "$abc" "" 29one:to:th
30# --output-delimiter not implemnted (yet?) 30alphabeta
31#testing "cut -bO overlaps" \ 31the qick \n" \
32# "cut --output-delimiter ' ' -b 1-3,2-5,7-9,9-10 input" \ 32 "$abc" ""
33# "one:t o:th\nalpha beta\nthe q ick \n" "$abc" "" 33testing "-b encapsulated" \
34 "cut -b 3-8,4-6 input" \
35 "\
36e:two:
37pha:be
38e quic\n" \
39 "$abc" ""
40optional LONG_OPTS
41testing "cut -b --output-delimiter overlaps" \
42 "cut --output-delimiter='^' -b 1-3,2-5,7-9,9-10 input" \
43 "\
44one:t^o:th
45alpha^beta
46the q^ick \n" \
47 "$abc" ""
48SKIP=
49
34testing "cut high-low error" "cut -b 8-3 input 2>/dev/null || echo err" "err\n" \ 50testing "cut high-low error" "cut -b 8-3 input 2>/dev/null || echo err" "err\n" \
35 "$abc" "" 51 "$abc" ""
36 52
@@ -68,10 +84,27 @@ testing "cut with -d -f( ) -s" "cut -d' ' -f3 -s input && echo yes" "yes\n" "$in
68testing "cut with -d -f(a) -s" "cut -da -f3 -s input" "n\nsium:Jim\n\ncion:Ed\n" "$input" "" 84testing "cut with -d -f(a) -s" "cut -da -f3 -s input" "n\nsium:Jim\n\ncion:Ed\n" "$input" ""
69testing "cut with -d -f(a) -s -n" "cut -da -f3 -s -n input" "n\nsium:Jim\n\ncion:Ed\n" "$input" "" 85testing "cut with -d -f(a) -s -n" "cut -da -f3 -s -n input" "n\nsium:Jim\n\ncion:Ed\n" "$input" ""
70 86
87input="\
88
89foo bar baz
90
91bing bong boop
92
93"
94testing "cut with -d -s omits blank lines" "cut -d' ' -f2 -s input" "bar\nbong\n" "$input" ""
95
71# substitute for awk 96# substitute for awk
72optional FEATURE_CUT_REGEX 97optional FEATURE_CUT_REGEX
73testing "cut -DF" "cut -DF 2,7,5" \ 98testing "cut -DF unordered" "cut -DF 2,7,5" \
74 "said and your\nare\nis demand. supply\nforecast :\nyou you better,\n\nEm: Took hate\n" "" \ 99 "\
100said and your
101are
102is demand. supply
103forecast :
104you you better,
105
106Em: Took hate
107" "" \
75"Bother, said Pooh. It's your husband, and he has a gun. 108"Bother, said Pooh. It's your husband, and he has a gun.
76Cheerios are donut seeds. 109Cheerios are donut seeds.
77Talk is cheap because supply exceeds demand. 110Talk is cheap because supply exceeds demand.
@@ -79,9 +112,92 @@ Weather forecast for tonight : dark.
79Apple: you can buy better, but you can't pay more. 112Apple: you can buy better, but you can't pay more.
80Subcalifragilisticexpialidocious. 113Subcalifragilisticexpialidocious.
81Auntie Em: Hate you, hate Kansas. Took the dog. Dorothy." 114Auntie Em: Hate you, hate Kansas. Took the dog. Dorothy."
115
116# No delimiter found: print entire line regardless of -F RANGES
117testing "cut -F1" "cut -d: -F1" \
118 "the_only_field\n" "" \
119 "the_only_field\n"
120testing "cut -F2" "cut -d: -F2" \
121 "the_only_field\n" "" \
122 "the_only_field\n"
123# No delimiter found and -s: skip entire line
124testing "cut -sF1" "cut -d: -sF1" \
125 "" "" \
126 "the_only_field\n"
127#^^^ the above is probably mishandled by toybox, it prints the line
128testing "cut -sF2" "cut -d: -sF2" \
129 "" "" \
130 "the_only_field\n"
131# -D disables special handling of lines with no delimiters, the line is treated as the 1st field
132testing "cut -DF1" "cut -d: -DF1" \
133 "the_only_field\n" "" \
134 "the_only_field\n"
135testing "cut -DF2" "cut -d: -DF2" \
136 "\n" "" \
137 "the_only_field\n"
138
139optional FEATURE_CUT_REGEX LONG_OPTS
140testing "cut -F preserves intermediate delimiters" \
141 "cut --output-delimiter=: -F2,4-6,7" \
142 "2:4 5 6:7\n" \
143 "" "1 2 3 4\t\t5 6 7 8\n"
144SKIP=
145
146optional LONG_OPTS
147testing "cut -f does not preserve intermediate delimiters" \
148 "cut --output-delimiter=: -d' ' -f2,4-6,7" \
149 "2:4:5:6:7\n" \
150 "" "1 2 3 4 5 6 7 8\n"
151SKIP=
152
153testing "cut empty field" "cut -d ':' -f 1-3" \
154 "a::b\n" \
155 "" "a::b\n"
156testing "cut empty field 2" "cut -d ':' -f 3-5" \
157 "b::c\n" \
158 "" "a::b::c:d\n"
159testing "cut non-existing field" "cut -d ':' -f1,3" \
160 "1\n" \
161 "" "1:\n"
162
163# cut -d$'\n' has a special meaning: "select input lines".
164# I didn't find any documentation for this feature.
165testing "cut -dNEWLINE" \
166 "cut -d'
167' -f4,2,6-8" \
168 "2\n4\n6\n7\n" \
169 "" "1\n2\n3\n4\n5\n6\n7"
170
171optional LONG_OPTS
172testing "cut -dNEWLINE --output-delimiter" \
173 "cut -d'
174' --output-delimiter=@@ -f4,2,6-8" \
175 "2@@4@@6@@7\n" \
176 "" "1\n2\n3\n4\n5\n6\n7"
177
178testing "cut -dNEWLINE --output-delimiter 2" \
179 "cut -d'
180' --output-delimiter=@@ -f4,2,6-8" \
181 "2@@4@@6@@7\n" \
182 "" "1\n2\n3\n4\n5\n6\n7\n"
183
184testing "cut -dNEWLINE --output-delimiter EMPTY_INPUT" \
185 "cut -d'
186' --output-delimiter=@@ -f4,2,6-8" \
187 "" \
188 "" ""
82SKIP= 189SKIP=
83 190
84testing "cut empty field" "cut -d ':' -f 1-3" "a::b\n" "" "a::b\n" 191# This seems to work as if delimiter is never found.
85testing "cut empty field 2" "cut -d ':' -f 3-5" "b::c\n" "" "a::b::c:d\n" 192# We test here that -d '' does *not* operate as if there was no -d
193# and delimiter has defaulted to TAB:
194testing "cut -d EMPTY" \
195 "cut -d '' -f2-" \
196 "1 2\t3 4 5\n" \
197 "" "1 2\t3 4 5\n"
198testing "cut -d EMPTY -s" \
199 "cut -d '' -f2- -s" \
200 "" \
201 "" "1 2\t3 4 5\n"
86 202
87exit $FAILCOUNT 203exit $FAILCOUNT
diff --git a/testsuite/hexdump.tests b/testsuite/hexdump.tests
index be0379cfc..517ec508b 100755
--- a/testsuite/hexdump.tests
+++ b/testsuite/hexdump.tests
@@ -82,4 +82,10 @@ testing "hexdump -e /2 %d" \
82"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"\ 82"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"\
83"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"\ 83"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"\
84 84
85testing "hexdump -n4 -e '\"%u\"'" \
86 "hexdump -n4 -e '\"%u\"'" \
87 "12345678" \
88 "" \
89 "\x4e\x61\xbc\x00AAAA"
90
85exit $FAILCOUNT 91exit $FAILCOUNT