#define BUFFERS_PRIVATE
#include "lib/net/buffers_net.h"
#include "lib/buf/buffers.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/net/nettypes.h"
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef PARANOIA
#define check() STMT_BEGIN buf_assert_ok(buf); STMT_END
#else
#define check() STMT_NIL
#endif
static inline int
read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most,
int *reached_eof, int *error, bool is_socket)
{
ssize_t read_result;
if (at_most > CHUNK_REMAINING_CAPACITY(chunk))
at_most = CHUNK_REMAINING_CAPACITY(chunk);
if (is_socket)
read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0);
else
read_result = read(fd, CHUNK_WRITE_PTR(chunk), at_most);
if (read_result < 0) {
int e = is_socket ? tor_socket_errno(fd) : errno;
if (!ERRNO_IS_EAGAIN(e)) {
#ifdef _WIN32
if (e == WSAENOBUFS)
log_warn(LD_NET, "%s() failed: WSAENOBUFS. Not enough ram?",
is_socket ? "recv" : "read");
#endif
if (error)
*error = e;
return -1;
}
return 0;
} else if (read_result == 0) {
log_debug(LD_NET,"Encountered eof on fd %d", (int)fd);
*reached_eof = 1;
return 0;
} else {
buf->datalen += read_result;
chunk->datalen += read_result;
log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result,
(int)buf->datalen);
tor_assert(read_result <= BUF_MAX_LEN);
return (int)read_result;
}
}
static int
buf_read_from_fd(buf_t *buf, int fd, size_t at_most,
int *reached_eof,
int *socket_error,
bool is_socket)
{
int r = 0;
size_t total_read = 0;
check();
tor_assert(reached_eof);
tor_assert(SOCKET_OK(fd));
if (BUG(buf->datalen > BUF_MAX_LEN))
return -1;
if (BUG(buf->datalen > BUF_MAX_LEN - at_most))
return -1;
while (at_most > total_read) {
size_t readlen = at_most - total_read;
chunk_t *chunk;
if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) {
chunk = buf_add_chunk_with_capacity(buf, at_most, 1);
if (readlen > chunk->memlen)
readlen = chunk->memlen;
} else {
size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail);
chunk = buf->tail;
if (cap < readlen)
readlen = cap;
}
r = read_to_chunk(buf, chunk, fd, readlen,
reached_eof, socket_error, is_socket);
check();
if (r < 0)
return r;
tor_assert(total_read+r <= BUF_MAX_LEN);
total_read += r;
if ((size_t)r < readlen) {
break;
}
}
return (int)total_read;
}
static inline int
flush_chunk(tor_socket_t fd, buf_t *buf, chunk_t *chunk, size_t sz,
bool is_socket)
{
ssize_t write_result;
if (sz > chunk->datalen)
sz = chunk->datalen;
if (is_socket)
write_result = tor_socket_send(fd, chunk->data, sz, 0);
else
write_result = write(fd, chunk->data, sz);
if (write_result < 0) {
int e = is_socket ? tor_socket_errno(fd) : errno;
if (!ERRNO_IS_EAGAIN(e)) {
#ifdef _WIN32
if (e == WSAENOBUFS)
log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?");
#endif
return -1;
}
log_debug(LD_NET,"write() would block, returning.");
return 0;
} else {
buf_drain(buf, write_result);
tor_assert(write_result <= BUF_MAX_LEN);
return (int)write_result;
}
}
static int
buf_flush_to_fd(buf_t *buf, int fd, size_t sz,
bool is_socket)
{
int r;
size_t flushed = 0;
tor_assert(SOCKET_OK(fd));
if (BUG(sz > buf->datalen)) {
sz = buf->datalen;
}
check();
while (sz) {
size_t flushlen0;
tor_assert(buf->head);
if (buf->head->datalen >= sz)
flushlen0 = sz;
else
flushlen0 = buf->head->datalen;
r = flush_chunk(fd, buf, buf->head, flushlen0, is_socket);
check();
if (r < 0)
return r;
flushed += r;
sz -= r;
if (r == 0 || (size_t)r < flushlen0)
break;
}
tor_assert(flushed <= BUF_MAX_LEN);
return (int)flushed;
}
int
buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz)
{
return buf_flush_to_fd(buf, s, sz, true);
}
int
buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
int *reached_eof,
int *socket_error)
{
return buf_read_from_fd(buf, s, at_most, reached_eof, socket_error, true);
}
int
buf_flush_to_pipe(buf_t *buf, int fd, size_t sz)
{
return buf_flush_to_fd(buf, fd, sz, false);
}
int
buf_read_from_pipe(buf_t *buf, int fd, size_t at_most,
int *reached_eof,
int *socket_error)
{
return buf_read_from_fd(buf, fd, at_most, reached_eof, socket_error, false);
}