mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-27 07:59:35 +01:00
193 lines
5.8 KiB
C
193 lines
5.8 KiB
C
|
/* Copyright (c) 2001 Matej Pfajfar.
|
||
|
* Copyright (c) 2001-2004, Roger Dingledine.
|
||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||
|
/* See LICENSE for licensing information */
|
||
|
|
||
|
#define BUFFERS_PRIVATE
|
||
|
#include "lib/net/buffers_net.h"
|
||
|
#include "lib/container/buffers.h"
|
||
|
#include "lib/log/torlog.h"
|
||
|
#include "lib/log/util_bug.h"
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
#ifdef PARANOIA
|
||
|
/** Helper: If PARANOIA is defined, assert that the buffer in local variable
|
||
|
* <b>buf</b> is well-formed. */
|
||
|
#define check() STMT_BEGIN buf_assert_ok(buf); STMT_END
|
||
|
#else
|
||
|
#define check() STMT_NIL
|
||
|
#endif /* defined(PARANOIA) */
|
||
|
|
||
|
/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into
|
||
|
* <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set
|
||
|
* *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking,
|
||
|
* and the number of bytes read otherwise. */
|
||
|
static inline int
|
||
|
read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most,
|
||
|
int *reached_eof, int *socket_error)
|
||
|
{
|
||
|
ssize_t read_result;
|
||
|
if (at_most > CHUNK_REMAINING_CAPACITY(chunk))
|
||
|
at_most = CHUNK_REMAINING_CAPACITY(chunk);
|
||
|
read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0);
|
||
|
|
||
|
if (read_result < 0) {
|
||
|
int e = tor_socket_errno(fd);
|
||
|
if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */
|
||
|
#ifdef _WIN32
|
||
|
if (e == WSAENOBUFS)
|
||
|
log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?");
|
||
|
#endif
|
||
|
*socket_error = e;
|
||
|
return -1;
|
||
|
}
|
||
|
return 0; /* would block. */
|
||
|
} else if (read_result == 0) {
|
||
|
log_debug(LD_NET,"Encountered eof on fd %d", (int)fd);
|
||
|
*reached_eof = 1;
|
||
|
return 0;
|
||
|
} else { /* actually got bytes. */
|
||
|
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 < INT_MAX);
|
||
|
return (int)read_result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most
|
||
|
* <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0
|
||
|
* (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on
|
||
|
* error; else return the number of bytes read.
|
||
|
*/
|
||
|
/* XXXX indicate "read blocked" somehow? */
|
||
|
int
|
||
|
buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
|
||
|
int *reached_eof,
|
||
|
int *socket_error)
|
||
|
{
|
||
|
/* XXXX It's stupid to overload the return values for these functions:
|
||
|
* "error status" and "number of bytes read" are not mutually exclusive.
|
||
|
*/
|
||
|
int r = 0;
|
||
|
size_t total_read = 0;
|
||
|
|
||
|
check();
|
||
|
tor_assert(reached_eof);
|
||
|
tor_assert(SOCKET_OK(s));
|
||
|
|
||
|
if (BUG(buf->datalen >= INT_MAX))
|
||
|
return -1;
|
||
|
if (BUG(buf->datalen >= INT_MAX - 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, s, readlen, reached_eof, socket_error);
|
||
|
check();
|
||
|
if (r < 0)
|
||
|
return r; /* Error */
|
||
|
tor_assert(total_read+r < INT_MAX);
|
||
|
total_read += r;
|
||
|
if ((size_t)r < readlen) { /* eof, block, or no more to read. */
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return (int)total_read;
|
||
|
}
|
||
|
|
||
|
/** Helper for buf_flush_to_socket(): try to write <b>sz</b> bytes from chunk
|
||
|
* <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct
|
||
|
* the bytes written from *<b>buf_flushlen</b>. Return the number of bytes
|
||
|
* written on success, 0 on blocking, -1 on failure.
|
||
|
*/
|
||
|
static inline int
|
||
|
flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz,
|
||
|
size_t *buf_flushlen)
|
||
|
{
|
||
|
ssize_t write_result;
|
||
|
|
||
|
if (sz > chunk->datalen)
|
||
|
sz = chunk->datalen;
|
||
|
write_result = tor_socket_send(s, chunk->data, sz, 0);
|
||
|
|
||
|
if (write_result < 0) {
|
||
|
int e = tor_socket_errno(s);
|
||
|
if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */
|
||
|
#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_flushlen -= write_result;
|
||
|
buf_drain(buf, write_result);
|
||
|
tor_assert(write_result < INT_MAX);
|
||
|
return (int)write_result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most
|
||
|
* <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by
|
||
|
* the number of bytes actually written, and remove the written bytes
|
||
|
* from the buffer. Return the number of bytes written on success,
|
||
|
* -1 on failure. Return 0 if write() would block.
|
||
|
*/
|
||
|
int
|
||
|
buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz,
|
||
|
size_t *buf_flushlen)
|
||
|
{
|
||
|
/* XXXX It's stupid to overload the return values for these functions:
|
||
|
* "error status" and "number of bytes flushed" are not mutually exclusive.
|
||
|
*/
|
||
|
int r;
|
||
|
size_t flushed = 0;
|
||
|
tor_assert(buf_flushlen);
|
||
|
tor_assert(SOCKET_OK(s));
|
||
|
if (BUG(*buf_flushlen > buf->datalen)) {
|
||
|
*buf_flushlen = buf->datalen;
|
||
|
}
|
||
|
if (BUG(sz > *buf_flushlen)) {
|
||
|
sz = *buf_flushlen;
|
||
|
}
|
||
|
|
||
|
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(s, buf, buf->head, flushlen0, buf_flushlen);
|
||
|
check();
|
||
|
if (r < 0)
|
||
|
return r;
|
||
|
flushed += r;
|
||
|
sz -= r;
|
||
|
if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */
|
||
|
break;
|
||
|
}
|
||
|
tor_assert(flushed < INT_MAX);
|
||
|
return (int)flushed;
|
||
|
}
|