libqrexec-utils: bring back buffered write helpers

It is required to prevent deadlocks in single-threaded select-based IO
programs (namely: qrexec). POSIX API doesn't support checking how much
can be written to pipe/socket without blocking, so to prevent blocking
application must use O_NONBLOCK mode, and somehow deal with non-written
data (buffer it).

QubesOS/qubes-issues#1347

(cherry picked from commit 6a44eaeb09)
This commit is contained in:
Marek Marczykowski-Górecki 2015-10-24 20:22:24 +02:00
parent aa6e51f369
commit 61c3357ce1
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
3 changed files with 17 additions and 28 deletions

View File

@ -12,7 +12,7 @@ endif
all: libqrexec-utils.so.$(SO_VER) libqubes-rpc-filecopy.so.$(SO_VER)
libqrexec-utils.so.$(SO_VER): unix-server.o ioall.o buffer.o exec.o txrx-vchan.o
libqrexec-utils.so.$(SO_VER): unix-server.o ioall.o buffer.o exec.o txrx-vchan.o write-stdin.o
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^ $(VCHANLIBS)
libqubes-rpc-filecopy.so.$(SO_VER): ioall.o copy-file.o crc32.o unpack.o
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^

View File

@ -28,6 +28,11 @@ struct buffer {
int buflen;
};
/* return codes for buffered writes */
#define WRITE_STDIN_OK 0 /* all written */
#define WRITE_STDIN_BUFFERED 1 /* something still in the buffer */
#define WRITE_STDIN_ERROR 2 /* write error, errno set */
typedef void (do_exec_t)(const char *);
void register_exec_func(do_exec_t *func);
@ -38,6 +43,9 @@ void buffer_remove(struct buffer *b, int len);
int buffer_len(struct buffer *b);
void *buffer_data(struct buffer *b);
int flush_client_data(int fd, struct buffer *buffer);
int write_stdin(int fd, const char *data, int len, struct buffer *buffer);
int fork_and_flush_stdin(int fd, struct buffer *buffer);
void do_fork_exec(const char *cmdline, int *pid, int *stdin_fd, int *stdout_fd,
int *stderr_fd);

View File

@ -29,16 +29,18 @@
#include "libqrexec-utils.h"
/*
There is buffered data in "buffer" for client id "client_id", and select()
reports that "fd" is writable. Write as much as possible to fd, if all sent,
notify the peer that this client's pipe is no longer full.
There is buffered data in "buffer" for client and select()
reports that "fd" is writable. Write as much as possible to fd.
*/
int flush_client_data(libvchan_t *vchan, int fd, int client_id, struct buffer *buffer)
int flush_client_data(int fd, struct buffer *buffer)
{
int ret;
int len;
for (;;) {
len = buffer_len(buffer);
if (!len) {
return WRITE_STDIN_OK;
}
if (len > MAX_DATA_CHUNK)
len = MAX_DATA_CHUNK;
ret = write(fd, buffer_data(buffer), len);
@ -52,27 +54,15 @@ int flush_client_data(libvchan_t *vchan, int fd, int client_id, struct buffer *b
// it will be wrong if we change MAX_DATA_CHUNK to something large
// as pipes writes are atomic only to PIPE_MAX limit
buffer_remove(buffer, ret);
len = buffer_len(buffer);
if (!len) {
struct server_header s_hdr;
s_hdr.type = MSG_XON;
s_hdr.client_id = client_id;
s_hdr.len = 0;
if (libvchan_send(vchan, (char*)&s_hdr, sizeof s_hdr) < 0)
return WRITE_STDIN_ERROR;
return WRITE_STDIN_OK;
}
}
}
/*
Write "len" bytes from "data" to "fd". If not all written, buffer the rest
to "buffer", and notify the peer that the client "client_id" pipe is full via
MSG_XOFF message.
to "buffer".
*/
int write_stdin(libvchan_t *vchan, int fd, int client_id, const char *data, int len,
struct buffer *buffer)
int write_stdin(int fd, const char *data, int len, struct buffer *buffer)
{
int ret;
int written = 0;
@ -88,26 +78,17 @@ int write_stdin(libvchan_t *vchan, int fd, int client_id, const char *data, int
exit(1);
}
if (ret == -1) {
struct server_header s_hdr;
if (errno != EAGAIN)
return WRITE_STDIN_ERROR;
buffer_append(buffer, data + written,
len - written);
s_hdr.type = MSG_XOFF;
s_hdr.client_id = client_id;
s_hdr.len = 0;
if (libvchan_send(vchan, (char*)&s_hdr, sizeof s_hdr) < 0)
return WRITE_STDIN_ERROR;
return WRITE_STDIN_BUFFERED;
}
written += ret;
}
return WRITE_STDIN_OK;
}
/*