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
This commit is contained in:
parent
632522b35e
commit
6a44eaeb09
@ -12,7 +12,7 @@ endif
|
|||||||
|
|
||||||
|
|
||||||
all: libqrexec-utils.so.$(SO_VER) libqubes-rpc-filecopy.so.$(SO_VER)
|
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)
|
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^ $(VCHANLIBS)
|
||||||
libqubes-rpc-filecopy.so.$(SO_VER): ioall.o copy-file.o crc32.o unpack.o
|
libqubes-rpc-filecopy.so.$(SO_VER): ioall.o copy-file.o crc32.o unpack.o
|
||||||
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^
|
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^
|
||||||
|
@ -28,6 +28,11 @@ struct buffer {
|
|||||||
int buflen;
|
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 *);
|
typedef void (do_exec_t)(const char *);
|
||||||
void register_exec_func(do_exec_t *func);
|
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);
|
int buffer_len(struct buffer *b);
|
||||||
void *buffer_data(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,
|
void do_fork_exec(const char *cmdline, int *pid, int *stdin_fd, int *stdout_fd,
|
||||||
int *stderr_fd);
|
int *stderr_fd);
|
||||||
|
@ -29,16 +29,18 @@
|
|||||||
#include "libqrexec-utils.h"
|
#include "libqrexec-utils.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
There is buffered data in "buffer" for client id "client_id", and select()
|
There is buffered data in "buffer" for client and select()
|
||||||
reports that "fd" is writable. Write as much as possible to fd, if all sent,
|
reports that "fd" is writable. Write as much as possible to fd.
|
||||||
notify the peer that this client's pipe is no longer full.
|
|
||||||
*/
|
*/
|
||||||
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 ret;
|
||||||
int len;
|
int len;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
len = buffer_len(buffer);
|
len = buffer_len(buffer);
|
||||||
|
if (!len) {
|
||||||
|
return WRITE_STDIN_OK;
|
||||||
|
}
|
||||||
if (len > MAX_DATA_CHUNK)
|
if (len > MAX_DATA_CHUNK)
|
||||||
len = MAX_DATA_CHUNK;
|
len = MAX_DATA_CHUNK;
|
||||||
ret = write(fd, buffer_data(buffer), len);
|
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
|
// it will be wrong if we change MAX_DATA_CHUNK to something large
|
||||||
// as pipes writes are atomic only to PIPE_MAX limit
|
// as pipes writes are atomic only to PIPE_MAX limit
|
||||||
buffer_remove(buffer, ret);
|
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
|
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
|
to "buffer".
|
||||||
MSG_XOFF message.
|
|
||||||
*/
|
*/
|
||||||
int write_stdin(libvchan_t *vchan, int fd, int client_id, const char *data, int len,
|
int write_stdin(int fd, const char *data, int len, struct buffer *buffer)
|
||||||
struct buffer *buffer)
|
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
int written = 0;
|
int written = 0;
|
||||||
@ -88,26 +78,17 @@ int write_stdin(libvchan_t *vchan, int fd, int client_id, const char *data, int
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
struct server_header s_hdr;
|
|
||||||
|
|
||||||
if (errno != EAGAIN)
|
if (errno != EAGAIN)
|
||||||
return WRITE_STDIN_ERROR;
|
return WRITE_STDIN_ERROR;
|
||||||
|
|
||||||
buffer_append(buffer, data + written,
|
buffer_append(buffer, data + written,
|
||||||
len - 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;
|
return WRITE_STDIN_BUFFERED;
|
||||||
}
|
}
|
||||||
written += ret;
|
written += ret;
|
||||||
}
|
}
|
||||||
return WRITE_STDIN_OK;
|
return WRITE_STDIN_OK;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
Reference in New Issue
Block a user