int pr_data_xfer(char *cl_buf, size_t cl_size) {
int len = 0;
int total = 0;
int res = 0;
pool *tmp_pool = NULL;
if (cl_buf == NULL ||
cl_size == 0) {
errno = EINVAL;
return -1;
}
/* Poll the control channel for any commands we should handle, like
* QUIT or ABOR.
*/
poll_ctrl();
/* If we don't have a data connection here (e.g. might have been closed
* by an ABOR), then return zero (no data transferred).
*/
if (session.d == NULL) {
int xerrno;
#if defined(ECONNABORTED)
xerrno = ECONNABORTED;
#elif defined(ENOTCONN)
xerrno = ENOTCONN;
#else
xerrno = EIO;
#endif
pr_trace_msg(trace_channel, 1,
"data connection is null prior to data transfer (possibly from "
"aborted transfer), returning '%s' error", strerror(xerrno));
pr_log_debug(DEBUG5,
"data connection is null prior to data transfer (possibly from "
"aborted transfer), returning '%s' error", strerror(xerrno));
errno = xerrno;
return -1;
}
if (session.xfer.direction == PR_NETIO_IO_RD) {
char *buf;
buf = session.xfer.buf;
/* We use ASCII translation if:
*
* - SF_ASCII session flag is set, AND IGNORE_ASCII data opt NOT set
*/
if (((session.sf_flags & SF_ASCII) &&
!(data_opts & PR_DATA_OPT_IGNORE_ASCII))) {
int adjlen, buflen;
do {
buflen = session.xfer.buflen; /* how much remains in buf */
adjlen = 0;
pr_signals_handle();
len = pr_netio_read(session.d->instrm, buf + buflen,
session.xfer.bufsize - buflen, 1);
while (len < 0) {
int xerrno = errno;
if (xerrno == EAGAIN || xerrno == EINTR) {
/* Since our socket is in non-blocking mode, read(2) can return
* EAGAIN if there is no data yet for us. Handle this by
* delaying temporarily, then trying again.
*/
errno = EINTR;
pr_signals_handle();
len = pr_netio_read(session.d->instrm, buf + buflen,
session.xfer.bufsize - buflen, 1);
continue;
}
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
if (len > 0 &&
data_first_byte_read == FALSE) {
if (pr_trace_get_level(timing_channel)) {
unsigned long elapsed_ms;
uint64_t read_ms;
pr_gettimeofday_millis(&read_ms);
elapsed_ms = (unsigned long) (read_ms - data_start_ms);
pr_trace_msg(timing_channel, 7,
"Time for first data byte read: %lu ms", elapsed_ms);
}
data_first_byte_read = TRUE;
}
if (len > 0) {
pr_trace_msg(trace_channel, 19, "read %d %s from network", len,
len != 1 ? "bytes" : "byte");
buflen += len;
if (timeout_stalled) {
pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
}
}
/* If buflen > 0, data remains in the buffer to be copied. */
if (len >= 0 &&
buflen > 0) {
/* Perform ASCII translation:
*
* buflen: is returned as the modified buffer length after
* translation
* res: is returned as the number of characters unprocessed in
* the buffer (to be dealt with later)
*
* We skip the call to pr_ascii_ftp_from_crlf() in one case:
* when we have one character in the buffer and have reached
* end of data, this is so that pr_ascii_ftp_from_crlf() won't sit
* forever waiting for the next character after a final '\r'.
*/
if (len > 0 ||
buflen > 1) {
size_t outlen = 0;
if (tmp_pool == NULL) {
tmp_pool = make_sub_pool(session.xfer.p);
pr_pool_tag(tmp_pool, "ASCII upload");
}
res = pr_ascii_ftp_from_crlf(tmp_pool, buf, buflen, &buf, &outlen);
if (res < 0) {
pr_trace_msg(trace_channel, 3, "error reading ASCII data: %s",
strerror(errno));
} else {
adjlen += res;
buflen = (int) outlen;
}
}
/* Now copy everything we can into cl_buf */
if ((size_t) buflen > cl_size) {
/* Because we have to cut our buffer short, make sure this
* is made up for later by increasing adjlen.
*/
adjlen += (buflen - cl_size);
buflen = cl_size;
}
memcpy(cl_buf, buf, buflen);
/* Copy whatever remains at the end of session.xfer.buf to the
* head of the buffer and adjust buf accordingly.
*
* adjlen is now the total bytes still waiting in buf, if
* anything remains, copy it to the start of the buffer.
*/
if (adjlen > 0) {
memcpy(buf, buf + buflen, adjlen);
}
/* Store everything back in session.xfer. */
session.xfer.buflen = adjlen;
total += buflen;
}
/* Restart if data was returned by pr_netio_read() (len > 0) but no
* data was copied to the client buffer (buflen = 0). This indicates
* that pr_ascii_ftp_from_crlf() needs more data in order to
* translate, so we need to call pr_netio_read() again.
*/
} while (len > 0 && buflen == 0);
/* Return how much data we actually copied into the client buffer. */
len = buflen;
} else {
len = pr_netio_read(session.d->instrm, cl_buf, cl_size, 1);
while (len < 0) {
int xerrno = errno;
if (xerrno == EAGAIN || xerrno == EINTR) {
/* Since our socket is in non-blocking mode, read(2) can return
* EAGAIN if there is no data yet for us. Handle this by
* delaying temporarily, then trying again.
*/
errno = EINTR;
pr_signals_handle();
len = pr_netio_read(session.d->instrm, cl_buf, cl_size, 1);
continue;
}
break;
}
if (len > 0) {
pr_trace_msg(trace_channel, 19, "read %d %s from network", len,
len != 1 ? "bytes" : "byte");
if (data_first_byte_read == FALSE) {
if (pr_trace_get_level(timing_channel)) {
unsigned long elapsed_ms;
uint64_t read_ms;
pr_gettimeofday_millis(&read_ms);
elapsed_ms = (unsigned long) (read_ms - data_start_ms);
pr_trace_msg(timing_channel, 7,
"Time for first data byte read: %lu ms", elapsed_ms);
}
data_first_byte_read = TRUE;
}
/* Non-ASCII mode doesn't need to use session.xfer.buf */
if (timeout_stalled) {
pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
}
total += len;
}
}
} else { /* PR_NETIO_IO_WR */
while (cl_size) {
int bwrote = 0;
int buflen = cl_size;
unsigned int xferbuflen;
char *xfer_buf = NULL;
pr_signals_handle();
if (buflen > pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR)) {
buflen = pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR);
}
xferbuflen = buflen;
/* Fill up our internal buffer. */
memcpy(session.xfer.buf, cl_buf, buflen);
/* We use ASCII translation if:
*
* - SF_ASCII_OVERRIDE session flag is set (e.g. for LIST/NLST)
* - SF_ASCII session flag is set, AND IGNORE_ASCII data opt NOT set
*/
if ((session.sf_flags & SF_ASCII_OVERRIDE) ||
((session.sf_flags & SF_ASCII) &&
!(data_opts & PR_DATA_OPT_IGNORE_ASCII))) {
char *out = NULL;
size_t outlen = 0;
if (tmp_pool == NULL) {
tmp_pool = make_sub_pool(session.xfer.p);
pr_pool_tag(tmp_pool, "ASCII download");
}
/* Scan the internal buffer, looking for LFs with no preceding CRs.
* Add CRs (and expand the internal buffer) as necessary. xferbuflen
* will be adjusted so that it contains the length of data in
* the internal buffer, including any added CRs.
*/
res = pr_ascii_ftp_to_crlf(tmp_pool, session.xfer.buf, xferbuflen,
&out, &outlen);
if (res < 0) {
pr_trace_msg(trace_channel, 1, "error writing ASCII data: %s",
strerror(errno));
} else {
xfer_buf = session.xfer.buf;
session.xfer.buf = out;
session.xfer.buflen = xferbuflen = outlen;
}
}
bwrote = pr_netio_write(session.d->outstrm, session.xfer.buf, xferbuflen);
while (bwrote < 0) {
int xerrno = errno;
if (xerrno == EAGAIN || xerrno == EINTR) {
/* Since our socket is in non-blocking mode, write(2) can return
* EAGAIN if there is not enough from for our data yet. Handle
* this by delaying temporarily, then trying again.
*/
errno = EINTR;
pr_signals_handle();
bwrote = pr_netio_write(session.d->outstrm, session.xfer.buf,
xferbuflen);
continue;
}
destroy_pool(tmp_pool);
if (xfer_buf != NULL) {
/* Free up the malloc'd memory. */
free(session.xfer.buf);
session.xfer.buf = xfer_buf;
}
errno = xerrno;
return -1;
}
if (bwrote > 0) {
pr_trace_msg(trace_channel, 19, "wrote %d %s to network", bwrote,
bwrote != 1 ? "bytes" : "byte");
if (data_first_byte_written == FALSE) {
if (pr_trace_get_level(timing_channel)) {
unsigned long elapsed_ms;
uint64_t write_ms;
pr_gettimeofday_millis(&write_ms);
elapsed_ms = (unsigned long) (write_ms - data_start_ms);
pr_trace_msg(timing_channel, 7,
"Time for first data byte written: %lu ms", elapsed_ms);
}
data_first_byte_written = TRUE;
}
if (timeout_stalled) {
pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
}
cl_size -= buflen;
cl_buf += buflen;
total += buflen;
}
if (xfer_buf != NULL) {
/* Yes, we are using malloc et al here, rather than the memory pools.
* See Bug#4352 for details.
*/
free(session.xfer.buf);
session.xfer.buf = xfer_buf;
}
}
len = total;
}
if (total &&
timeout_idle) {
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
}
session.xfer.total_bytes += total;
session.total_bytes += total;
if (session.xfer.direction == PR_NETIO_IO_RD) {
session.total_bytes_in += total;
} else {
session.total_bytes_out += total;
}
destroy_pool(tmp_pool);
return (len < 0 ? -1 : len);
}
#if defined(HAVE_SENDFILE)
/* 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 (offset == NULL ||
count == 0) {
errno = EINVAL;
return -1;
}
if (session.xfer.direction == PR_NETIO_IO_RD) {
errno = EPERM;
return -1;
}
if (session.d == NULL) {
errno = EPERM;
return -1;
}
flags = fcntl(PR_NETIO_FD(session.d->outstrm), F_GETFL);
if (flags < 0) {
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) < 0) {
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 */
errno = 0;
len = sendfile(PR_NETIO_FD(session.d->outstrm), retr_fd, offset, count);
/* If no data could be written (e.g. the file was truncated), we're
* done (Bug#4318).
*/
if (len == 0) {
if (errno != EINTR &&
errno != EAGAIN) {
break;
}
/* Handle our interrupting signal, and continue. */
pr_signals_handle();
}
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;
}
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 < 0) {
# 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) {
# else
if (FALSE) {
# 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;
}
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;
(void) fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags);
errno = error;
return -1;
}
break;
}
if (flags & O_NONBLOCK) {
(void) 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;
}
#else
pr_sendfile_t pr_data_sendfile(int retr_fd, off_t *offset, off_t count) {
errno = ENOSYS;
return -1;
}
#endif /* HAVE_SENDFILE */