Skip to content

Commit 259b3c0

Browse files
author
Denys Vlasenko
committed
sed: open input files sequentially to avoid EMFILE
Currently, sed pre-opens all files, which may cause EMFILE errors on systems with low ulimit -n. Change sed to open one file at a time. function old new delta get_next_line 177 235 +58 sed_main 682 652 -30 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 58/-30) Total: 28 bytes Based on the patch by Daniel Borca <[email protected]> Signed-off-by: Denys Vlasenko <[email protected]>
1 parent 8bd810b commit 259b3c0

File tree

2 files changed

+36
-27
lines changed

2 files changed

+36
-27
lines changed

editors/sed.c

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* resulting sed_cmd_t structures are appended to a linked list
2424
* (G.sed_cmd_head/G.sed_cmd_tail).
2525
*
26-
* add_input_file() adds a FILE* to the list of input files. We need to
26+
* add_input_file() adds a char* to the list of input files. We need to
2727
* know all input sources ahead of time to find the last line for the $ match.
2828
*
2929
* process_files() does actual sedding, reading data lines from each input FILE*
@@ -135,20 +135,23 @@ static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v";
135135
struct globals {
136136
/* options */
137137
int be_quiet, regex_type;
138+
138139
FILE *nonstdout;
139140
char *outname, *hold_space;
141+
smallint exitcode;
140142

141-
/* List of input files */
143+
/* list of input files */
142144
int input_file_count, current_input_file;
143-
FILE **input_file_list;
145+
const char **input_file_list;
146+
FILE *current_fp;
144147

145148
regmatch_t regmatch[10];
146149
regex_t *previous_regex_ptr;
147150

148151
/* linked list of sed commands */
149152
sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
150153

151-
/* Linked list of append lines */
154+
/* linked list of append lines */
152155
llist_t *append_head;
153156

154157
char *add_cmd_line;
@@ -200,8 +203,8 @@ static void sed_free_and_close_stuff(void)
200203

201204
free(G.hold_space);
202205

203-
while (G.current_input_file < G.input_file_count)
204-
fclose(G.input_file_list[G.current_input_file++]);
206+
if (G.current_fp)
207+
fclose(G.current_fp);
205208
}
206209
#else
207210
void sed_free_and_close_stuff(void);
@@ -939,8 +942,20 @@ static char *get_next_line(char *gets_char, char *last_puts_char, char last_gets
939942
/* will be returned if last line in the file
940943
* doesn't end with either '\n' or '\0' */
941944
gc = NO_EOL_CHAR;
942-
while (G.current_input_file < G.input_file_count) {
943-
FILE *fp = G.input_file_list[G.current_input_file];
945+
for (; G.input_file_list[G.current_input_file]; G.current_input_file++) {
946+
FILE *fp = G.current_fp;
947+
if (!fp) {
948+
const char *path = G.input_file_list[G.current_input_file];
949+
fp = stdin;
950+
if (path != bb_msg_standard_input) {
951+
fp = fopen_or_warn(path, "r");
952+
if (!fp) {
953+
G.exitcode = EXIT_FAILURE;
954+
continue;
955+
}
956+
}
957+
G.current_fp = fp;
958+
}
944959
/* Read line up to a newline or NUL byte, inclusive,
945960
* return malloc'ed char[]. length of the chunk read
946961
* is stored in len. NULL if EOF/error */
@@ -971,8 +986,8 @@ static char *get_next_line(char *gets_char, char *last_puts_char, char last_gets
971986
* (note: *no* newline after "b bang"!) */
972987
}
973988
/* Close this file and advance to next one */
974-
fclose(fp);
975-
G.current_input_file++;
989+
fclose_if_not_stdin(fp);
990+
G.current_fp = NULL;
976991
}
977992
*gets_char = gc;
978993
return temp;
@@ -1399,7 +1414,7 @@ static void add_cmd_block(char *cmdstr)
13991414
free(sv);
14001415
}
14011416

1402-
static void add_input_file(FILE *file)
1417+
static void add_input_file(const char *file)
14031418
{
14041419
G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
14051420
G.input_file_list[G.input_file_count++] = file;
@@ -1423,8 +1438,6 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
14231438
"file\0" Required_argument "f";
14241439
#endif
14251440

1426-
int status = EXIT_SUCCESS;
1427-
14281441
INIT_G();
14291442

14301443
/* destroy command strings on exit */
@@ -1491,27 +1504,21 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
14911504
if (argv[0] == NULL) {
14921505
if (opt & OPT_in_place)
14931506
bb_error_msg_and_die(bb_msg_requires_arg, "-i");
1494-
add_input_file(stdin);
1507+
add_input_file(bb_msg_standard_input);
14951508
} else {
14961509
int i;
14971510

14981511
for (i = 0; argv[i]; i++) {
14991512
struct stat statbuf;
15001513
int nonstdoutfd;
1501-
FILE *file;
15021514
sed_cmd_t *sed_cmd;
15031515

15041516
if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) {
1505-
add_input_file(stdin);
1517+
add_input_file(bb_msg_standard_input);
15061518
process_files();
15071519
continue;
15081520
}
1509-
file = fopen_or_warn(argv[i], "r");
1510-
if (!file) {
1511-
status = EXIT_FAILURE;
1512-
continue;
1513-
}
1514-
add_input_file(file);
1521+
add_input_file(argv[i]);
15151522
if (!(opt & OPT_in_place)) {
15161523
continue;
15171524
}
@@ -1523,7 +1530,7 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
15231530
G.nonstdout = xfdopen_for_write(nonstdoutfd);
15241531

15251532
/* Set permissions/owner of output file */
1526-
fstat(fileno(file), &statbuf);
1533+
stat(argv[i], &statbuf);
15271534
/* chmod'ing AFTER chown would preserve suid/sgid bits,
15281535
* but GNU sed 4.2.1 does not preserve them either */
15291536
fchmod(nonstdoutfd, statbuf.st_mode);
@@ -1549,12 +1556,13 @@ int sed_main(int argc UNUSED_PARAM, char **argv)
15491556
}
15501557
}
15511558
/* Here, to handle "sed 'cmds' nonexistent_file" case we did:
1552-
* if (G.current_input_file >= G.input_file_count)
1553-
* return status;
1559+
* if (G.current_input_file[G.current_input_file] == NULL)
1560+
* return G.exitcode;
15541561
* but it's not needed since process_files() works correctly
15551562
* in this case too. */
15561563
}
1564+
15571565
process_files();
15581566

1559-
return status;
1567+
return G.exitcode;
15601568
}

libbb/fclose_nonstdin.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ int FAST_FUNC fclose_if_not_stdin(FILE *f)
1818
{
1919
/* Some more paranoid applets want ferror() check too */
2020
int r = ferror(f); /* NB: does NOT set errno! */
21-
if (r) errno = EIO; /* so we'll help it */
21+
if (r)
22+
errno = EIO; /* so we'll help it */
2223
if (f != stdin)
2324
return (r | fclose(f)); /* fclose does set errno on error */
2425
return r;

0 commit comments

Comments
 (0)