X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/4655dcf218be15551c974a034b9c9f95e2505842..0c2e8f93643fd94a8d388e4373c30331d0af75b4:/io.c diff --git a/io.c b/io.c index b5fd3776..9aab9dce 100644 --- a/io.c +++ b/io.c @@ -115,6 +115,17 @@ static char int_byte_extra[64] = { 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 6, /* (C0 - FF)/4 */ }; +/* Our I/O buffers are sized with no bits on in the lowest byte of the "size" + * (indeed, our rounding of sizes in 1024-byte units assures more than this). + * This allows the code that is storing bytes near the physical end of a + * circular buffer to temporarily reduce the buffer's size (in order to make + * some storing idioms easier), while also making it simple to restore the + * buffer's actual size when the buffer's "pos" wraps around to the start (we + * just round the buffer's size up again). */ + +#define IOBUF_WAS_REDUCED(siz) ((siz) & 0xFF) +#define IOBUF_RESTORE_SIZE(siz) (((siz) | 0xFF) + 1) + #define IN_MULTIPLEXED (iobuf.in_multiplexed) #define OUT_MULTIPLEXED (iobuf.out_empty_len != 0) @@ -136,6 +147,7 @@ enum festatus { FES_SUCCESS, FES_REDO, FES_NO_SEND }; static flist_ndx_list redo_list, hlink_list; +static void drain_multiplex_messages(void); static void sleep_for_bwlimit(int bytes_written); static void check_timeout(void) @@ -173,9 +185,9 @@ static void check_timeout(void) * There is another case for older protocol versions (< 24) where the module * listing was not terminated, so we must ignore an EOF error in that case and * exit. In this situation, kluge_around_eof will be > 0. */ -static NORETURN void whine_about_eof(int fd) +static NORETURN void whine_about_eof(BOOL allow_kluge) { - if (kluge_around_eof && fd == sock_f_in) { + if (kluge_around_eof && allow_kluge) { int i; if (kluge_around_eof > 0) exit_cleanup(0); @@ -442,6 +454,39 @@ static void forward_filesfrom_data(void) } } +void reduce_iobuf_size(xbuf *out, size_t new_size) +{ + if (new_size < out->size) { + if (DEBUG_GTE(IO, 4)) { + const char *name = out == &iobuf.out ? "iobuf.out" + : out == &iobuf.msg ? "iobuf.msg" + : NULL; + if (name) { + rprintf(FINFO, "[%s] reduced size of %s (-%d)\n", + who_am_i(), name, (int)(out->size - new_size)); + } + } + out->size = new_size; + } +} + +void restore_iobuf_size(xbuf *out) +{ + if (IOBUF_WAS_REDUCED(out->size)) { + size_t new_size = IOBUF_RESTORE_SIZE(out->size); + if (DEBUG_GTE(IO, 4)) { + const char *name = out == &iobuf.out ? "iobuf.out" + : out == &iobuf.msg ? "iobuf.msg" + : NULL; + if (name) { + rprintf(FINFO, "[%s] restored size of %s (+%d)\n", + who_am_i(), name, (int)(new_size - out->size)); + } + } + out->size = new_size; + } +} + /* Perform buffered input and output until specified conditions are met. When * given a "needed" read requirement, we'll return without doing any I/O if the * iobuf.in bytes are already available. When reading, we'll read as many @@ -507,13 +552,12 @@ static char *perform_io(size_t needed, int flags) * Also make sure it will fit in the free space at the end, or * else we need to shift some bytes. */ if (needed && iobuf.in.size < needed) { - if (!(iobuf.in.buf = realloc_array(iobuf.in.buf, char, needed))) - out_of_memory("perform_io"); + size_t new_size = ROUND_UP_1024(needed); if (DEBUG_GTE(IO, 4)) { - rprintf(FINFO, "[%s] resized input buffer from %ld to %ld bytes.\n", - who_am_i(), (long)iobuf.in.size, (long)needed); + rprintf(FINFO, "[%s] resizing input buffer from %ld to %ld bytes.\n", + who_am_i(), (long)iobuf.in.size, (long)new_size); } - iobuf.in.size = needed; + realloc_xbuf(&iobuf.in, new_size); } if (iobuf.in.size - iobuf.in.pos < needed || (iobuf.in.len < needed && iobuf.in.len < 1024 @@ -541,8 +585,8 @@ static char *perform_io(size_t needed, int flags) if (DEBUG_GTE(IO, 3)) { rprintf(FINFO, "[%s] perform_io(%ld, outroom) needs to flush %ld\n", who_am_i(), (long)needed, - iobuf.out.len > iobuf.out.size - needed - ? (long)iobuf.out.len - (iobuf.out.size - needed) : 0L); + iobuf.out.len + needed > iobuf.out.size + ? (long)(iobuf.out.len + needed - iobuf.out.size) : 0L); } break; @@ -557,8 +601,8 @@ static char *perform_io(size_t needed, int flags) if (DEBUG_GTE(IO, 3)) { rprintf(FINFO, "[%s] perform_io(%ld, msgroom) needs to flush %ld\n", who_am_i(), (long)needed, - iobuf.out.len > iobuf.msg.size - needed - ? (long)iobuf.out.len - (iobuf.msg.size - needed) : 0L); + iobuf.msg.len + needed > iobuf.msg.size + ? (long)(iobuf.msg.len + needed - iobuf.msg.size) : 0L); } break; @@ -578,11 +622,13 @@ static char *perform_io(size_t needed, int flags) goto double_break; break; case PIO_NEED_OUTROOM: - if (iobuf.out.len <= iobuf.out.size - needed) + /* Note that iobuf.out_empty_len doesn't factor into this check + * because iobuf.out.len already holds any needed header len. */ + if (iobuf.out.len + needed <= iobuf.out.size) goto double_break; break; case PIO_NEED_MSGROOM: - if (iobuf.msg.len <= iobuf.msg.size - needed) + if (iobuf.msg.len + needed <= iobuf.msg.size) goto double_break; break; } @@ -612,22 +658,12 @@ static char *perform_io(size_t needed, int flags) if (iobuf.raw_flushing_ends_before || (!iobuf.msg.len && iobuf.out.len > iobuf.out_empty_len && !(flags & PIO_NEED_MSGROOM))) { if (OUT_MULTIPLEXED && !iobuf.raw_flushing_ends_before) { - size_t val; - /* The iobuf.raw_flushing_ends_before value can point off the end * of the iobuf.out buffer for a while, for easier subtracting. */ iobuf.raw_flushing_ends_before = iobuf.out.pos + iobuf.out.len; SIVAL(iobuf.out.buf + iobuf.raw_data_header_pos, 0, ((MPLEX_BASE + (int)MSG_DATA)<<24) + iobuf.out.len - 4); - if ((val = iobuf.out.size - iobuf.raw_data_header_pos) < 4) { - /* We used some of the overflow bytes, so move them. */ - if (DEBUG_GTE(IO, 4)) { - rprintf(FINFO, "[%s] wrap-bytes moved: %d (perform_io)\n", - who_am_i(), (int)val); - } - memcpy(iobuf.out.buf, iobuf.out.buf + iobuf.out.size, 4 - val); - } if (DEBUG_GTE(IO, 1)) { rprintf(FINFO, "[%s] send_msg(%d, %ld)\n", @@ -638,6 +674,14 @@ static char *perform_io(size_t needed, int flags) iobuf.raw_data_header_pos = iobuf.raw_flushing_ends_before; if (iobuf.raw_data_header_pos >= iobuf.out.size) iobuf.raw_data_header_pos -= iobuf.out.size; + else if (iobuf.raw_data_header_pos + 4 > iobuf.out.size) { + /* The 4-byte header won't fit at the end of the buffer, + * so we'll temporarily reduce the output buffer's size + * and put the header at the start of the buffer. */ + reduce_iobuf_size(&iobuf.out, iobuf.raw_data_header_pos); + iobuf.raw_data_header_pos = 0; + } + /* Yes, it is possible for this to make len > size for a while. */ iobuf.out.len += 4; } @@ -659,11 +703,19 @@ static char *perform_io(size_t needed, int flags) if (max_fd < 0) { switch (flags & PIO_NEED_FLAGS) { case PIO_NEED_INPUT: + iobuf.in.len = 0; + if (kluge_around_eof == 2) + exit_cleanup(0); + if (iobuf.in_fd == -2) + whine_about_eof(True); rprintf(FERROR, "error in perform_io: no fd for input.\n"); exit_cleanup(RERR_PROTOCOL); case PIO_NEED_OUTROOM: case PIO_NEED_MSGROOM: msgs2stderr = 1; + drain_multiplex_messages(); + if (iobuf.out_fd == -2) + whine_about_eof(True); rprintf(FERROR, "error in perform_io: no fd for output.\n"); exit_cleanup(RERR_PROTOCOL); default: @@ -674,8 +726,7 @@ static char *perform_io(size_t needed, int flags) } if (extra_flist_sending_enabled) { - if (file_total - file_old_total < MAX_FILECNT_LOOKAHEAD - && file_total - file_old_total >= MIN_FILECNT_LOOKAHEAD) + if (file_total - file_old_total < MAX_FILECNT_LOOKAHEAD) tv.tv_sec = 0; else { extra_flist_sending_enabled = False; @@ -708,8 +759,9 @@ static char *perform_io(size_t needed, int flags) int n; if ((n = read(iobuf.in_fd, iobuf.in.buf + pos, len)) <= 0) { if (n == 0) { + /* Signal that input has become invalid. */ if (!read_batch || batch_fd < 0 || am_generator) - whine_about_eof(iobuf.in_fd); /* Doesn't return. */ + iobuf.in_fd = -2; batch_fd = -1; continue; } @@ -723,7 +775,7 @@ static char *perform_io(size_t needed, int flags) rsyserr(FERROR_SOCKET, errno, "read error"); } else rsyserr(FERROR, errno, "read error"); - exit_cleanup(RERR_STREAMIO); + exit_cleanup(RERR_SOCKETIO); } } if (msgs2stderr && DEBUG_GTE(IO, 2)) @@ -751,9 +803,11 @@ static char *perform_io(size_t needed, int flags) else { /* Don't write errors on a dead socket. */ msgs2stderr = 1; - out->len = iobuf.raw_flushing_ends_before = out->pos = 0; + iobuf.out_fd = -2; + iobuf.out.len = iobuf.msg.len = iobuf.raw_flushing_ends_before = 0; rsyserr(FERROR_SOCKET, errno, "[%s] write error", who_am_i()); - exit_cleanup(RERR_STREAMIO); + drain_multiplex_messages(); + exit_cleanup(RERR_SOCKETIO); } } if (msgs2stderr && DEBUG_GTE(IO, 2)) { @@ -772,11 +826,12 @@ static char *perform_io(size_t needed, int flags) if (iobuf.raw_flushing_ends_before) iobuf.raw_flushing_ends_before -= out->size; out->pos = 0; - } - if (out->pos == iobuf.raw_flushing_ends_before) + restore_iobuf_size(out); + } else if (out->pos == iobuf.raw_flushing_ends_before) iobuf.raw_flushing_ends_before = 0; if ((out->len -= n) == empty_buf_len) { out->pos = 0; + restore_iobuf_size(out); if (empty_buf_len) iobuf.raw_data_header_pos = 0; } @@ -806,8 +861,11 @@ void noop_io_until_death(void) { char buf[1024]; - kluge_around_eof = 1; - set_io_timeout(10); + kluge_around_eof = 2; + /* Setting an I/O timeout ensures that if something inexplicably weird + * happens, we won't hang around forever. */ + if (!io_timeout) + set_io_timeout(60); while (1) read_buf(iobuf.in_fd, buf, sizeof buf); @@ -817,7 +875,7 @@ void noop_io_until_death(void) int send_msg(enum msgcode code, const char *buf, size_t len, int convert) { char *hdr; - size_t pos; + size_t needed, pos; BOOL want_debug = DEBUG_GTE(IO, 1) && convert >= 0 && (msgs2stderr || code != MSG_INFO); if (!OUT_MULTIPLEXED) @@ -826,24 +884,35 @@ int send_msg(enum msgcode code, const char *buf, size_t len, int convert) if (want_debug) rprintf(FINFO, "[%s] send_msg(%d, %ld)\n", who_am_i(), (int)code, (long)len); + /* When checking for enough free space for this message, we need to + * make sure that there is space for the 4-byte header, plus we'll + * assume that we may waste up to 3 bytes (if the header doesn't fit + * at the physical end of the buffer). */ #ifdef ICONV_OPTION if (convert > 0 && ic_send == (iconv_t)-1) convert = 0; if (convert > 0) { - /* Ensuring double-size room leaves space for a potential conversion. */ - if (iobuf.msg.len + len*2 + 4 > iobuf.msg.size) - perform_io(len*2 + 4, PIO_NEED_MSGROOM); + /* Ensuring double-size room leaves space for maximal conversion expansion. */ + needed = len*2 + 4 + 3; } else #endif - if (iobuf.msg.len + len + 4 > iobuf.msg.size) - perform_io(len + 4, PIO_NEED_MSGROOM); + needed = len + 4 + 3; + if (iobuf.msg.len + needed > iobuf.msg.size) + perform_io(needed, PIO_NEED_MSGROOM); pos = iobuf.msg.pos + iobuf.msg.len; /* Must be set after any flushing. */ if (pos >= iobuf.msg.size) pos -= iobuf.msg.size; + else if (pos + 4 > iobuf.msg.size) { + /* The 4-byte header won't fit at the end of the buffer, + * so we'll temporarily reduce the message buffer's size + * and put the header at the start of the buffer. */ + reduce_iobuf_size(&iobuf.msg, pos); + pos = 0; + } hdr = iobuf.msg.buf + pos; - iobuf.msg.len += 4; /* Leave room for the coming header bytes. */ + iobuf.msg.len += 4; /* Allocate room for the coming header bytes. */ #ifdef ICONV_OPTION if (convert > 0) { @@ -878,13 +947,6 @@ int send_msg(enum msgcode code, const char *buf, size_t len, int convert) } SIVAL(hdr, 0, ((MPLEX_BASE + (int)code)<<24) + len); - /* If the header used any overflow bytes, move them to the start. */ - if ((pos = hdr+4 - iobuf.msg.buf) > iobuf.msg.size) { - size_t siz = pos - iobuf.msg.size; - if (DEBUG_GTE(IO, 4)) - rprintf(FINFO, "[%s] wrap-bytes moved: %d (send_msg)\n", who_am_i(), (int)siz); - memcpy(iobuf.msg.buf, hdr+4 - siz, siz); - } if (want_debug && convert > 0) rprintf(FINFO, "[%s] converted msg len=%ld\n", who_am_i(), (long)len); @@ -1154,25 +1216,15 @@ BOOL io_start_buffering_out(int f_out) if (msgs2stderr && DEBUG_GTE(IO, 2)) rprintf(FINFO, "[%s] io_start_buffering_out(%d)\n", who_am_i(), f_out); - if (OUT_MULTIPLEXED && !iobuf.msg.buf) { - iobuf.msg.size = IO_BUFFER_SIZE - 4; - if (!(iobuf.msg.buf = new_array(char, iobuf.msg.size + 4))) - out_of_memory("io_start_buffering_out"); - iobuf.msg.pos = iobuf.msg.len = 0; - } - if (iobuf.out.buf) { if (iobuf.out_fd == -1) iobuf.out_fd = f_out; - assert(f_out == iobuf.out_fd); + else + assert(f_out == iobuf.out_fd); return False; } - iobuf.out.size = IO_BUFFER_SIZE * 2 - 4; - /* The 4 overflow bytes makes some circular-buffer wrapping operations easier. */ - if (!(iobuf.out.buf = new_array(char, iobuf.out.size + 4))) - out_of_memory("io_start_buffering_out"); - iobuf.out.pos = iobuf.out.len = 0; + alloc_xbuf(&iobuf.out, ROUND_UP_1024(IO_BUFFER_SIZE * 2)); iobuf.out_fd = f_out; return True; @@ -1186,16 +1238,12 @@ BOOL io_start_buffering_in(int f_in) if (iobuf.in.buf) { if (iobuf.in_fd == -1) iobuf.in_fd = f_in; - assert(f_in == iobuf.in_fd); + else + assert(f_in == iobuf.in_fd); return False; } - iobuf.in.size = IO_BUFFER_SIZE; - if (!(iobuf.in.buf = new_array(char, iobuf.in.size))) - out_of_memory("io_start_buffering_in"); - - iobuf.in.pos = iobuf.in.len = 0; - + alloc_xbuf(&iobuf.in, ROUND_UP_1024(IO_BUFFER_SIZE)); iobuf.in_fd = f_in; return True; @@ -1313,9 +1361,10 @@ static void read_a_msg(void) if (msg_bytes != 4 || am_sender) goto invalid_msg; data = perform_io(4, PIO_INPUT_AND_CONSUME); - io_error |= IVAL(data, 0); + val = IVAL(data, 0); + io_error |= val; if (!am_generator) - send_msg(MSG_IO_ERROR, data, 4, 0); + send_msg_int(MSG_IO_ERROR, val); break; case MSG_IO_TIMEOUT: if (msg_bytes != 4 || am_server || am_generator) @@ -1392,19 +1441,21 @@ static void read_a_msg(void) exit_cleanup(RERR_STREAMIO); } data = perform_io(4, PIO_INPUT_AND_CONSUME); + val = IVAL(data, 0); if (am_generator) - got_flist_entry_status(FES_SUCCESS, IVAL(data, 0)); + got_flist_entry_status(FES_SUCCESS, val); else - successful_send(IVAL(data, 0)); + successful_send(val); break; case MSG_NO_SEND: if (msg_bytes != 4) goto invalid_msg; data = perform_io(4, PIO_INPUT_AND_CONSUME); + val = IVAL(data, 0); if (am_generator) - got_flist_entry_status(FES_NO_SEND, IVAL(data, 0)); + got_flist_entry_status(FES_NO_SEND, val); else - send_msg(MSG_NO_SEND, data, 4, 0); + send_msg_int(MSG_NO_SEND, val); break; case MSG_ERROR_SOCKET: case MSG_ERROR_UTF8: @@ -1438,15 +1489,41 @@ static void read_a_msg(void) } break; case MSG_ERROR_EXIT: - if (msg_bytes != 4) + if (DEBUG_GTE(EXIT, 3)) + rprintf(FINFO, "[%s] got MSG_ERROR_EXIT with %d bytes\n", who_am_i(), msg_bytes); + if (msg_bytes == 0) { + if (!am_sender && !am_generator) { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT (len 0)\n", + who_am_i()); + } + send_msg(MSG_ERROR_EXIT, "", 0, 0); + io_flush(FULL_FLUSH); + } + val = 0; + } else if (msg_bytes == 4) { + data = perform_io(4, PIO_INPUT_AND_CONSUME); + val = IVAL(data, 0); + if (protocol_version >= 31) { + if (am_generator) { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT with exit_code %d\n", + who_am_i(), val); + } + send_msg_int(MSG_ERROR_EXIT, val); + } else { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT (len 0)\n", + who_am_i()); + } + send_msg(MSG_ERROR_EXIT, "", 0, 0); + } + } + } else goto invalid_msg; - data = perform_io(4, PIO_INPUT_AND_CONSUME); - val = IVAL(data, 0); - if (am_generator && protocol_version >= 31) - send_msg_int(MSG_ERROR_EXIT, val); - if (am_generator) - val = RERR_RCVR_ERROR; /* avoids duplicate errors */ - exit_cleanup(val); + /* Send a negative linenum so that we don't end up + * with a duplicate exit message. */ + _exit_cleanup(val, __FILE__, 0 - __LINE__); default: rprintf(FERROR, "unexpected tag %d [%s%s]\n", tag, who_am_i(), inc_recurse ? "/inc" : ""); @@ -1454,6 +1531,23 @@ static void read_a_msg(void) } } +static void drain_multiplex_messages(void) +{ + while (IN_MULTIPLEXED && iobuf.in.len) { + if (iobuf.raw_input_ends_before) { + size_t raw_len = iobuf.raw_input_ends_before - iobuf.in.pos; + iobuf.raw_input_ends_before = 0; + if (raw_len >= iobuf.in.len) { + iobuf.in.len = 0; + break; + } + iobuf.in.pos += raw_len; + iobuf.in.len -= raw_len; + } + read_a_msg(); + } +} + void wait_for_receiver(void) { if (!iobuf.raw_input_ends_before) @@ -1607,7 +1701,7 @@ void read_buf(int f, char *buf, size_t len) { if (f != iobuf.in_fd) { if (safe_read(f, buf, len) != len) - whine_about_eof(f); /* Doesn't return. */ + whine_about_eof(False); /* Doesn't return. */ goto batch_copy; } @@ -1895,7 +1989,7 @@ void write_buf(int f, const char *buf, size_t len) goto batch_copy; } - if (iobuf.out.size - iobuf.out.len < len) + if (iobuf.out.len + len > iobuf.out.size) perform_io(len, PIO_NEED_OUTROOM); pos = iobuf.out.pos + iobuf.out.len; /* Must be set after any flushing. */ @@ -2064,11 +2158,11 @@ void io_printf(int fd, const char *format, ...) va_end(ap); if (len < 0) - exit_cleanup(RERR_STREAMIO); + exit_cleanup(RERR_PROTOCOL); if (len > (int)sizeof buf) { rprintf(FERROR, "io_printf() was too long for the buffer.\n"); - exit_cleanup(RERR_STREAMIO); + exit_cleanup(RERR_PROTOCOL); } write_sbuf(fd, buf); @@ -2082,6 +2176,9 @@ void io_start_multiplex_out(int fd) if (msgs2stderr && DEBUG_GTE(IO, 2)) rprintf(FINFO, "[%s] io_start_multiplex_out(%d)\n", who_am_i(), fd); + if (!iobuf.msg.buf) + alloc_xbuf(&iobuf.msg, ROUND_UP_1024(IO_BUFFER_SIZE)); + iobuf.out_empty_len = 4; /* See also OUT_MULTIPLEXED */ io_start_buffering_out(fd);