--- /dev/null
+From 1962e6128a4d86a7c54977577e1e4224cadbb5f7 Mon Sep 17 00:00:00 2001
+From: Alexander Scheel <ascheel@redhat.com>
+Date: Wed, 2 Aug 2017 15:11:49 -0400
+Subject: [PATCH] [client] Switch to non-blocking sockets
+
+Switch the gssproxy client library to non-blocking sockets, allowing
+for timeout and retry operations. The client will automatically retry
+both send() and recv() operations three times on ETIMEDOUT. If the
+combined send() and recv() hit the three time limit, ETIMEDOUT will be
+exposed to the caller in the minor status.
+
+Signed-off-by: Alexander Scheel <ascheel@redhat.com>
+Reviewed-by: Simo Sorce <simo@redhat.com>
+[rharwood@redhat.com: commit message cleanups, rebased]
+Reviewed-by: Robbie Harwood <rharwood@redhat.com>
+(cherry picked from commit d035646c8feb0b78f0c157580ca02c46cd00dd7e)
+---
+ proxy/src/client/gpm_common.c | 317 +++++++++++++++++++++++++++++++---
+ 1 file changed, 295 insertions(+), 22 deletions(-)
+
+diff --git a/proxy/src/client/gpm_common.c b/proxy/src/client/gpm_common.c
+index 2133618..dba23a6 100644
+--- a/proxy/src/client/gpm_common.c
++++ b/proxy/src/client/gpm_common.c
+@@ -7,9 +7,15 @@
+ #include <stdlib.h>
+ #include <time.h>
+ #include <pthread.h>
++#include <sys/epoll.h>
++#include <fcntl.h>
++#include <sys/timerfd.h>
+
+ #define FRAGMENT_BIT (1 << 31)
+
++#define RESPONSE_TIMEOUT 15
++#define MAX_TIMEOUT_RETRY 3
++
+ struct gpm_ctx {
+ pthread_mutex_t lock;
+ int fd;
+@@ -20,6 +26,9 @@ struct gpm_ctx {
+ gid_t gid;
+
+ int next_xid;
++
++ int epollfd;
++ int timerfd;
+ };
+
+ /* a single global struct is not particularly efficient,
+@@ -39,6 +48,8 @@ static void gpm_init_once(void)
+ pthread_mutex_init(&gpm_global_ctx.lock, &attr);
+
+ gpm_global_ctx.fd = -1;
++ gpm_global_ctx.epollfd = -1;
++ gpm_global_ctx.timerfd = -1;
+
+ seedp = time(NULL) + getpid() + pthread_self();
+ gpm_global_ctx.next_xid = rand_r(&seedp);
+@@ -69,6 +80,7 @@ static int gpm_open_socket(struct gpm_ctx *gpmctx)
+ struct sockaddr_un addr = {0};
+ char name[PATH_MAX];
+ int ret;
++ unsigned flags;
+ int fd = -1;
+
+ ret = get_pipe_name(name);
+@@ -86,6 +98,18 @@ static int gpm_open_socket(struct gpm_ctx *gpmctx)
+ goto done;
+ }
+
++ ret = fcntl(fd, F_GETFD, &flags);
++ if (ret != 0) {
++ ret = errno;
++ goto done;
++ }
++
++ ret = fcntl(fd, F_SETFD, flags | O_NONBLOCK);
++ if (ret != 0) {
++ ret = errno;
++ goto done;
++ }
++
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret == -1) {
+ ret = errno;
+@@ -163,6 +187,158 @@ static int gpm_release_sock(struct gpm_ctx *gpmctx)
+ return pthread_mutex_unlock(&gpmctx->lock);
+ }
+
++static void gpm_timer_close(struct gpm_ctx *gpmctx) {
++ if (gpmctx->timerfd < 0) {
++ return;
++ }
++
++ close(gpmctx->timerfd);
++ gpmctx->timerfd = -1;
++}
++
++static int gpm_timer_setup(struct gpm_ctx *gpmctx, int timeout_seconds) {
++ int ret;
++ struct itimerspec its;
++
++ if (gpmctx->timerfd >= 0) {
++ gpm_timer_close(gpmctx);
++ }
++
++ gpmctx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
++ if (gpmctx->timerfd < 0) {
++ return errno;
++ }
++
++ its.it_interval.tv_sec = timeout_seconds;
++ its.it_interval.tv_nsec = 0;
++ its.it_value.tv_sec = timeout_seconds;
++ its.it_value.tv_nsec = 0;
++
++ ret = timerfd_settime(gpmctx->timerfd, 0, &its, NULL);
++ if (ret) {
++ ret = errno;
++ gpm_timer_close(gpmctx);
++ return ret;
++ }
++
++ return 0;
++}
++
++static void gpm_epoll_close(struct gpm_ctx *gpmctx) {
++ if (gpmctx->epollfd < 0) {
++ return;
++ }
++
++ close(gpmctx->epollfd);
++ gpmctx->epollfd = -1;
++}
++
++static int gpm_epoll_setup(struct gpm_ctx *gpmctx) {
++ struct epoll_event ev;
++ int ret;
++
++ if (gpmctx->epollfd >= 0) {
++ gpm_epoll_close(gpmctx);
++ }
++
++ gpmctx->epollfd = epoll_create1(EPOLL_CLOEXEC);
++ if (gpmctx->epollfd == -1) {
++ return errno;
++ }
++
++ /* Add timer */
++ ev.events = EPOLLIN;
++ ev.data.fd = gpmctx->timerfd;
++ ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_ADD, gpmctx->timerfd, &ev);
++ if (ret == -1) {
++ ret = errno;
++ gpm_epoll_close(gpmctx);
++ return ret;
++ }
++
++ return ret;
++}
++
++static int gpm_epoll_wait(struct gpm_ctx *gpmctx, uint32_t event_flags) {
++ int ret;
++ int epoll_ret;
++ struct epoll_event ev;
++ struct epoll_event events[2];
++ uint64_t timer_read;
++
++ if (gpmctx->epollfd < 0) {
++ ret = gpm_epoll_setup(gpmctx);
++ if (ret)
++ return ret;
++ }
++
++ ev.events = event_flags;
++ ev.data.fd = gpmctx->fd;
++ epoll_ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_ADD, gpmctx->fd, &ev);
++ if (epoll_ret == -1) {
++ ret = errno;
++ gpm_epoll_close(gpmctx);
++ return ret;
++ }
++
++ do {
++ epoll_ret = epoll_wait(gpmctx->epollfd, events, 2, -1);
++ } while (epoll_ret < 0 && errno == EINTR);
++
++ if (epoll_ret < 0) {
++ /* Error while waiting that isn't EINTR */
++ ret = errno;
++ gpm_epoll_close(gpmctx);
++ } else if (epoll_ret == 0) {
++ /* Shouldn't happen as timeout == -1; treat it like a timeout
++ * occurred. */
++ ret = ETIMEDOUT;
++ gpm_epoll_close(gpmctx);
++ } else if (epoll_ret == 1 && events[0].data.fd == gpmctx->timerfd) {
++ /* Got an event which is only our timer */
++ ret = read(gpmctx->timerfd, &timer_read, sizeof(uint64_t));
++ if (ret == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
++ /* In the case when reading from the timer failed, don't hide the
++ * timer error behind ETIMEDOUT such that it isn't retried */
++ ret = errno;
++ } else {
++ /* If ret == 0, then we definitely timed out. Else, if ret == -1
++ * and errno == EAGAIN or errno == EWOULDBLOCK, we're in a weird
++ * edge case where epoll thinks the timer can be read, but it
++ * is blocking more; treat it like a TIMEOUT and retry, as
++ * nothing around us would handle EAGAIN from timer and retry
++ * it. */
++ ret = ETIMEDOUT;
++ }
++ gpm_epoll_close(gpmctx);
++ } else {
++ /* If ret == 2, then we ignore the timerfd; that way if the next
++ * operation cannot be performed immediately, we timeout and retry.
++ * If ret == 1 and data.fd == gpmctx->fd, return 0. */
++ ret = 0;
++ }
++
++ epoll_ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_DEL, gpmctx->fd, NULL);
++ if (epoll_ret == -1) {
++ /* If we previously had an error, expose that error instead of
++ * clobbering it with errno; else if no error, then assume it is
++ * better to notify of the error deleting the event than it is
++ * to continue. */
++ if (ret == 0)
++ ret = errno;
++ gpm_epoll_close(gpmctx);
++ }
++
++ return ret;
++}
++
++static int gpm_retry_socket(struct gpm_ctx *gpmctx)
++{
++ gpm_epoll_close(gpmctx);
++ gpm_close_socket(gpmctx);
++ return gpm_open_socket(gpmctx);
++}
++
+ /* must be called after the lock has been grabbed */
+ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+ char *buffer, uint32_t length)
+@@ -183,8 +359,13 @@ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+ retry = false;
+ do {
+ do {
++ ret = gpm_epoll_wait(gpmctx, EPOLLOUT);
++ if (ret != 0) {
++ goto done;
++ }
++
+ ret = 0;
+- wn = send(gpmctx->fd, &size, sizeof(uint32_t), MSG_NOSIGNAL);
++ wn = write(gpmctx->fd, &size, sizeof(uint32_t));
+ if (wn == -1) {
+ ret = errno;
+ }
+@@ -192,8 +373,7 @@ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+ if (wn != 4) {
+ /* reopen and retry once */
+ if (retry == false) {
+- gpm_close_socket(gpmctx);
+- ret = gpm_open_socket(gpmctx);
++ ret = gpm_retry_socket(gpmctx);
+ if (ret == 0) {
+ retry = true;
+ continue;
+@@ -208,9 +388,14 @@ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+
+ pos = 0;
+ while (length > pos) {
+- wn = send(gpmctx->fd, buffer + pos, length - pos, MSG_NOSIGNAL);
++ ret = gpm_epoll_wait(gpmctx, EPOLLOUT);
++ if (ret) {
++ goto done;
++ }
++
++ wn = write(gpmctx->fd, buffer + pos, length - pos);
+ if (wn == -1) {
+- if (errno == EINTR) {
++ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+ continue;
+ }
+ ret = errno;
+@@ -231,7 +416,7 @@ done:
+
+ /* must be called after the lock has been grabbed */
+ static int gpm_recv_buffer(struct gpm_ctx *gpmctx,
+- char *buffer, uint32_t *length)
++ char **buffer, uint32_t *length)
+ {
+ uint32_t size;
+ ssize_t rn;
+@@ -239,6 +424,11 @@ static int gpm_recv_buffer(struct gpm_ctx *gpmctx,
+ int ret;
+
+ do {
++ ret = gpm_epoll_wait(gpmctx, EPOLLIN);
++ if (ret) {
++ goto done;
++ }
++
+ ret = 0;
+ rn = read(gpmctx->fd, &size, sizeof(uint32_t));
+ if (rn == -1) {
+@@ -258,11 +448,22 @@ static int gpm_recv_buffer(struct gpm_ctx *gpmctx,
+ goto done;
+ }
+
++ *buffer = malloc(*length);
++ if (*buffer == NULL) {
++ ret = ENOMEM;
++ goto done;
++ }
++
+ pos = 0;
+ while (*length > pos) {
+- rn = read(gpmctx->fd, buffer + pos, *length - pos);
++ ret = gpm_epoll_wait(gpmctx, EPOLLIN);
++ if (ret) {
++ goto done;
++ }
++
++ rn = read(gpmctx->fd, *buffer + pos, *length - pos);
+ if (rn == -1) {
+- if (errno == EINTR) {
++ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+ continue;
+ }
+ ret = errno;
+@@ -281,6 +482,7 @@ done:
+ if (ret) {
+ /* on errors we can only close the fd and return */
+ gpm_close_socket(gpmctx);
++ gpm_epoll_close(gpmctx);
+ }
+ return ret;
+ }
+@@ -309,6 +511,63 @@ static struct gpm_ctx *gpm_get_ctx(void)
+ return &gpm_global_ctx;
+ }
+
++static int gpm_send_recv_loop(struct gpm_ctx *gpmctx, char *send_buffer,
++ uint32_t send_length, char** recv_buffer,
++ uint32_t *recv_length)
++{
++ int ret;
++ int retry_count;
++
++ /* setup timer */
++ ret = gpm_timer_setup(gpmctx, RESPONSE_TIMEOUT);
++ if (ret)
++ return ret;
++
++ for (retry_count = 0; retry_count < MAX_TIMEOUT_RETRY; retry_count++) {
++ /* send to proxy */
++ ret = gpm_send_buffer(gpmctx, send_buffer, send_length);
++
++ if (ret == 0) {
++ /* No error, continue to recv */
++ } else if (ret == ETIMEDOUT) {
++ /* Close and reopen socket before trying again */
++ ret = gpm_retry_socket(gpmctx);
++ if (ret != 0)
++ return ret;
++ ret = ETIMEDOUT;
++
++ /* RETRY entire send */
++ continue;
++ } else {
++ /* Other error */
++ return ret;
++ }
++
++ /* receive answer */
++ ret = gpm_recv_buffer(gpmctx, recv_buffer, recv_length);
++ if (ret == 0) {
++ /* No error */
++ break;
++ } else if (ret == ETIMEDOUT) {
++ /* Close and reopen socket before trying again */
++ ret = gpm_retry_socket(gpmctx);
++
++ /* Free buffer and set it to NULL to prevent free(xdr_reply_ctx) */
++ free(recv_buffer);
++ recv_buffer = NULL;
++
++ if (ret != 0)
++ return ret;
++ ret = ETIMEDOUT;
++ } else {
++ /* Other error */
++ return ret;
++ }
++ }
++
++ return ret;
++}
++
+ OM_uint32 gpm_release_buffer(OM_uint32 *minor_status,
+ gss_buffer_t buffer)
+ {
+@@ -399,15 +658,20 @@ int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res)
+ gp_rpc_msg msg;
+ XDR xdr_call_ctx;
+ XDR xdr_reply_ctx;
+- char buffer[MAX_RPC_SIZE];
+- uint32_t length;
++ char *send_buffer = NULL;
++ char *recv_buffer = NULL;
++ uint32_t send_length;
++ uint32_t recv_length;
+ uint32_t xid;
+ bool xdrok;
+ bool sockgrab = false;
+ int ret;
+
+- xdrmem_create(&xdr_call_ctx, buffer, MAX_RPC_SIZE, XDR_ENCODE);
+- xdrmem_create(&xdr_reply_ctx, buffer, MAX_RPC_SIZE, XDR_DECODE);
++ send_buffer = malloc(MAX_RPC_SIZE);
++ if (send_buffer == NULL)
++ return ENOMEM;
++
++ xdrmem_create(&xdr_call_ctx, send_buffer, MAX_RPC_SIZE, XDR_ENCODE);
+
+ memset(&msg, 0, sizeof(gp_rpc_msg));
+ msg.header.type = GP_RPC_CALL;
+@@ -450,22 +714,22 @@ int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res)
+ goto done;
+ }
+
+- /* send to proxy */
+- ret = gpm_send_buffer(gpmctx, buffer, xdr_getpos(&xdr_call_ctx));
+- if (ret) {
+- goto done;
+- }
++ /* set send_length */
++ send_length = xdr_getpos(&xdr_call_ctx);
+
+- /* receive answer */
+- ret = gpm_recv_buffer(gpmctx, buffer, &length);
+- if (ret) {
++ /* Send request, receive response with timeout */
++ ret = gpm_send_recv_loop(gpmctx, send_buffer, send_length, &recv_buffer,
++ &recv_length);
++ if (ret)
+ goto done;
+- }
+
+ /* release the lock */
+ gpm_release_sock(gpmctx);
+ sockgrab = false;
+
++ /* Create the reply context */
++ xdrmem_create(&xdr_reply_ctx, recv_buffer, recv_length, XDR_DECODE);
++
+ /* decode header */
+ memset(&msg, 0, sizeof(gp_rpc_msg));
+ xdrok = xdr_gp_rpc_msg(&xdr_reply_ctx, &msg);
+@@ -489,12 +753,21 @@ int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res)
+ }
+
+ done:
++ gpm_timer_close(gpmctx);
++ gpm_epoll_close(gpmctx);
++
+ if (sockgrab) {
+ gpm_release_sock(gpmctx);
+ }
+ xdr_free((xdrproc_t)xdr_gp_rpc_msg, (char *)&msg);
+ xdr_destroy(&xdr_call_ctx);
+- xdr_destroy(&xdr_reply_ctx);
++
++ if (recv_buffer != NULL)
++ xdr_destroy(&xdr_reply_ctx);
++
++ free(send_buffer);
++ free(recv_buffer);
++
+ return ret;
+ }
+