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.
This commit is contained in:
Marek Marczykowski 2012-08-27 00:49:45 +02:00
parent f79101d114
commit 3bce6047b5
2 changed files with 43 additions and 8 deletions

View File

@ -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");
}
}

View File

@ -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");