diff --git a/channel.h b/channel.h index 6aae9ea..55e708a 100644 --- a/channel.h +++ b/channel.h @@ -71,6 +71,9 @@ struct Channel { /* whether close/eof messages have been exchanged */ int sent_close, recv_close; int recv_eof, sent_eof; + /* once flushing is set, readfd will close once no more data is available + (not waiting for EOF) */ + int flushing; struct dropbear_progress_connection *conn_pending; int initconn; /* used for TCP forwarding, whether the channel has been @@ -93,9 +96,9 @@ struct ChanType { const char *name; /* Sets up the channel */ int (*inithandler)(struct Channel*); - /* Called to check whether a channel should close, separately from the FD being closed. + /* Called to check whether a channel should close, separately from the FD being EOF. Used for noticing process exiting */ - int (*check_close)(const struct Channel*); + int (*check_close)(struct Channel*); /* Handler for ssh_msg_channel_request */ void (*reqhandler)(struct Channel*); /* Called prior to sending ssh_msg_channel_close, used for sending exit status */ diff --git a/common-channel.c b/common-channel.c index 5c8d9bc..047eece 100644 --- a/common-channel.c +++ b/common-channel.c @@ -285,14 +285,27 @@ static void check_close(struct Channel *channel) { /* if a type-specific check_close is defined we will only exit once that has been triggered. this is only used for a server "session" - channel, to ensure that the shell has exited (and the exit status + channel, to ensure that the shell has exited (and the exit status retrieved) before we close things up. */ - if (!channel->type->check_close + if (!channel->type->check_close || channel->sent_close || channel->type->check_close(channel)) { close_allowed = 1; } + /* In flushing mode we close FDs as soon as pipes are empty. + This is used to drain out FDs when the process exits, in the case + where the FD doesn't have EOF - "sleep 10&echo hello" case */ + if (channel->flushing) { + if (channel->readfd >= 0 && !fd_read_pending(channel->readfd)) { + close_chan_fd(channel, channel->readfd, SHUT_RD); + } + if (ERRFD_IS_READ(channel) + && channel->errfd >= 0 && !fd_read_pending(channel->errfd)) { + close_chan_fd(channel, channel->errfd, SHUT_RD); + } + } + if (channel->recv_close && !write_pending(channel) && close_allowed) { if (!channel->sent_close) { TRACE(("Sending MSG_CHANNEL_CLOSE in response to same.")) diff --git a/dbutil.c b/dbutil.c index f278efa..7980442 100644 --- a/dbutil.c +++ b/dbutil.c @@ -715,3 +715,22 @@ void fsync_parent_dir(const char* fn) { m_free(fn_dir); #endif } + +int fd_read_pending(int fd) { + fd_set fds; + struct timeval timeout; + + DROPBEAR_FD_ZERO(&fds); + FD_SET(fd, &fds); + while (1) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select(fd+1, &fds, NULL, NULL, &timeout) < 0) { + if (errno == EINTR) { + continue; + } + return 0; + } + return FD_ISSET(fd, &fds); + } +} diff --git a/dbutil.h b/dbutil.h index 2a1c82c..39e1c35 100644 --- a/dbutil.h +++ b/dbutil.h @@ -90,6 +90,8 @@ char * expand_homedir_path(const char *inpath); void fsync_parent_dir(const char* fn); +int fd_read_pending(int fd); + #if DROPBEAR_MSAN /* FD_ZERO seems to leave some memory uninitialized. clear it to avoid false positives */ #define DROPBEAR_FD_ZERO(fds) do { memset((fds), 0x0, sizeof(fd_set)); FD_ZERO(fds); } while(0) diff --git a/svr-chansession.c b/svr-chansession.c index 8ae1a4b..2f90bac 100644 --- a/svr-chansession.c +++ b/svr-chansession.c @@ -54,7 +54,7 @@ static void closechansess(const struct Channel *channel); static void cleanupchansess(const struct Channel *channel); static int newchansess(struct Channel *channel); static void chansessionrequest(struct Channel *channel); -static int sesscheckclose(const struct Channel *channel); +static int sesscheckclose(struct Channel *channel); static void send_exitsignalstatus(const struct Channel *channel); static void send_msg_chansess_exitstatus(const struct Channel * channel, @@ -77,9 +77,13 @@ extern char** environ; /* Returns whether the channel is ready to close. The child process must not be running (has never started, or has exited) */ -static int sesscheckclose(const struct Channel *channel) { +static int sesscheckclose(struct Channel *channel) { struct ChanSess *chansess = (struct ChanSess*)channel->typedata; TRACE(("sesscheckclose, pid %d, exitpid %d", chansess->pid, chansess->exit.exitpid)) + + if (chansess->exit.exitpid != -1) { + channel->flushing = 1; + } return chansess->pid == 0 || chansess->exit.exitpid != -1; }