/* pr_data_sendfile() actually transfers the data on the data connection. * ASCII translation is not performed. * return 0 if reading and data connection closes, or -1 if error */ pr_sendfile_t pr_data_sendfile(int retr_fd, off_t *offset, off_t count) { int flags, error; pr_sendfile_t len = 0, total = 0; #if defined(HAVE_AIX_SENDFILE) struct sf_parms parms; int rc; #endif /* HAVE_AIX_SENDFILE */ if (session.xfer.direction == PR_NETIO_IO_RD) return -1; flags = fcntl(PR_NETIO_FD(session.d->outstrm), F_GETFL); if (flags == -1) return -1; /* Set fd to blocking-mode for sendfile() */ if (flags & O_NONBLOCK) { if (fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags^O_NONBLOCK) == -1) return -1; } for (;;) { #if defined(HAVE_LINUX_SENDFILE) || defined(HAVE_SOLARIS_SENDFILE) off_t orig_offset = *offset; /* Linux semantics are fairly straightforward in a glibc 2.x world: * * #include <sys/sendfile.h> * * ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) * * Unfortunately, this API does not allow for an off_t number of bytes * to be sent, only a size_t. This means we need to make sure that * the given count does not exceed the maximum value for a size_t. Even * worse, the return value is a ssize_t, not a size_t, which means * the maximum value used can only be of the maximum _signed_ value, * not the maximum unsigned value. This means calling sendfile() more * times. How annoying. */ #if defined(HAVE_LINUX_SENDFILE) if (count > INT_MAX) count = INT_MAX; #elif defined(HAVE_SOLARIS_SENDFILE) # if SIZEOF_SIZE_T == SIZEOF_INT if (count > INT_MAX) count = INT_MAX; # elif SIZEOF_SIZE_T == SIZEOF_LONG if (count > LONG_MAX) count = LONG_MAX; # elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG if (count > LLONG_MAX) count = LLONG_MAX; # endif #endif /* !HAVE_SOLARIS_SENDFILE */ len = sendfile(PR_NETIO_FD(session.d->outstrm), retr_fd, offset, count); if (len != -1 && len < count) { /* Under Linux semantics, this occurs when a signal has interrupted * sendfile(). */ if (XFER_ABORTED) { errno = EINTR; session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; return -1; } count -= len; /* Only reset the timers if data have actually been written out. */ if (len > 0) { if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } if (timeout_idle) { pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } } session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; total += len; pr_signals_handle(); continue; } else if (len == -1) { /* Linux updates offset on error, not len like BSD, fix up so * BSD-based code works. */ len = *offset - orig_offset; *offset = orig_offset; #elif defined(HAVE_BSD_SENDFILE) /* BSD semantics for sendfile are flexible...it'd be nice if we could * standardize on something like it. The semantics are: * * #include <sys/types.h> * #include <sys/socket.h> * #include <sys/uio.h> * * int sendfile(int in_fd, int out_fd, off_t offset, size_t count, * struct sf_hdtr *hdtr, off_t *len, int flags) * * The comments above, about size_t versus off_t, apply here as * well. Except that BSD's sendfile() uses an off_t * for returning * the number of bytes sent, so we can use the maximum unsigned * value. */ #if SIZEOF_SIZE_T == SIZEOF_INT if (count > UINT_MAX) count = UINT_MAX; #elif SIZEOF_SIZE_T == SIZEOF_LONG if (count > ULONG_MAX) count = ULONG_MAX; #elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG if (count > ULLONG_MAX) count = ULLONG_MAX; #endif if (sendfile(retr_fd, PR_NETIO_FD(session.d->outstrm), *offset, count, NULL, &len, 0) == -1) { #elif defined(HAVE_MACOSX_SENDFILE) off_t orig_len = count; int res; /* Since Mac OSX uses the fourth argument as a value-return parameter, * success or failure, we need to put the result into len after the * call. */ res = sendfile(retr_fd, PR_NETIO_FD(session.d->outstrm), *offset, &orig_len, NULL, 0); len = orig_len; if (res == -1) { #elif defined(HAVE_AIX_SENDFILE) memset(&parms, 0, sizeof(parms)); parms.file_descriptor = retr_fd; parms.file_offset = (uint64_t) *offset; parms.file_bytes = (int64_t) count; rc = send_file(&(PR_NETIO_FD(session.d->outstrm)), &(parms), (uint_t)0); len = (int) parms.bytes_sent; if (rc == -1 || rc == 1) { #endif /* HAVE_AIX_SENDFILE */ /* IMO, BSD's semantics are warped. Apparently, since we have our * alarms tagged SA_INTERRUPT (allowing system calls to be * interrupted - primarily for select), BSD will interrupt a * sendfile operation as well, so we have to catch and handle this * case specially. It should also be noted that the sendfile(2) man * page doesn't state any of this. * * HP/UX has the same semantics, however, EINTR is well documented * as a side effect in the sendfile(2) man page. HP/UX, however, * is implemented horribly wrong. If a signal would result in * -1 being returned and EINTR being set, what ACTUALLY happens is * that errno is cleared and the number of bytes written is returned. * * For obvious reasons, HP/UX sendfile is not supported yet. */ if (errno == EINTR) { if (XFER_ABORTED) { session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; return -1; } pr_signals_handle(); /* If we got everything in this transaction, we're done. */ if (len >= count) { break; } else { count -= len; } *offset += len; if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } if (timeout_idle) { pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; total += len; continue; } error = errno; fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags); errno = error; return -1; } break; } if (flags & O_NONBLOCK) fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags); if (timeout_stalled) { pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } if (timeout_idle) { pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } session.xfer.total_bytes += len; session.total_bytes += len; session.total_bytes_out += len; session.total_raw_out += len; total += len; return total; }
int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn, conn_t *frontend_conn) { if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { /* Nothing to do if we're not yet connected to the backend server. */ return 0; } while (TRUE) { fd_set rfds; struct timeval tv; int ctrlfd, res, xerrno = 0; /* By using a timeout of zero, we effect a poll on the fd. */ tv.tv_sec = 0; tv.tv_usec = 0; pr_signals_handle(); FD_ZERO(&rfds); ctrlfd = PR_NETIO_FD(backend_conn->instrm); FD_SET(ctrlfd, &rfds); res = select(ctrlfd + 1, &rfds, NULL, NULL, &tv); if (res < 0) { xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error calling select(2) on backend control connection (fd %d): %s", ctrlfd, strerror(xerrno)); return 0; } if (res == 0) { /* Nothing there. */ break; } pr_trace_msg(trace_channel, 19, "select(2) reported %d for backend %s (fd %d)", res, backend_conn->remote_name, ctrlfd); if (FD_ISSET(ctrlfd, &rfds)) { unsigned int resp_nlines = 0; pr_response_t *resp; pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); pr_trace_msg(trace_channel, 9, "reading async response from backend %s", backend_conn->remote_name); resp = proxy_ftp_ctrl_recv_resp(p, backend_conn, &resp_nlines); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving response from backend control connection: %s", strerror(xerrno)); errno = xerrno; return -1; } res = proxy_ftp_ctrl_send_resp(p, frontend_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending response to frontend control connection: %s", strerror(xerrno)); errno = xerrno; return -1; } } break; } return 0; }