#else
# define CLEAR_RANDOM_T(rnd) ((void)0)
#endif
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
#ifndef F_DUPFD_CLOEXEC
# define F_DUPFD_CLOEXEC F_DUPFD
#endif
};
#endif
+/* We almost can use standard FILE api, but we need an ability to move
+ * its fd when redirects coincide with it. No api exists for that
+ * (RFE for it at https://sourceware.org/bugzilla/show_bug.cgi?id=21902).
+ * HFILE is our internal alternative. Only supports reading.
+ * Since we now can, we incorporate linked list of all opened HFILEs
+ * into the struct (used to be a separate mini-list).
+ */
+typedef struct HFILE {
+ char *cur;
+ char *end;
+ struct HFILE *next_hfile;
+ int is_stdin;
+ int fd;
+ char buf[1024];
+} HFILE;
+
typedef struct in_str {
const char *p;
int peek_buf[2];
int last_char;
- FILE *file;
+ HFILE *file;
} in_str;
/* The descrip member of this structure is only used to make
NUM_OPT_O
};
-
-struct FILE_list {
- struct FILE_list *next;
- FILE *fp;
- int fd;
-};
-
-
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
struct globals {
unsigned lineno;
char *lineno_var;
#endif
- struct FILE_list *FILE_list;
+ HFILE *HFILE_list;
/* Which signals have non-DFL handler (even with no traps set)?
* Set at the start to:
* (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
}
-/* Manipulating the list of open FILEs */
-static FILE *remember_FILE(FILE *fp)
+/* Manipulating HFILEs */
+static HFILE *hfopen(const char *name)
{
- if (fp) {
- struct FILE_list *n = xmalloc(sizeof(*n));
- n->next = G.FILE_list;
- G.FILE_list = n;
- n->fp = fp;
- n->fd = fileno(fp);
- close_on_exec_on(n->fd);
+ HFILE *fp;
+ int fd;
+
+ fd = STDIN_FILENO;
+ if (name) {
+ fd = open(name, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return NULL;
+ if (O_CLOEXEC == 0) /* ancient libc */
+ close_on_exec_on(fd);
}
+
+ fp = xmalloc(sizeof(*fp));
+ fp->is_stdin = (name == NULL);
+ fp->fd = fd;
+ fp->cur = fp->end = fp->buf;
+ fp->next_hfile = G.HFILE_list;
+ G.HFILE_list = fp;
return fp;
}
-static void fclose_and_forget(FILE *fp)
+static void hfclose(HFILE *fp)
{
- struct FILE_list **pp = &G.FILE_list;
+ HFILE **pp = &G.HFILE_list;
while (*pp) {
- struct FILE_list *cur = *pp;
- if (cur->fp == fp) {
- *pp = cur->next;
- free(cur);
+ HFILE *cur = *pp;
+ if (cur == fp) {
+ *pp = cur->next_hfile;
break;
}
- pp = &cur->next;
+ pp = &cur->next_hfile;
}
- fclose(fp);
+ if (fp->fd >= 0)
+ close(fp->fd);
+ free(fp);
}
-static int save_FILEs_on_redirect(int fd, int avoid_fd)
+static int refill_HFILE_and_getc(HFILE *fp)
{
- struct FILE_list *fl = G.FILE_list;
+ int n;
+
+ if (fp->fd < 0) {
+ /* Already saw EOF */
+ return EOF;
+ }
+ /* Try to buffer more input */
+ fp->cur = fp->buf;
+ n = safe_read(fp->fd, fp->buf, sizeof(fp->buf));
+ if (n < 0) {
+ bb_perror_msg("read error");
+ n = 0;
+ }
+ fp->end = fp->buf + n;
+ if (n == 0) {
+ /* EOF/error */
+ close(fp->fd);
+ fp->fd = -1;
+ return EOF;
+ }
+ return (unsigned char)(*fp->cur++);
+}
+/* Inlined for common case of non-empty buffer.
+ */
+static ALWAYS_INLINE int hfgetc(HFILE *fp)
+{
+ if (fp->cur < fp->end)
+ return (unsigned char)(*fp->cur++);
+ /* Buffer empty */
+ return refill_HFILE_and_getc(fp);
+}
+static int move_HFILEs_on_redirect(int fd, int avoid_fd)
+{
+ HFILE *fl = G.HFILE_list;
while (fl) {
if (fd == fl->fd) {
/* We use it only on script files, they are all CLOEXEC */
fl->fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
- return 1;
- }
- fl = fl->next;
- }
- return 0;
-}
-static void restore_redirected_FILEs(void)
-{
- struct FILE_list *fl = G.FILE_list;
- while (fl) {
- int should_be = fileno(fl->fp);
- if (fl->fd != should_be) {
- debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be);
- xmove_fd(fl->fd, should_be);
- fl->fd = should_be;
+ return 1; /* "found and moved" */
}
- fl = fl->next;
+ fl = fl->next_hfile;
}
+ return 0; /* "not in the list" */
}
#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
-static void close_all_FILE_list(void)
+static void close_all_HFILE_list(void)
{
- struct FILE_list *fl = G.FILE_list;
+ HFILE *fl = G.HFILE_list;
while (fl) {
- /* fclose would also free FILE object.
+ /* hfclose would also free HFILE object.
* It is disastrous if we share memory with a vforked parent.
* I'm not sure we never come here after vfork.
* Therefore just close fd, nothing more.
*/
- /*fclose(fl->fp); - unsafe */
- close(fl->fd);
- fl = fl->next;
+ /*hfclose(fl); - unsafe */
+ if (fl->fd >= 0)
+ close(fl->fd);
+ fl = fl->next_hfile;
}
}
#endif
-static int fd_in_FILEs(int fd)
+static int fd_in_HFILEs(int fd)
{
- struct FILE_list *fl = G.FILE_list;
+ HFILE *fl = G.HFILE_list;
while (fl) {
if (fl->fd == fd)
return 1;
- fl = fl->next;
+ fl = fl->next_hfile;
}
return 0;
}
}
fflush_all();
//FIXME: here ^C or SIGINT will have effect only after <Enter>
- r = fgetc(i->file);
+ r = hfgetc(i->file);
/* In !ENABLE_FEATURE_EDITING we don't use read_line_input,
* no ^C masking happens during fgetc, no special code for ^C:
* it generates SIGINT as usual.
{
int ch;
/* If it's interactive stdin, get new line. */
- if (G_interactive_fd && i->file == stdin) {
+ if (G_interactive_fd && i->file->is_stdin) {
/* Returns first char (or EOF), the rest is in i->p[] */
ch = get_user_input(i);
G.promptmode = 1; /* PS2 */
debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
} else {
/* Not stdin: script file, sourced file, etc */
- do ch = fgetc(i->file); while (ch == '\0');
+ do ch = hfgetc(i->file); while (ch == '\0');
}
return ch;
}
#else
-static inline int fgetc_interactive(struct in_str *i)
+static ALWAYS_INLINE int fgetc_interactive(struct in_str *i)
{
int ch;
- do ch = fgetc(i->file); while (ch == '\0');
+ do ch = hfgetc(i->file); while (ch == '\0');
return ch;
}
#endif /* INTERACTIVE */
ch = i->peek_buf[1];
if (ch == 0) {
/* We did not read it yet, get it now */
- do ch = fgetc(i->file); while (ch == '\0');
+ do ch = hfgetc(i->file); while (ch == '\0');
i->peek_buf[1] = ch;
}
}
}
-static void setup_file_in_str(struct in_str *i, FILE *f)
+static void setup_file_in_str(struct in_str *i, HFILE *fp)
{
memset(i, 0, sizeof(*i));
- i->file = f;
+ i->file = fp;
/* i->p = NULL; */
}
//IF_HUSH_LINENO_VAR(G.lineno = sv;)
}
-static void parse_and_run_file(FILE *f)
+static void parse_and_run_file(HFILE *fp)
{
struct in_str input;
IF_HUSH_LINENO_VAR(unsigned sv = G.lineno;)
IF_HUSH_LINENO_VAR(G.lineno = 1;)
- setup_file_in_str(&input, f);
+ setup_file_in_str(&input, fp);
parse_and_run_stream(&input, ';');
IF_HUSH_LINENO_VAR(G.lineno = sv;)
}
#if ENABLE_HUSH_TICK
-static FILE *generate_stream_from_string(const char *s, pid_t *pid_p)
+static int generate_stream_from_string(const char *s, pid_t *pid_p)
{
pid_t pid;
int channel[2];
free(to_free);
# endif
close(channel[1]);
- return remember_FILE(xfdopen_for_read(channel[0]));
+ return channel[0];
}
/* Return code is exit status of the process that is run. */
pid_t pid;
int status, ch, eol_cnt;
- fp = generate_stream_from_string(s, &pid);
+ fp = xfdopen_for_read(generate_stream_from_string(s, &pid));
/* Now send results of command back into original context */
eol_cnt = 0;
}
debug_printf("done reading from `cmd` pipe, closing it\n");
- fclose_and_forget(fp);
+ fclose(fp);
/* We need to extract exitcode. Test case
* "true; echo `sleep 1; false` $?"
* should print 1 */
return 1; /* "we closed fd" */
}
#endif
+ /* If this one of script's fds? */
+ if (move_HFILEs_on_redirect(fd, avoid_fd))
+ return 1; /* yes. "we closed fd" (actually moved it) */
+
/* Are we called from setup_redirects(squirrel==NULL)? Two cases:
- * (1) Redirect in a forked child. No need to save FILEs' fds,
- * we aren't going to use them anymore, ok to trash.
- * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds,
- * but how are we doing to restore them?
- * "fileno(fd) = new_fd" can't be done.
+ * (1) Redirect in a forked child.
+ * (2) "exec 3>FILE".
*/
if (!sqp)
return 0;
- /* If this one of script's fds? */
- if (save_FILEs_on_redirect(fd, avoid_fd))
- return 1; /* yes. "we closed fd" */
-
/* Check whether it collides with any open fds (e.g. stdio), save fds as needed */
*sqp = add_squirrel(*sqp, fd, avoid_fd);
return 0; /* "we did not close fd" */
}
/* If moved, G.interactive_fd stays on new fd, not restoring it */
-
- restore_redirected_FILEs();
}
#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
{
if (G_interactive_fd)
close(G_interactive_fd);
- close_all_FILE_list();
+ close_all_HFILE_list();
}
#endif
return 1;
#endif
/* If this one of script's fds? */
- if (fd_in_FILEs(fd))
+ if (fd_in_HFILEs(fd))
return 1;
if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) {
save_fd_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
/* for REDIRECT_HEREDOC2, rd_filename holds _contents_
* of the heredoc */
- debug_printf_parse("set heredoc '%s'\n",
+ debug_printf_redir("set heredoc '%s'\n",
redir->rd_filename);
setup_heredoc(redir);
continue;
int mode;
if (redir->rd_filename == NULL) {
- /*
- * Examples:
+ /* Examples:
* "cmd >" (no filename)
* "cmd > <file" (2nd redirect starts too early)
*/
}
#endif
/* { list } */
- debug_printf("non-subshell group\n");
+ debug_printf_exec("non-subshell group\n");
rcode = 1; /* exitcode if redir failed */
if (setup_redirects(command, &squirrel) == 0) {
debug_printf_exec(": run_list\n");
/* If we are login shell... */
if (flags & OPT_login) {
- FILE *input;
+ HFILE *input;
debug_printf("sourcing /etc/profile\n");
- input = fopen_for_read("/etc/profile");
+ input = hfopen("/etc/profile");
if (input != NULL) {
- remember_FILE(input);
install_special_sighandlers();
parse_and_run_file(input);
- fclose_and_forget(input);
+ hfclose(input);
}
/* bash: after sourcing /etc/profile,
* tries to source (in the given order):
/* -s is: hush -s ARGV1 ARGV2 (no SCRIPT) */
if (!(flags & OPT_s) && G.global_argv[1]) {
- FILE *input;
+ HFILE *input;
/*
* "bash <script>" (which is never interactive (unless -i?))
* sources $BASH_ENV here (without scanning $PATH).
G.global_argv++;
debug_printf("running script '%s'\n", G.global_argv[0]);
xfunc_error_retval = 127; /* for "hush /does/not/exist" case */
- input = xfopen_for_read(G.global_argv[0]);
+ input = hfopen(G.global_argv[0]);
+ if (!input) {
+ bb_simple_perror_msg_and_die(G.global_argv[0]);
+ }
xfunc_error_retval = 1;
- remember_FILE(input);
install_special_sighandlers();
parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP
- fclose_and_forget(input);
+ hfclose(input);
#endif
goto final_return;
}
);
}
- parse_and_run_file(stdin);
+ parse_and_run_file(hfopen(NULL)); /* stdin */
final_return:
hush_exit(G.last_exitcode);
static int FAST_FUNC builtin_source(char **argv)
{
char *arg_path, *filename;
- FILE *input;
+ HFILE *input;
save_arg_t sv;
char *args_need_save;
#if ENABLE_HUSH_FUNCTIONS
return EXIT_FAILURE;
}
}
- input = remember_FILE(fopen_or_warn(filename, "r"));
+ input = hfopen(filename);
free(arg_path);
if (!input) {
- /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
+ bb_perror_msg("%s", filename);
/* POSIX: non-interactive shell should abort here,
* not merely fail. So far no one complained :)
*/
/* "false; . ./empty_line; echo Zero:$?" should print 0 */
G.last_exitcode = 0;
parse_and_run_file(input);
- fclose_and_forget(input);
+ hfclose(input);
if (args_need_save) /* can't use argv[1] instead: "shift" can mangle it */
restore_G_args(&sv, argv);