Unix: turn LocalProxySocket into a general FdSocket.

The new FdSocket just takes an arbitrary pair of file descriptors to
read and write, optionally with an extra input fd providing the
standard error output from a command. uxproxy.c now just does the
forking and pipe setup, and once it's got all its fds, it hands off to
FdSocket to actually do the reading and writing.

This is very like the reorganisation I did on the Windows side in
commit 98a6a3553 (back in 2013, in preparation for named-pipe sockets
and connection sharing). The idea is that it should enable me to make
a thing that the PuTTY code base sees as a Socket, but which actually
connects to the standard I/O handles of the process it lives in.
This commit is contained in:
Simon Tatham 2018-10-07 14:55:32 +01:00
Родитель 7de8801e73
Коммит c95b277798
4 изменённых файлов: 389 добавлений и 350 удалений

3
Recipe
Просмотреть файл

@ -270,7 +270,8 @@ MISC = misc marshal
MISCNET = timing callback MISC version settings tree234 proxy CONF be_misc
WINMISC = MISCNET winstore winnet winhandl cmdline windefs winmisc winproxy
+ wintime winhsock errsock winsecur winucs miscucs
UXMISC = MISCNET uxstore uxsel uxnet uxpeer uxmisc uxproxy errsock time
UXMISC = MISCNET uxstore uxsel uxnet uxpeer uxmisc time
+ uxproxy uxfdsock errsock
# import.c and dependencies, for PuTTYgen-like utilities that have to
# load foreign key files.

Просмотреть файл

@ -344,6 +344,11 @@ extern const struct BackendVtable serial_backend;
*/
int so_peercred(int fd, int *pid, int *uid, int *gid);
/*
* uxfdsock.c.
*/
Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug);
/*
* Default font setting, which can vary depending on NOT_X_WINDOWS.
*/

364
unix/uxfdsock.c Normal file
Просмотреть файл

@ -0,0 +1,364 @@
/*
* uxfdsick.c: implementation of Socket that just talks to two
* existing input and output file descriptors.
*/
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include "tree234.h"
#include "putty.h"
#include "network.h"
typedef struct FdSocket {
int outfd, infd, inerrfd;
bufchain pending_output_data;
bufchain pending_input_data;
bufchain pending_input_error_data;
enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
int pending_error;
Plug *plug;
Socket sock;
} FdSocket;
static void fdsocket_select_result_input(int fd, int event);
static void fdsocket_select_result_output(int fd, int event);
static void fdsocket_select_result_input_error(int fd, int event);
/*
* Trees to look up the fds in.
*/
static tree234 *fdsocket_by_outfd;
static tree234 *fdsocket_by_infd;
static tree234 *fdsocket_by_inerrfd;
static int fdsocket_infd_cmp(void *av, void *bv)
{
FdSocket *a = (FdSocket *)av;
FdSocket *b = (FdSocket *)bv;
if (a->infd < b->infd)
return -1;
if (a->infd > b->infd)
return +1;
return 0;
}
static int fdsocket_infd_find(void *av, void *bv)
{
int a = *(int *)av;
FdSocket *b = (FdSocket *)bv;
if (a < b->infd)
return -1;
if (a > b->infd)
return +1;
return 0;
}
static int fdsocket_inerrfd_cmp(void *av, void *bv)
{
FdSocket *a = (FdSocket *)av;
FdSocket *b = (FdSocket *)bv;
if (a->inerrfd < b->inerrfd)
return -1;
if (a->inerrfd > b->inerrfd)
return +1;
return 0;
}
static int fdsocket_inerrfd_find(void *av, void *bv)
{
int a = *(int *)av;
FdSocket *b = (FdSocket *)bv;
if (a < b->inerrfd)
return -1;
if (a > b->inerrfd)
return +1;
return 0;
}
static int fdsocket_outfd_cmp(void *av, void *bv)
{
FdSocket *a = (FdSocket *)av;
FdSocket *b = (FdSocket *)bv;
if (a->outfd < b->outfd)
return -1;
if (a->outfd > b->outfd)
return +1;
return 0;
}
static int fdsocket_outfd_find(void *av, void *bv)
{
int a = *(int *)av;
FdSocket *b = (FdSocket *)bv;
if (a < b->outfd)
return -1;
if (a > b->outfd)
return +1;
return 0;
}
static Plug *fdsocket_plug(Socket *s, Plug *p)
{
FdSocket *fds = container_of(s, FdSocket, sock);
Plug *ret = fds->plug;
if (p)
fds->plug = p;
return ret;
}
static void fdsocket_close(Socket *s)
{
FdSocket *fds = container_of(s, FdSocket, sock);
if (fds->outfd >= 0) {
del234(fdsocket_by_outfd, fds);
uxsel_del(fds->outfd);
close(fds->outfd);
}
if (fds->infd >= 0) {
del234(fdsocket_by_infd, fds);
uxsel_del(fds->infd);
close(fds->infd);
}
if (fds->inerrfd >= 0) {
del234(fdsocket_by_inerrfd, fds);
uxsel_del(fds->inerrfd);
close(fds->inerrfd);
}
bufchain_clear(&fds->pending_input_data);
bufchain_clear(&fds->pending_output_data);
delete_callbacks_for_context(fds);
sfree(fds);
}
static void fdsocket_error_callback(void *vs)
{
FdSocket *fds = (FdSocket *)vs;
/*
* Just in case other socket work has caused this socket to vanish
* or become somehow non-erroneous before this callback arrived...
*/
if (!fds->pending_error)
return;
/*
* An error has occurred on this socket. Pass it to the plug.
*/
plug_closing(fds->plug, strerror(fds->pending_error),
fds->pending_error, 0);
}
static int fdsocket_try_send(FdSocket *fds)
{
int sent = 0;
while (bufchain_size(&fds->pending_output_data) > 0) {
void *data;
int len, ret;
bufchain_prefix(&fds->pending_output_data, &data, &len);
ret = write(fds->outfd, data, len);
if (ret < 0 && errno != EWOULDBLOCK) {
if (!fds->pending_error) {
fds->pending_error = errno;
queue_toplevel_callback(fdsocket_error_callback, fds);
}
return 0;
} else if (ret <= 0) {
break;
} else {
bufchain_consume(&fds->pending_output_data, ret);
sent += ret;
}
}
if (fds->outgoingeof == EOF_PENDING) {
del234(fdsocket_by_outfd, fds);
close(fds->outfd);
uxsel_del(fds->outfd);
fds->outfd = -1;
fds->outgoingeof = EOF_SENT;
}
if (bufchain_size(&fds->pending_output_data) == 0)
uxsel_del(fds->outfd);
else
uxsel_set(fds->outfd, 2, fdsocket_select_result_output);
return sent;
}
static int fdsocket_write(Socket *s, const void *data, int len)
{
FdSocket *fds = container_of(s, FdSocket, sock);
assert(fds->outgoingeof == EOF_NO);
bufchain_add(&fds->pending_output_data, data, len);
fdsocket_try_send(fds);
return bufchain_size(&fds->pending_output_data);
}
static int fdsocket_write_oob(Socket *s, const void *data, int len)
{
/*
* oob data is treated as inband; nasty, but nothing really
* better we can do
*/
return fdsocket_write(s, data, len);
}
static void fdsocket_write_eof(Socket *s)
{
FdSocket *fds = container_of(s, FdSocket, sock);
assert(fds->outgoingeof == EOF_NO);
fds->outgoingeof = EOF_PENDING;
fdsocket_try_send(fds);
}
static void fdsocket_flush(Socket *s)
{
/* FdSocket *fds = container_of(s, FdSocket, sock); */
/* do nothing */
}
static void fdsocket_set_frozen(Socket *s, int is_frozen)
{
FdSocket *fds = container_of(s, FdSocket, sock);
if (fds->infd < 0)
return;
if (is_frozen)
uxsel_del(fds->infd);
else
uxsel_set(fds->infd, 1, fdsocket_select_result_input);
}
static const char *fdsocket_socket_error(Socket *s)
{
return NULL;
}
static void fdsocket_select_result_input(int fd, int event)
{
FdSocket *fds;
char buf[20480];
int retd;
if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find)))
return;
retd = read(fds->infd, buf, sizeof(buf));
if (retd > 0) {
plug_receive(fds->plug, 0, buf, retd);
} else {
if (retd < 0) {
plug_closing(fds->plug, strerror(errno), errno, 0);
} else {
plug_closing(fds->plug, NULL, 0, 0);
}
del234(fdsocket_by_infd, fds);
uxsel_del(fds->infd);
close(fds->infd);
fds->infd = -1;
}
}
static void fdsocket_select_result_output(int fd, int event)
{
FdSocket *fds;
if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find)))
return;
if (fdsocket_try_send(fds))
plug_sent(fds->plug, bufchain_size(&fds->pending_output_data));
}
static void fdsocket_select_result_input_error(int fd, int event)
{
FdSocket *fds;
char buf[20480];
int retd;
if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find)))
return;
retd = read(fd, buf, sizeof(buf));
if (retd > 0) {
log_proxy_stderr(fds->plug, &fds->pending_input_error_data, buf, retd);
} else {
del234(fdsocket_by_inerrfd, fds);
uxsel_del(fds->inerrfd);
close(fds->inerrfd);
fds->inerrfd = -1;
}
}
static const SocketVtable FdSocket_sockvt = {
fdsocket_plug,
fdsocket_close,
fdsocket_write,
fdsocket_write_oob,
fdsocket_write_eof,
fdsocket_flush,
fdsocket_set_frozen,
fdsocket_socket_error,
NULL, /* peer_info */
};
Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug)
{
FdSocket *fds;
fds = snew(FdSocket);
fds->sock.vt = &FdSocket_sockvt;
fds->plug = plug;
fds->outgoingeof = EOF_NO;
fds->pending_error = 0;
fds->infd = infd;
fds->outfd = outfd;
fds->inerrfd = inerrfd;
bufchain_init(&fds->pending_input_data);
bufchain_init(&fds->pending_output_data);
bufchain_init(&fds->pending_input_error_data);
if (fds->outfd >= 0) {
if (!fdsocket_by_outfd)
fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp);
add234(fdsocket_by_outfd, fds);
}
if (fds->infd >= 0) {
if (!fdsocket_by_infd)
fdsocket_by_infd = newtree234(fdsocket_infd_cmp);
add234(fdsocket_by_infd, fds);
uxsel_set(fds->infd, 1, fdsocket_select_result_input);
}
if (fds->inerrfd >= 0) {
assert(fds->inerrfd != fds->infd);
if (!fdsocket_by_inerrfd)
fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp);
add234(fdsocket_by_inerrfd, fds);
uxsel_set(fds->inerrfd, 1, fdsocket_select_result_input_error);
}
return &fds->sock;
}

Просмотреть файл

@ -14,306 +14,6 @@
#include "network.h"
#include "proxy.h"
typedef struct LocalProxySocket {
int to_cmd, from_cmd, cmd_err; /* fds */
char *error;
Plug *plug;
bufchain pending_output_data;
bufchain pending_input_data;
bufchain pending_error_data;
enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
int pending_error;
Socket sock;
} LocalProxySocket;
static void localproxy_select_result(int fd, int event);
/*
* Trees to look up the pipe fds in.
*/
static tree234 *localproxy_by_fromfd;
static tree234 *localproxy_by_tofd;
static tree234 *localproxy_by_errfd;
static int localproxy_fromfd_cmp(void *av, void *bv)
{
LocalProxySocket *a = (LocalProxySocket *)av;
LocalProxySocket *b = (LocalProxySocket *)bv;
if (a->from_cmd < b->from_cmd)
return -1;
if (a->from_cmd > b->from_cmd)
return +1;
return 0;
}
static int localproxy_fromfd_find(void *av, void *bv)
{
int a = *(int *)av;
LocalProxySocket *b = (LocalProxySocket *)bv;
if (a < b->from_cmd)
return -1;
if (a > b->from_cmd)
return +1;
return 0;
}
static int localproxy_tofd_cmp(void *av, void *bv)
{
LocalProxySocket *a = (LocalProxySocket *)av;
LocalProxySocket *b = (LocalProxySocket *)bv;
if (a->to_cmd < b->to_cmd)
return -1;
if (a->to_cmd > b->to_cmd)
return +1;
return 0;
}
static int localproxy_tofd_find(void *av, void *bv)
{
int a = *(int *)av;
LocalProxySocket *b = (LocalProxySocket *)bv;
if (a < b->to_cmd)
return -1;
if (a > b->to_cmd)
return +1;
return 0;
}
static int localproxy_errfd_cmp(void *av, void *bv)
{
LocalProxySocket *a = (LocalProxySocket *)av;
LocalProxySocket *b = (LocalProxySocket *)bv;
if (a->cmd_err < b->cmd_err)
return -1;
if (a->cmd_err > b->cmd_err)
return +1;
return 0;
}
static int localproxy_errfd_find(void *av, void *bv)
{
int a = *(int *)av;
LocalProxySocket *b = (LocalProxySocket *)bv;
if (a < b->cmd_err)
return -1;
if (a > b->cmd_err)
return +1;
return 0;
}
/* basic proxy socket functions */
static Plug *sk_localproxy_plug (Socket *s, Plug *p)
{
LocalProxySocket *ps = container_of(s, LocalProxySocket, sock);
Plug *ret = ps->plug;
if (p)
ps->plug = p;
return ret;
}
static void sk_localproxy_close (Socket *s)
{
LocalProxySocket *ps = container_of(s, LocalProxySocket, sock);
if (ps->to_cmd >= 0) {
del234(localproxy_by_tofd, ps);
uxsel_del(ps->to_cmd);
close(ps->to_cmd);
}
if (ps->from_cmd >= 0) {
del234(localproxy_by_fromfd, ps);
uxsel_del(ps->from_cmd);
close(ps->from_cmd);
}
if (ps->cmd_err >= 0) {
del234(localproxy_by_errfd, ps);
uxsel_del(ps->cmd_err);
close(ps->cmd_err);
}
bufchain_clear(&ps->pending_input_data);
bufchain_clear(&ps->pending_output_data);
bufchain_clear(&ps->pending_error_data);
delete_callbacks_for_context(ps);
sfree(ps);
}
static void localproxy_error_callback(void *vs)
{
LocalProxySocket *ps = (LocalProxySocket *)vs;
/*
* Just in case other socket work has caused this socket to vanish
* or become somehow non-erroneous before this callback arrived...
*/
if (!ps->pending_error)
return;
/*
* An error has occurred on this socket. Pass it to the plug.
*/
plug_closing(ps->plug, strerror(ps->pending_error), ps->pending_error, 0);
}
static int localproxy_try_send(LocalProxySocket *ps)
{
int sent = 0;
while (bufchain_size(&ps->pending_output_data) > 0) {
void *data;
int len, ret;
bufchain_prefix(&ps->pending_output_data, &data, &len);
ret = write(ps->to_cmd, data, len);
if (ret < 0 && errno != EWOULDBLOCK) {
if (!ps->pending_error) {
ps->pending_error = errno;
queue_toplevel_callback(localproxy_error_callback, ps);
}
return 0;
} else if (ret <= 0) {
break;
} else {
bufchain_consume(&ps->pending_output_data, ret);
sent += ret;
}
}
if (ps->outgoingeof == EOF_PENDING) {
del234(localproxy_by_tofd, ps);
close(ps->to_cmd);
uxsel_del(ps->to_cmd);
ps->to_cmd = -1;
ps->outgoingeof = EOF_SENT;
}
if (bufchain_size(&ps->pending_output_data) == 0)
uxsel_del(ps->to_cmd);
else
uxsel_set(ps->to_cmd, 2, localproxy_select_result);
return sent;
}
static int sk_localproxy_write (Socket *s, const void *data, int len)
{
LocalProxySocket *ps = container_of(s, LocalProxySocket, sock);
assert(ps->outgoingeof == EOF_NO);
bufchain_add(&ps->pending_output_data, data, len);
localproxy_try_send(ps);
return bufchain_size(&ps->pending_output_data);
}
static int sk_localproxy_write_oob (Socket *s, const void *data, int len)
{
/*
* oob data is treated as inband; nasty, but nothing really
* better we can do
*/
return sk_localproxy_write(s, data, len);
}
static void sk_localproxy_write_eof (Socket *s)
{
LocalProxySocket *ps = container_of(s, LocalProxySocket, sock);
assert(ps->outgoingeof == EOF_NO);
ps->outgoingeof = EOF_PENDING;
localproxy_try_send(ps);
}
static void sk_localproxy_flush (Socket *s)
{
/* LocalProxySocket *ps = container_of(s, LocalProxySocket, sock); */
/* do nothing */
}
static void sk_localproxy_set_frozen (Socket *s, int is_frozen)
{
LocalProxySocket *ps = container_of(s, LocalProxySocket, sock);
if (ps->from_cmd < 0)
return;
if (is_frozen)
uxsel_del(ps->from_cmd);
else
uxsel_set(ps->from_cmd, 1, localproxy_select_result);
}
static const char * sk_localproxy_socket_error (Socket *s)
{
LocalProxySocket *ps = container_of(s, LocalProxySocket, sock);
return ps->error;
}
static void localproxy_select_result(int fd, int event)
{
LocalProxySocket *s;
char buf[20480];
int ret;
if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
!(s = find234(localproxy_by_errfd, &fd, localproxy_errfd_find)) &&
!(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
return; /* boggle */
if (event == 1) {
if (fd == s->cmd_err) {
ret = read(fd, buf, sizeof(buf));
if (ret > 0) {
log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret);
} else {
del234(localproxy_by_errfd, s);
uxsel_del(s->cmd_err);
close(s->cmd_err);
s->cmd_err = -1;
}
} else {
assert(fd == s->from_cmd);
ret = read(fd, buf, sizeof(buf));
if (ret > 0) {
plug_receive(s->plug, 0, buf, ret);
} else {
if (ret < 0) {
plug_closing(s->plug, strerror(errno), errno, 0);
} else {
plug_closing(s->plug, NULL, 0, 0);
}
del234(localproxy_by_fromfd, s);
uxsel_del(s->from_cmd);
close(s->from_cmd);
s->from_cmd = -1;
}
}
} else if (event == 2) {
assert(fd == s->to_cmd);
if (localproxy_try_send(s))
plug_sent(s->plug, bufchain_size(&s->pending_output_data));
}
}
static const SocketVtable LocalProxySocket_sockvt = {
sk_localproxy_plug,
sk_localproxy_close,
sk_localproxy_write,
sk_localproxy_write_oob,
sk_localproxy_write_eof,
sk_localproxy_flush,
sk_localproxy_set_frozen,
sk_localproxy_socket_error,
NULL, /* peer_info */
};
Socket *platform_new_connection(SockAddr *addr, const char *hostname,
int port, int privport,
int oobinline, int nodelay, int keepalive,
@ -321,24 +21,13 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname,
{
char *cmd;
LocalProxySocket *ret;
int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
int infd, outfd, inerrfd;
proxytype = conf_get_int(conf, CONF_proxy_type);
if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
return NULL;
ret = snew(LocalProxySocket);
ret->sock.vt = &LocalProxySocket_sockvt;
ret->plug = plug;
ret->error = NULL;
ret->outgoingeof = EOF_NO;
ret->pending_error = 0;
bufchain_init(&ret->pending_input_data);
bufchain_init(&ret->pending_output_data);
bufchain_init(&ret->pending_error_data);
if (proxytype == PROXY_CMD) {
cmd = format_telnet_command(addr, port, conf);
@ -355,21 +44,15 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname,
if (pipe(to_cmd_pipe) < 0 ||
pipe(from_cmd_pipe) < 0 ||
pipe(cmd_err_pipe) < 0) {
ret->error = dupprintf("pipe: %s", strerror(errno));
sfree(cmd);
return &ret->sock;
return new_error_socket_fmt(plug, "pipe: %s", strerror(errno));
}
cloexec(to_cmd_pipe[1]);
cloexec(from_cmd_pipe[0]);
cloexec(cmd_err_pipe[0]);
pid = fork();
if (pid < 0) {
ret->error = dupprintf("fork: %s", strerror(errno));
sfree(cmd);
return &ret->sock;
} else if (pid == 0) {
if (pid == 0) {
close(0);
close(1);
dup2(to_cmd_pipe[0], 0);
@ -385,49 +68,35 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname,
sfree(cmd);
if (pid < 0)
return new_error_socket_fmt(plug, "fork: %s", strerror(errno));
close(to_cmd_pipe[0]);
close(from_cmd_pipe[1]);
close(cmd_err_pipe[1]);
ret->to_cmd = to_cmd_pipe[1];
ret->from_cmd = from_cmd_pipe[0];
ret->cmd_err = cmd_err_pipe[0];
outfd = to_cmd_pipe[1];
infd = from_cmd_pipe[0];
inerrfd = cmd_err_pipe[0];
} else {
cmd = format_telnet_command(addr, port, conf);
ret->to_cmd = open("/dev/null", O_WRONLY);
if (ret->to_cmd == -1) {
ret->error = dupprintf("/dev/null: %s", strerror(errno));
outfd = open("/dev/null", O_WRONLY);
if (outfd == -1) {
sfree(cmd);
return &ret->sock;
return new_error_socket_fmt(
plug, "/dev/null: %s", strerror(errno));
}
ret->from_cmd = open(cmd, O_RDONLY);
if (ret->from_cmd == -1) {
ret->error = dupprintf("%s: %s", cmd, strerror(errno));
infd = open(cmd, O_RDONLY);
if (infd == -1) {
sfree(cmd);
return &ret->sock;
return new_error_socket_fmt(plug, "%s: %s", cmd, strerror(errno));
}
sfree(cmd);
ret->cmd_err = -1;
inerrfd = -1;
}
if (!localproxy_by_fromfd)
localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
if (!localproxy_by_tofd)
localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
if (!localproxy_by_errfd)
localproxy_by_errfd = newtree234(localproxy_errfd_cmp);
add234(localproxy_by_fromfd, ret);
add234(localproxy_by_tofd, ret);
if (ret->cmd_err >= 0)
add234(localproxy_by_errfd, ret);
uxsel_set(ret->from_cmd, 1, localproxy_select_result);
if (ret->cmd_err >= 0)
uxsel_set(ret->cmd_err, 1, localproxy_select_result);
/* We are responsible for this and don't need it any more */
sk_addr_free(addr);
return &ret->sock;
return make_fd_socket(infd, outfd, inerrfd, plug);
}