From 3bce6047b50a6497efdfbe8e4378c311bcbb8980 Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Mon, 27 Aug 2012 00:49:45 +0200 Subject: [PATCH] dom0/qrexec: properly process data after client terminated one way of transfer Instead of removing client from list at EPIPE error from write, assume that client does not wish read future data, but still can write something. --- qrexec/qrexec_client.c | 20 ++++++++++++++++++-- qrexec/qrexec_daemon.c | 31 +++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index 81550c2c..b4d1835b 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -116,8 +116,24 @@ void handle_input(int s) } } if (!write_all(s, buf, ret)) { - perror("write daemon"); - do_exit(1); + if (errno == EPIPE) { + // daemon disconnected its end of socket, so no future data will be + // send there; there is no sense to read from child stdout + // + // since AF_UNIX socket is buffered it doesn't mean all data was + // received from the agent + close(local_stdout_fd); + local_stdout_fd = -1; + if (local_stdin_fd == -1) { + // since child does no longer accept data on its stdin, doesn't + // make sense to process the data from the daemon + // + // we don't know real exit VM process code (exiting here, before + // MSG_SERVER_TO_CLIENT_EXIT_CODE message) + do_exit(1); + } + } else + perror("write daemon"); } } diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index ef69289b..14e5cbcc 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -38,8 +38,10 @@ enum client_flags { CLIENT_INVALID = 0, // table slot not used CLIENT_CMDLINE = 1, // waiting for cmdline from client CLIENT_DATA = 2, // waiting for data from client - CLIENT_DONT_READ = 4, // don't read from the client, the other side pipe is full, or EOF - CLIENT_OUTQ_FULL = 8 // don't write to client, its stdin pipe is full + CLIENT_DONT_READ = 4, // don't read from the client, the other side pipe is full, or EOF (additionally marked with CLIENT_EOF) + CLIENT_OUTQ_FULL = 8, // don't write to client, its stdin pipe is full + CLIENT_EOF = 16, // got EOF + CLIENT_EXITED = 32 // only send remaining data from client and remove from list }; struct _client { @@ -170,7 +172,7 @@ void terminate_client_and_flush_data(int fd) int i; struct server_header s_hdr; - if (fork_and_flush_stdin(fd, &clients[fd].buffer)) + if (!(clients[fd].state & CLIENT_EXITED) && fork_and_flush_stdin(fd, &clients[fd].buffer)) children_count++; close(fd); clients[fd].state = CLIENT_INVALID; @@ -266,7 +268,10 @@ void handle_message_from_client(int fd) write_all_vchan_ext(&s_hdr, sizeof(s_hdr)); write_all_vchan_ext(buf, ret); if (ret == 0) // EOF - so don't select() on this client - clients[fd].state |= CLIENT_DONT_READ; + clients[fd].state |= CLIENT_DONT_READ | CLIENT_EOF; + if (clients[fd].state & CLIENT_EXITED) + //client already exited and all data sent - cleanup now + terminate_client_and_flush_data(fd); } /* @@ -282,7 +287,14 @@ void write_buffered_data_to_client(int client_id) clients[client_id].state &= ~CLIENT_OUTQ_FULL; break; case WRITE_STDIN_ERROR: - terminate_client_and_flush_data(client_id); + // do not write to this fd anymore + clients[client_id].state |= CLIENT_EXITED; + if (clients[client_id].state & CLIENT_EOF) + terminate_client_and_flush_data(client_id); + else + // client will be removed when read returns 0 (EOF) + // clear CLIENT_OUTQ_FULL flag to no select on this fd anymore + clients[client_id].state &= ~CLIENT_OUTQ_FULL; break; case WRITE_STDIN_BUFFERED: // no room for all data, don't clear CLIENT_OUTQ_FULL flag break; @@ -305,6 +317,9 @@ void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_ /* make both the header and data be consecutive in the buffer */ *(struct client_header *) buf = *hdr; read_all_vchan_ext(buf + sizeof(*hdr), len); + if (clients[client_id].state & CLIENT_EXITED) + // ignore data for no longer running client + return; switch (write_stdin (client_id, client_id, buf, len + sizeof(*hdr), @@ -315,7 +330,11 @@ void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_ clients[client_id].state |= CLIENT_OUTQ_FULL; break; case WRITE_STDIN_ERROR: - terminate_client_and_flush_data(client_id); + // do not write to this fd anymore + clients[client_id].state |= CLIENT_EXITED; + // if already got EOF, remove client + if (clients[client_id].state & CLIENT_EOF) + terminate_client_and_flush_data(client_id); break; default: fprintf(stderr, "unknown write_stdin?\n");