Mark stdin as nonblocking if we get EWOULDBLOCK, and before handing it off to child processes when either starting them or moving them to the foreground.

https://github.com/fish-shell/fish-shell/issues/176
This commit is contained in:
ridiculousfish 2013-04-07 12:40:08 -07:00
parent 3a7ab3f030
commit 437b4397b9
10 changed files with 77 additions and 54 deletions

View File

@ -135,7 +135,7 @@ static int try_get_socket_once(void)
return -1; return -1;
} }
if ((fcntl(s, F_SETFL, O_NONBLOCK) != 0) || (fcntl(s, F_SETFD, FD_CLOEXEC) != 0)) if ((make_fd_nonblocking(s) != 0) || (fcntl(s, F_SETFD, FD_CLOEXEC) != 0))
{ {
wperror(L"fcntl"); wperror(L"fcntl");
close(s); close(s);

View File

@ -502,10 +502,10 @@ static void internal_exec_helper(parser_t &parser,
} }
/* Returns whether we can use posix spawn for a given process in a given job. /* Returns whether we can use posix spawn for a given process in a given job.
Per https://github.com/fish-shell/fish-shell/issues/364 , error handling for file redirections is too difficult with posix_spawn Per https://github.com/fish-shell/fish-shell/issues/364 , error handling for file redirections is too difficult with posix_spawn,
So in that case we use fork/exec so in that case we use fork/exec.
Furthermore, to avoid the race between the caller calling tcsetpgrp() and the client checking the foreground process group, we don't use posix_spawn if we're going to foreground the process. (If we use fork(), we can call tcsetpgrp after the fork, before the exec, and avoid the racse). Furthermore, to avoid the race between the caller calling tcsetpgrp() and the client checking the foreground process group, we don't use posix_spawn if we're going to foreground the process. (If we use fork(), we can call tcsetpgrp after the fork, before the exec, and avoid the race).
*/ */
static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *process) static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *process)
{ {
@ -1312,6 +1312,9 @@ void exec(parser_t &parser, job_t *j)
/* Get argv and envv before we fork */ /* Get argv and envv before we fork */
null_terminated_array_t<char> argv_array; null_terminated_array_t<char> argv_array;
convert_wide_array_to_narrow(p->get_argv_array(), &argv_array); convert_wide_array_to_narrow(p->get_argv_array(), &argv_array);
/* Ensure that stdin is blocking before we hand it off (see issue #176). It's a little strange that we only do this with stdin and not with stdout or stderr. However in practice, setting or clearing O_NONBLOCK on stdin also sets it for the other two fds, presumably because they refer to the same underlying file (/dev/tty?) */
make_fd_blocking(STDIN_FILENO);
const char * const *argv = argv_array.get(); const char * const *argv = argv_array.get();
const char * const *envv = env_export_arr(false); const char * const *envv = env_export_arr(false);

View File

@ -578,7 +578,7 @@ static int get_socket(void)
goto unlock; goto unlock;
} }
if (fcntl(s, F_SETFL, O_NONBLOCK) != 0) if (make_fd_nonblocking(s) != 0)
{ {
wperror(L"fcntl"); wperror(L"fcntl");
close(s); close(s);
@ -988,7 +988,7 @@ int main(int argc, char ** argv)
{ {
debug(4, L"Connected with new child on fd %d", child_socket); debug(4, L"Connected with new child on fd %d", child_socket);
if (fcntl(child_socket, F_SETFL, O_NONBLOCK) != 0) if (make_fd_nonblocking(child_socket) != 0)
{ {
wperror(L"fcntl"); wperror(L"fcntl");
close(child_socket); close(child_socket);

View File

@ -81,7 +81,7 @@ void input_common_destroy()
} }
/** /**
Internal function used by input_common_readch to read one byte from fd 1. This function should only be called by Internal function used by input_common_readch to read one byte from fd 0. This function should only be called by
input_common_readch(). input_common_readch().
*/ */
static wint_t readb() static wint_t readb()

4
io.cpp
View File

@ -138,9 +138,7 @@ io_buffer_t *io_buffer_t::create(bool is_input, int fd)
wperror(L"pipe"); wperror(L"pipe");
success = false; success = false;
} }
else if (fcntl(buffer_redirect->pipe_fd[0], else if (make_fd_nonblocking(buffer_redirect->pipe_fd[0]) != 0)
F_SETFL,
O_NONBLOCK))
{ {
debug(1, PIPE_ERROR); debug(1, PIPE_ERROR);
wperror(L"fcntl"); wperror(L"fcntl");

View File

@ -362,9 +362,7 @@ int job_signal(job_t *j, int signal)
Return 0 if all went well, nonzero otherwise. Return 0 if all went well, nonzero otherwise.
This is called from a signal handler. This is called from a signal handler.
*/ */
static void mark_process_status(const job_t *j, static void mark_process_status(const job_t *j, process_t *p, int status)
process_t *p,
int status)
{ {
// debug( 0, L"Process %ls %ls", p->argv[0], WIFSTOPPED (status)?L"stopped":(WIFEXITED( status )?L"exited":(WIFSIGNALED( status )?L"signaled to exit":L"BLARGH")) ); // debug( 0, L"Process %ls %ls", p->argv[0], WIFSTOPPED (status)?L"stopped":(WIFEXITED( status )?L"exited":(WIFSIGNALED( status )?L"signaled to exit":L"BLARGH")) );
p->status = status; p->status = status;
@ -418,8 +416,8 @@ void job_mark_process_as_failed(const job_t *job, process_t *p)
static void handle_child_status(pid_t pid, int status) static void handle_child_status(pid_t pid, int status)
{ {
bool found_proc = false; bool found_proc = false;
const job_t *j=0; const job_t *j = NULL;
process_t *p=0; process_t *p = NULL;
// char mess[MESS_SIZE]; // char mess[MESS_SIZE];
/* /*
snprintf( mess, snprintf( mess,
@ -965,7 +963,7 @@ static void read_try(job_t *j)
a job that has previously been stopped. In that case, we need to a job that has previously been stopped. In that case, we need to
set the terminal attributes to those saved in the job. set the terminal attributes to those saved in the job.
*/ */
static int terminal_give_to_job(job_t *j, int cont) static bool terminal_give_to_job(job_t *j, int cont)
{ {
if (tcsetpgrp(0, j->pgid)) if (tcsetpgrp(0, j->pgid))
@ -975,7 +973,7 @@ static int terminal_give_to_job(job_t *j, int cont)
j->job_id, j->job_id,
j->command_wcstr()); j->command_wcstr());
wperror(L"tcsetpgrp"); wperror(L"tcsetpgrp");
return 0; return false;
} }
if (cont) if (cont)
@ -987,10 +985,10 @@ static int terminal_give_to_job(job_t *j, int cont)
j->job_id, j->job_id,
j->command_wcstr()); j->command_wcstr());
wperror(L"tcsetattr"); wperror(L"tcsetattr");
return 0; return false;
} }
} }
return 1; return true;
} }
/** /**
@ -1059,18 +1057,17 @@ void job_continue(job_t *j, bool cont)
{ {
if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND)) if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND))
{ {
/* Put the job into the foreground. */ /* Put the job into the foreground. Hack: ensure that stdin is marked as blocking first (#176). */
int ok; make_fd_blocking(STDIN_FILENO);
signal_block(); signal_block();
ok = terminal_give_to_job(j, cont); bool ok = terminal_give_to_job(j, cont);
signal_unblock(); signal_unblock();
if (!ok) if (!ok)
return; return;
} }
/* /*
@ -1186,7 +1183,6 @@ void job_continue(job_t *j, bool cont)
// were sometimes having their output combined with the set_color calls in the wrong order! // were sometimes having their output combined with the set_color calls in the wrong order!
read_try(j); read_try(j);
process_t *p = j->first_process; process_t *p = j->first_process;
while (p->next) while (p->next)
p = p->next; p = p->next;
@ -1205,9 +1201,8 @@ void job_continue(job_t *j, bool cont)
} }
} }
} }
/*
Put the shell back in the foreground. /* Put the shell back in the foreground. */
*/
if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND)) if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND))
{ {
int ok; int ok;

View File

@ -2328,12 +2328,12 @@ void set_env_cmd_duration(struct timeval *after, struct timeval *before)
} }
} }
void reader_run_command(parser_t &parser, const wchar_t *cmd) void reader_run_command(parser_t &parser, const wcstring &cmd)
{ {
struct timeval time_before, time_after; struct timeval time_before, time_after;
wcstring ft = tok_first(cmd); wcstring ft = tok_first(cmd.c_str());
if (! ft.empty()) if (! ft.empty())
env_set(L"_", ft.c_str(), ENV_GLOBAL); env_set(L"_", ft.c_str(), ENV_GLOBAL);
@ -2709,7 +2709,7 @@ static void handle_end_loop()
Read interactively. Read input from stdin while providing editing Read interactively. Read input from stdin while providing editing
facilities. facilities.
*/ */
static int read_i() static int read_i(void)
{ {
reader_push(L"fish"); reader_push(L"fish");
reader_set_complete_function(&complete); reader_set_complete_function(&complete);
@ -2724,8 +2724,6 @@ static int read_i()
while ((!data->end_loop) && (!sanity_check())) while ((!data->end_loop) && (!sanity_check()))
{ {
const wchar_t *tmp;
event_fire_generic(L"fish_prompt"); event_fire_generic(L"fish_prompt");
if (function_exists(LEFT_PROMPT_FUNCTION_NAME)) if (function_exists(LEFT_PROMPT_FUNCTION_NAME))
reader_set_left_prompt(LEFT_PROMPT_FUNCTION_NAME); reader_set_left_prompt(LEFT_PROMPT_FUNCTION_NAME);
@ -2744,9 +2742,7 @@ static int read_i()
during evaluation. during evaluation.
*/ */
const wchar_t *tmp = reader_readline();
tmp = reader_readline();
if (data->end_loop) if (data->end_loop)
{ {
@ -2754,13 +2750,11 @@ static int read_i()
} }
else if (tmp) else if (tmp)
{ {
tmp = wcsdup(tmp); wcstring command = tmp;
data->buff_pos=0; data->buff_pos=0;
data->command_line.clear(); data->command_line.clear();
data->command_line_changed(); data->command_line_changed();
reader_run_command(parser, tmp); reader_run_command(parser, command);
free((void *)tmp);
if (data->end_loop) if (data->end_loop)
{ {
handle_end_loop(); handle_end_loop();
@ -2837,7 +2831,7 @@ static wchar_t unescaped_quote(const wcstring &str, size_t pos)
} }
const wchar_t *reader_readline() const wchar_t *reader_readline(void)
{ {
wint_t c; wint_t c;
int last_char=0; int last_char=0;
@ -3612,7 +3606,7 @@ static int read_ni(int fd, const io_chain_t &io)
wchar_t *buff=0; wchar_t *buff=0;
std::vector<char> acc; std::vector<char> acc;
int des = fd == 0 ? dup(0) : fd; int des = (fd == STDIN_FILENO ? dup(STDIN_FILENO) : fd);
int res=0; int res=0;
if (des == -1) if (des == -1)
@ -3629,14 +3623,23 @@ static int read_ni(int fd, const io_chain_t &io)
char buff[4096]; char buff[4096];
size_t c = fread(buff, 1, 4096, in_stream); size_t c = fread(buff, 1, 4096, in_stream);
if (ferror(in_stream) && (errno != EINTR)) if (ferror(in_stream))
{ {
debug(1, if (errno == EINTR)
_(L"Error while reading from file descriptor")); {
/* We got a signal, just keep going */
continue;
}
else if ((errno == EAGAIN || errno == EWOULDBLOCK) && make_fd_blocking(des) == 0)
{
/* We succeeded in making the fd blocking, try again */
continue;
}
/* Fatal error */
debug(1, _(L"Error while reading from file descriptor"));
/* /* Reset buffer on error. We won't evaluate incomplete files. */
Reset buffer on error. We won't evaluate incomplete files.
*/
acc.clear(); acc.clear();
break; break;

View File

@ -85,7 +85,7 @@ void reader_repaint_if_needed();
Run the specified command with the correct terminal modes, and Run the specified command with the correct terminal modes, and
while taking care to perform job notification, set the title, etc. while taking care to perform job notification, set the title, etc.
*/ */
void reader_run_command(const wchar_t *buff); void reader_run_command(const wcstring &buff);
/** /**
Get the string of character currently entered into the command Get the string of character currently entered into the command

View File

@ -264,12 +264,10 @@ int wstat(const wcstring &file_name, struct stat *buf)
int lwstat(const wcstring &file_name, struct stat *buf) int lwstat(const wcstring &file_name, struct stat *buf)
{ {
// fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
cstring tmp = wcs2string(file_name); cstring tmp = wcs2string(file_name);
return lstat(tmp.c_str(), buf); return lstat(tmp.c_str(), buf);
} }
int waccess(const wcstring &file_name, int mode) int waccess(const wcstring &file_name, int mode)
{ {
cstring tmp = wcs2string(file_name); cstring tmp = wcs2string(file_name);
@ -292,6 +290,28 @@ void wperror(const wcstring &s)
fwprintf(stderr, L"%s\n", strerror(e)); fwprintf(stderr, L"%s\n", strerror(e));
} }
int make_fd_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
int err = 0;
if (! (flags & O_NONBLOCK))
{
err = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
return err == -1 ? errno : 0;
}
int make_fd_blocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
int err = 0;
if (flags & O_NONBLOCK)
{
err = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
}
return err == -1 ? errno : 0;
}
static inline void safe_append(char *buffer, const char *s, size_t buffsize) static inline void safe_append(char *buffer, const char *s, size_t buffsize)
{ {
strncat(buffer, s, buffsize - strlen(buffer) - 1); strncat(buffer, s, buffsize - strlen(buffer) - 1);

10
wutil.h
View File

@ -48,9 +48,13 @@ int wopen(const wcstring &pathname, int flags, mode_t mode = 0);
/** Wide character version of open() that also sets the close-on-exec flag (atomically when possible). */ /** Wide character version of open() that also sets the close-on-exec flag (atomically when possible). */
int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode = 0); int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode = 0);
/** /** Mark an fd as nonblocking; returns errno or 0 on success */
Wide character version of creat(). int make_fd_nonblocking(int fd);
*/
/** Mark an fd as blocking; returns errno or 0 on success */
int make_fd_blocking(int fd);
/** Wide character version of creat(). */
int wcreat(const wcstring &pathname, mode_t mode); int wcreat(const wcstring &pathname, mode_t mode);