xapian-core  2.1.0
remoteconnection.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2006-2026 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "remoteconnection.h"
24 
25 #include <xapian/error.h>
26 
27 #include "safefcntl.h"
28 #include "safeunistd.h"
29 
30 #ifdef HAVE_POLL_H
31 # include <poll.h>
32 #else
33 # include "safesysselect.h"
34 #endif
35 
36 #include <algorithm>
37 #include <cerrno>
38 #include <climits>
39 #include <cstdint>
40 #include <string>
41 #ifdef __WIN32__
42 # include <type_traits>
43 #endif
44 
45 #include "debuglog.h"
46 #include "fd.h"
47 #include "filetests.h"
48 #include "omassert.h"
49 #include "overflow.h"
50 #include "pack.h"
51 #include "posixy_wrapper.h"
52 #include "realtime.h"
53 #include "socket_utils.h"
54 
55 using namespace std;
56 
57 static constexpr size_t CHUNKSIZE{4096};
58 
59 [[noreturn]]
60 static void
62 {
63  throw Xapian::DatabaseClosedError("Database has been closed");
64 }
65 
66 [[noreturn]]
67 static void
68 throw_timeout(const char* msg, const string& context)
69 {
70  throw Xapian::NetworkTimeoutError(msg, context);
71 }
72 
73 #ifdef __WIN32__
74 static inline void
75 update_overlapped_offset(WSAOVERLAPPED & overlapped, DWORD n)
76 {
77  if (add_overflows(overlapped.Offset, n, overlapped.Offset))
78  ++overlapped.OffsetHigh;
79 }
80 #endif
81 
82 RemoteConnection::RemoteConnection(int fdin_, int fdout_,
83  const string & context_)
84  : fdin(fdin_), fdout(fdout_), context(context_)
85 {
86 #ifdef __WIN32__
87  memset(&overlapped, 0, sizeof(overlapped));
88  overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
89  if (!overlapped.hEvent)
90  throw Xapian::NetworkError("Failed to setup OVERLAPPED",
91  context, -int(GetLastError()));
92 
93 #elif defined USE_SO_NOSIGPIPE
94  // SO_NOSIGPIPE is a non-standardised socket option supported by a number
95  // of platforms - at least DragonFlyBSD, FreeBSD, macOS (not older
96  // versions, e.g. 10.15 apparently lacks it), Solaris; notably not
97  // supported by Linux or OpenBSD though.
98  //
99  // We use it where supported due to one big advantage over POSIX's
100  // MSG_NOSIGNAL which is that we can just set it once for a socket whereas
101  // with MSG_NOSIGNAL we need to call send(..., MSG_NOSIGNAL) instead of
102  // write(...), but send() only works on sockets, so with MSG_NOSIGNAL any
103  // code which might be working with files or pipes as well as sockets needs
104  // conditional handling depending on whether the fd is a socket or not.
105  //
106  // SO_NOSIGPIPE is present on NetBSD, but it seems when using it we still
107  // get SIGPIPE (reproduced on NetBSD 9.3 and 10.0) so we avoid using it
108  // there.
109  int on = 1;
110  if (setsockopt(fdout, SOL_SOCKET, SO_NOSIGPIPE,
111  reinterpret_cast<char*>(&on), sizeof(on)) < 0) {
112  // Some platforms (including FreeBSD, macOS, DragonflyBSD) seem to
113  // fail with EBADF instead of ENOTSOCK when passed a non-socket so
114  // allow either. If the descriptor is actually not valid we'll report
115  // it the next time we try to use it (as we would when not trying to
116  // use SO_NOSIGPIPE so this actually gives a more consistent error
117  // across platforms.
118  if (errno != ENOTSOCK && errno != EBADF) {
119  throw Xapian::NetworkError("Couldn't set SO_NOSIGPIPE on socket",
120  errno);
121  }
122  }
123 #elif defined USE_MSG_NOSIGNAL
124  // We can use send(..., MSG_NOSIGNAL) to avoid generating SIGPIPE
125  // (MSG_NOSIGNAL was added in POSIX.1-2008). This seems to be pretty much
126  // universally supported by current Unix-like platforms, but older macOS
127  // and Solaris apparently didn't have it.
128  //
129  // If fdout is not a socket, we'll set send_flags = 0 when the first send()
130  // fails with ENOTSOCK and use write() instead from then on.
131 #else
132  // It's simplest to just ignore SIGPIPE. Not ideal, but it seems only old
133  // versions of macOS and of Solaris will end up here so let's not bother
134  // trying to do any clever trickery.
135  if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
136  throw Xapian::NetworkError("Couldn't set SIGPIPE to SIG_IGN", errno);
137  }
138 #endif
139 }
140 
141 #ifdef __WIN32__
142 RemoteConnection::~RemoteConnection()
143 {
144  if (overlapped.hEvent)
145  CloseHandle(overlapped.hEvent);
146 }
147 #endif
148 
149 bool
151 {
152  LOGCALL(REMOTE, bool, "RemoteConnection::read_at_least", min_len | end_time);
153 
154  if (buffer.length() >= min_len) RETURN(true);
155 
156 #ifdef __WIN32__
157  HANDLE hin = fd_to_handle(fdin);
158  do {
159  char buf[CHUNKSIZE];
160  DWORD received;
161  BOOL ok = ReadFile(hin, buf, sizeof(buf), &received, &overlapped);
162  if (!ok) {
163  int errcode = GetLastError();
164  if (errcode != ERROR_IO_PENDING)
165  throw Xapian::NetworkError("read failed", context, -errcode);
166  // Is asynch - just wait for the data to be received or a timeout.
167  DWORD waitrc;
168  waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
169  if (waitrc != WAIT_OBJECT_0) {
170  LOGLINE(REMOTE, "read: timeout has expired");
171  throw_timeout("Timeout expired while trying to read", context);
172  }
173  // Get the final result of the read.
174  if (!GetOverlappedResult(hin, &overlapped, &received, FALSE))
175  throw Xapian::NetworkError("Failed to get overlapped result",
176  context, -int(GetLastError()));
177  }
178 
179  if (received == 0) {
180  RETURN(false);
181  }
182 
183  buffer.append(buf, received);
184 
185  // We must update the offset in the OVERLAPPED structure manually.
186  update_overlapped_offset(overlapped, received);
187  } while (buffer.length() < min_len);
188 #else
189  // If there's no end_time, just use blocking I/O.
190  if (fcntl(fdin, F_SETFL, (end_time != 0.0) ? O_NONBLOCK : 0) < 0) {
191  throw Xapian::NetworkError("Failed to set fdin non-blocking-ness",
192  context, errno);
193  }
194 
195  while (true) {
196  char buf[CHUNKSIZE];
197  ssize_t received = read(fdin, buf, sizeof(buf));
198 
199  if (received > 0) {
200  buffer.append(buf, received);
201  if (buffer.length() >= min_len) RETURN(true);
202  continue;
203  }
204 
205  if (received == 0) {
206  RETURN(false);
207  }
208 
209  LOGLINE(REMOTE, "read gave errno = " << errno);
210  if (errno == EINTR) continue;
211 
212  if (errno != EAGAIN)
213  throw Xapian::NetworkError("read failed", context, errno);
214 
215  Assert(end_time != 0.0);
216  while (true) {
217  // Calculate how far in the future end_time is.
218  double now = RealTime::now();
219  double time_diff = end_time - now;
220  // Check if the timeout has expired.
221  if (time_diff < 0) {
222  LOGLINE(REMOTE, "read: timeout has expired");
223  throw_timeout("Timeout expired while trying to read", context);
224  }
225 
226  // Wait until there is data, an error, or the timeout is reached.
227 # ifdef HAVE_POLL
228  struct pollfd fds;
229  fds.fd = fdin;
230  fds.events = POLLIN;
231  int poll_result = poll(&fds, 1, int(time_diff * 1000));
232  if (poll_result > 0) break;
233 
234  if (poll_result == 0)
235  throw_timeout("Timeout expired while trying to read", context);
236 
237  // EINTR means poll was interrupted by a signal. EAGAIN means that
238  // allocation of internal data structures failed.
239  if (errno != EINTR && errno != EAGAIN)
240  throw Xapian::NetworkError("poll failed during read",
241  context, errno);
242 # else
243  if (fdin >= FD_SETSIZE) {
244  // We can't block with a timeout, so just sleep and retry.
245  RealTime::sleep(now + min(0.001, time_diff / 4));
246  break;
247  }
248  fd_set fdset;
249  FD_ZERO(&fdset);
250  FD_SET(fdin, &fdset);
251 
252  struct timeval tv;
253  RealTime::to_timeval(time_diff, &tv);
254  int select_result = select(fdin + 1, &fdset, 0, 0, &tv);
255  if (select_result > 0) break;
256 
257  if (select_result == 0)
258  throw_timeout("Timeout expired while trying to read", context);
259 
260  // EINTR means select was interrupted by a signal. The Linux
261  // select(2) man page says: "Portable programs may wish to check
262  // for EAGAIN and loop, just as with EINTR" and that seems to be
263  // necessary for cygwin at least.
264  if (errno != EINTR && errno != EAGAIN)
265  throw Xapian::NetworkError("select failed during read",
266  context, errno);
267 # endif
268  }
269  }
270 #endif
271  RETURN(true);
272 }
273 
274 #ifndef __WIN32__
275 ssize_t
276 RemoteConnection::send_or_write(const void* p, size_t len)
277 {
278 # ifdef USE_MSG_NOSIGNAL
279  if (send_flags) {
280  ssize_t n = send(fdout, p, len, send_flags);
281  if (usual(n >= 0 || errno != ENOTSOCK)) return n;
282  // In some testcases in the testsuite and in xapian-progsrv (in some
283  // cases) fdout won't be a socket. Clear send_flags so we only try
284  // send() once in this case.
285  send_flags = 0;
286  }
287 # endif
288  return write(fdout, p, len);
289 }
290 #endif
291 
292 void
293 RemoteConnection::send_message(char type, string_view message, double end_time)
294 {
295  LOGCALL_VOID(REMOTE, "RemoteConnection::send_message", type | message | end_time);
296  if (fdout == -1)
298 
299  string header;
300  header += type;
301  pack_uint(header, message.size());
302  string_view header_view = header;
303 
304 #ifdef __WIN32__
305  HANDLE hout = fd_to_handle(fdout);
306  const string_view* str = &header_view;
307 
308  size_t count = 0;
309  while (true) {
310  DWORD n;
311  BOOL ok = WriteFile(hout, str->data() + count, str->size() - count, &n, &overlapped);
312  if (!ok) {
313  int errcode = GetLastError();
314  if (errcode != ERROR_IO_PENDING)
315  throw Xapian::NetworkError("write failed", context, -errcode);
316  // Just wait for the data to be sent, or a timeout.
317  DWORD waitrc;
318  waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
319  if (waitrc != WAIT_OBJECT_0) {
320  LOGLINE(REMOTE, "write: timeout has expired");
321  throw_timeout("Timeout expired while trying to write", context);
322  }
323  // Get the final result.
324  if (!GetOverlappedResult(hout, &overlapped, &n, FALSE))
325  throw Xapian::NetworkError("Failed to get overlapped result",
326  context, -int(GetLastError()));
327  }
328 
329  count += n;
330 
331  // We must update the offset in the OVERLAPPED structure manually.
332  update_overlapped_offset(overlapped, n);
333 
334  if (count == str->size()) {
335  if (str == &message || message.empty()) return;
336  str = &message;
337  count = 0;
338  }
339  }
340 #else
341  // If there's no end_time, just use blocking I/O.
342  if (fcntl(fdout, F_SETFL, (end_time != 0.0) ? O_NONBLOCK : 0) < 0) {
343  throw Xapian::NetworkError("Failed to set fdout non-blocking-ness",
344  context, errno);
345  }
346 
347  const string_view* str = &header_view;
348 
349  size_t count = 0;
350  while (true) {
351  // We've set write to non-blocking, so just try writing as there
352  // will usually be space.
353  ssize_t n = send_or_write(str->data() + count, str->size() - count);
354 
355  if (n >= 0) {
356  count += n;
357  if (count == str->size()) {
358  if (str == &message || message.empty()) return;
359  str = &message;
360  count = 0;
361  }
362  continue;
363  }
364 
365  LOGLINE(REMOTE, "write gave errno = " << errno);
366  if (errno == EINTR) continue;
367 
368  if (errno != EAGAIN)
369  throw Xapian::NetworkError("write failed", context, errno);
370 
371  double now = RealTime::now();
372  double time_diff = end_time - now;
373  if (time_diff < 0) {
374  LOGLINE(REMOTE, "write: timeout has expired");
375  throw_timeout("Timeout expired while trying to write", context);
376  }
377 
378  // Wait until there is space or the timeout is reached.
379 # ifdef HAVE_POLL
380  struct pollfd fds;
381  fds.fd = fdout;
382  fds.events = POLLOUT;
383  int result = poll(&fds, 1, int(time_diff * 1000));
384 # define POLLSELECT "poll"
385 # else
386  if (fdout >= FD_SETSIZE) {
387  // We can't block with a timeout, so just sleep and retry.
388  RealTime::sleep(now + min(0.001, time_diff / 4));
389  continue;
390  }
391 
392  fd_set fdset;
393  FD_ZERO(&fdset);
394  FD_SET(fdout, &fdset);
395 
396  struct timeval tv;
397  RealTime::to_timeval(time_diff, &tv);
398  int result = select(fdout + 1, 0, &fdset, 0, &tv);
399 # define POLLSELECT "select"
400 # endif
401 
402  if (result < 0) {
403  if (errno == EINTR || errno == EAGAIN) {
404  // EINTR/EAGAIN means select was interrupted by a signal.
405  // We could just retry the poll/select, but it's easier to just
406  // retry the write.
407  continue;
408  }
409  throw Xapian::NetworkError(POLLSELECT " failed during write",
410  context, errno);
411 # undef POLLSELECT
412  }
413 
414  if (result == 0)
415  throw_timeout("Timeout expired while trying to write", context);
416  }
417 #endif
418 }
419 
420 void
421 RemoteConnection::send_file(char type, int fd, double end_time)
422 {
423  LOGCALL_VOID(REMOTE, "RemoteConnection::send_file", type | fd | end_time);
424  if (fdout == -1)
426 
427  auto size = file_size(fd);
428  if (errno)
429  throw Xapian::NetworkError("Couldn't stat file to send", errno);
430  // FIXME: Use sendfile() or similar if available?
431 
432  char buf[CHUNKSIZE];
433  buf[0] = type;
434  size_t c = 1;
435  {
436  string enc_size;
437  pack_uint(enc_size, size);
438  c += enc_size.size();
439  // An encoded length should be just a few bytes.
440  AssertRel(c, <=, sizeof(buf));
441  memcpy(buf + 1, enc_size.data(), enc_size.size());
442  }
443 
444 #ifdef __WIN32__
445  HANDLE hout = fd_to_handle(fdout);
446  size_t count = 0;
447  while (true) {
448  DWORD n;
449  BOOL ok = WriteFile(hout, buf + count, c - count, &n, &overlapped);
450  if (!ok) {
451  int errcode = GetLastError();
452  if (errcode != ERROR_IO_PENDING)
453  throw Xapian::NetworkError("write failed", context, -errcode);
454  // Just wait for the data to be sent, or a timeout.
455  DWORD waitrc;
456  waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
457  if (waitrc != WAIT_OBJECT_0) {
458  LOGLINE(REMOTE, "write: timeout has expired");
459  throw_timeout("Timeout expired while trying to write", context);
460  }
461  // Get the final result.
462  if (!GetOverlappedResult(hout, &overlapped, &n, FALSE))
463  throw Xapian::NetworkError("Failed to get overlapped result",
464  context, -int(GetLastError()));
465  }
466 
467  count += n;
468 
469  // We must update the offset in the OVERLAPPED structure manually.
470  update_overlapped_offset(overlapped, n);
471 
472  if (count == c) {
473  if (size == 0) return;
474 
475  ssize_t res;
476  do {
477  res = read(fd, buf, sizeof(buf));
478  } while (res < 0 && errno == EINTR);
479  if (res < 0) throw Xapian::NetworkError("read failed", errno);
480  c = size_t(res);
481 
482  size -= c;
483  count = 0;
484  }
485  }
486 #else
487  // If there's no end_time, just use blocking I/O.
488  if (fcntl(fdout, F_SETFL, (end_time != 0.0) ? O_NONBLOCK : 0) < 0) {
489  throw Xapian::NetworkError("Failed to set fdout non-blocking-ness",
490  context, errno);
491  }
492 
493  size_t count = 0;
494  while (true) {
495  // We've set write to non-blocking, so just try writing as there
496  // will usually be space.
497  ssize_t n = send_or_write(buf + count, c - count);
498 
499  if (n >= 0) {
500  count += n;
501  if (count == c) {
502  if (size == 0) return;
503 
504  ssize_t res;
505  do {
506  res = read(fd, buf, sizeof(buf));
507  } while (res < 0 && errno == EINTR);
508  if (res < 0) throw Xapian::NetworkError("read failed", errno);
509  c = size_t(res);
510 
511  size -= c;
512  count = 0;
513  }
514  continue;
515  }
516 
517  LOGLINE(REMOTE, "write gave errno = " << errno);
518  if (errno == EINTR) continue;
519 
520  if (errno != EAGAIN)
521  throw Xapian::NetworkError("write failed", context, errno);
522 
523  double now = RealTime::now();
524  double time_diff = end_time - now;
525  if (time_diff < 0) {
526  LOGLINE(REMOTE, "write: timeout has expired");
527  throw_timeout("Timeout expired while trying to write", context);
528  }
529 
530  // Wait until there is space or the timeout is reached.
531 # ifdef HAVE_POLL
532  struct pollfd fds;
533  fds.fd = fdout;
534  fds.events = POLLOUT;
535  int result = poll(&fds, 1, int(time_diff * 1000));
536 # define POLLSELECT "poll"
537 # else
538  if (fdout >= FD_SETSIZE) {
539  // We can't block with a timeout, so just sleep and retry.
540  RealTime::sleep(now + min(0.001, time_diff / 4));
541  continue;
542  }
543 
544  fd_set fdset;
545  FD_ZERO(&fdset);
546  FD_SET(fdout, &fdset);
547 
548  struct timeval tv;
549  RealTime::to_timeval(time_diff, &tv);
550  int result = select(fdout + 1, 0, &fdset, 0, &tv);
551 # define POLLSELECT "select"
552 # endif
553 
554  if (result < 0) {
555  if (errno == EINTR || errno == EAGAIN) {
556  // EINTR/EAGAIN means select was interrupted by a signal.
557  // We could just retry the poll/select, but it's easier to just
558  // retry the write.
559  continue;
560  }
561  throw Xapian::NetworkError(POLLSELECT " failed during write",
562  context, errno);
563 # undef POLLSELECT
564  }
565 
566  if (result == 0)
567  throw_timeout("Timeout expired while trying to write", context);
568  }
569 #endif
570 }
571 
572 int
574 {
575  LOGCALL(REMOTE, int, "RemoteConnection::sniff_next_message_type", end_time);
576  if (fdin == -1)
578 
579  if (!read_at_least(1, end_time))
580  RETURN(-1);
581  unsigned char type = buffer[0];
582  RETURN(type);
583 }
584 
585 int
587 {
588  LOGCALL(REMOTE, int, "RemoteConnection::get_message", result | end_time);
589  if (fdin == -1)
591 
592  if (!read_at_least(2, end_time))
593  RETURN(-1);
594  // This code assume things about the pack_uint() encoding in order to
595  // handle partial reads.
596  size_t len = static_cast<unsigned char>(buffer[1]);
597  if (len < 128) {
598  if (!read_at_least(len + 2, end_time))
599  RETURN(-1);
600  result.assign(buffer.data() + 2, len);
601  unsigned char type = buffer[0];
602  buffer.erase(0, len + 2);
603  RETURN(type);
604  }
605 
606  // We know the message payload is at least 128 bytes of data, and if we
607  // read that much we'll definitely have the whole of the length.
608  if (!read_at_least(128 + 2, end_time))
609  RETURN(-1);
610  const char* p = buffer.data();
611  const char* p_end = p + buffer.size();
612  ++p;
613  if (!unpack_uint(&p, p_end, &len)) {
614  RETURN(-1);
615  }
616  size_t header_len = (p - buffer.data());
617  if (!read_at_least(header_len + len, end_time))
618  RETURN(-1);
619  result.assign(buffer.data() + header_len, len);
620  unsigned char type = buffer[0];
621  buffer.erase(0, header_len + len);
622  RETURN(type);
623 }
624 
625 int
627 {
628  LOGCALL(REMOTE, int, "RemoteConnection::get_message_chunked", end_time);
629 
630  if (fdin == -1)
632 
633  if (!read_at_least(2, end_time))
634  RETURN(-1);
635  // This code assume things about the pack_uint() encoding in order to
636  // handle partial reads.
637  uint_least64_t len = static_cast<unsigned char>(buffer[1]);
638  if (len < 128) {
639  chunked_data_left = len;
640  char type = buffer[0];
641  buffer.erase(0, 2);
642  RETURN(type);
643  }
644 
645  // We know the message payload is at least 128 bytes of data, and if we
646  // read that much we'll definitely have the whole of the length.
647  if (!read_at_least(128 + 2, end_time))
648  RETURN(-1);
649  const char* p = buffer.data();
650  const char* p_end = p + buffer.size();
651  ++p;
652  if (!unpack_uint(&p, p_end, &len)) {
653  RETURN(-1);
654  }
655  chunked_data_left = len;
656  size_t header_len = (p - buffer.data());
657  unsigned char type = buffer[0];
658  buffer.erase(0, header_len);
659  RETURN(type);
660 }
661 
662 int
663 RemoteConnection::get_message_chunk(string &result, size_t at_least,
664  double end_time)
665 {
666  LOGCALL(REMOTE, int, "RemoteConnection::get_message_chunk", result | at_least | end_time);
667  if (fdin == -1)
669 
670  if (at_least <= result.size()) RETURN(true);
671  at_least -= result.size();
672 
673  bool read_enough = (at_least <= chunked_data_left);
674  if (!read_enough) at_least = chunked_data_left;
675 
676  if (!read_at_least(at_least, end_time))
677  RETURN(-1);
678 
679  size_t retlen = min(buffer.size(), chunked_data_left);
680  result.append(buffer, 0, retlen);
681  buffer.erase(0, retlen);
682  chunked_data_left -= retlen;
683 
684  RETURN(int(read_enough));
685 }
686 
688 static void
689 write_all(int fd, const char * p, size_t n)
690 {
691  while (n) {
692  ssize_t c = write(fd, p, n);
693  if (c < 0) {
694  if (errno == EINTR) continue;
695  throw Xapian::NetworkError("Error writing to file", errno);
696  }
697  p += c;
698  n -= c;
699  }
700 }
701 
702 int
703 RemoteConnection::receive_file(const string &file, double end_time)
704 {
705  LOGCALL(REMOTE, int, "RemoteConnection::receive_file", file | end_time);
706  if (fdin == -1)
708 
709  // FIXME: Do we want to be able to delete the file during writing?
710  FD fd(posixy_open(file.c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666));
711  if (fd == -1)
712  throw Xapian::NetworkError("Couldn't open file for writing: " + file, errno);
713 
714  int type = get_message_chunked(end_time);
715  do {
716  size_t min_read = min(chunked_data_left, CHUNKSIZE);
717  if (!read_at_least(min_read, end_time))
718  RETURN(-1);
719  write_all(fd, buffer.data(), min_read);
720  chunked_data_left -= min_read;
721  buffer.erase(0, min_read);
722  } while (chunked_data_left);
723  RETURN(type);
724 }
725 
726 void
728 {
729  LOGCALL_VOID(REMOTE, "RemoteConnection::shutdown", NO_ARGS);
730 
731  if (fdin < 0) return;
732 
733  // We can be called from a destructor, so we can't throw an exception.
734  try {
735  send_message(MSG_SHUTDOWN, {}, 0.0);
736 #ifdef __WIN32__
737  HANDLE hin = fd_to_handle(fdin);
738  char dummy;
739  DWORD received;
740  BOOL ok = ReadFile(hin, &dummy, 1, &received, &overlapped);
741  if (!ok && GetLastError() == ERROR_IO_PENDING) {
742  // Wait for asynchronous read to complete.
743  (void)WaitForSingleObject(overlapped.hEvent, INFINITE);
744  }
745 #else
746  // Wait for the connection to be closed - when this happens
747  // poll()/select() will report that a read won't block.
748 # ifdef HAVE_POLL
749  struct pollfd fds;
750  fds.fd = fdin;
751  fds.events = POLLIN;
752  int res;
753  do {
754  res = poll(&fds, 1, -1);
755  } while (res < 0 && (errno == EINTR || errno == EAGAIN));
756 # else
757  if (fdin < FD_SETSIZE) {
758  fd_set fdset;
759  FD_ZERO(&fdset);
760  FD_SET(fdin, &fdset);
761  int res;
762  do {
763  res = select(fdin + 1, &fdset, 0, 0, NULL);
764  } while (res < 0 && (errno == EINTR || errno == EAGAIN));
765  }
766 # endif
767 #endif
768  } catch (...) {
769  }
770 }
771 
772 void
774 {
775  LOGCALL_VOID(REMOTE, "RemoteConnection::do_close", NO_ARGS);
776 
777  if (fdin >= 0) {
779 
780  // If the same fd is used in both directions, don't close it twice.
781  if (fdin == fdout) fdout = -1;
782 
783  fdin = -1;
784  }
785 
786  if (fdout >= 0) {
788  fdout = -1;
789  }
790 }
791 
792 #ifdef __WIN32__
793 DWORD
794 RemoteConnection::calc_read_wait_msecs(double end_time)
795 {
796  if (end_time == 0.0)
797  return INFINITE;
798 
799  // Calculate how far in the future end_time is.
800  double time_diff = end_time - RealTime::now();
801 
802  // DWORD is unsigned, so we mustn't try and return a negative value.
803  if (time_diff < 0.0) {
804  throw_timeout("Timeout expired before starting read", context);
805  }
806  return static_cast<DWORD>(time_diff * 1000.0);
807 }
808 #endif
Definition: fd.h:30
void send_message(char type, std::string_view s, double end_time)
Send a message.
int fdin
The file descriptor used for reading.
std::string buffer
Buffer to hold unprocessed input.
RemoteConnection(const RemoteConnection &)
Don't allow copying.
int get_message_chunk(std::string &result, size_t at_least, double end_time)
Read a chunk of a message from fdin.
bool read_at_least(size_t min_len, double end_time)
Read until there are at least min_len bytes in buffer.
int fdout
The file descriptor used for writing.
int receive_file(const std::string &file, double end_time)
Save the contents of a message as a file.
void do_close()
Close the connection.
int get_message(std::string &result, double end_time)
Read one message from fdin.
size_t chunked_data_left
Remaining bytes of message data still to come over fdin for a chunked read.
void shutdown()
Shutdown the connection.
int sniff_next_message_type(double end_time)
Check what the next message type is.
int get_message_chunked(double end_time)
Prepare to read one message from fdin in chunks.
std::string context
The context to report with errors.
ssize_t send_or_write(const void *p, size_t n)
Helper which calls send() or write().
void send_file(char type, int fd, double end_time)
Send the contents of a file as a message.
Indicates an attempt to access a closed database.
Definition: error.h:1085
Indicates a problem communicating with a remote database.
Definition: error.h:791
Indicates a timeout expired while communicating with a remote database.
Definition: error.h:833
#define usual(COND)
Definition: config.h:617
PositionList * p
Debug logging macros.
#define RETURN(...)
Definition: debuglog.h:484
#define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS)
Definition: debuglog.h:478
#define LOGLINE(a, b)
Definition: debuglog.h:485
#define LOGCALL_VOID(CATEGORY, FUNC, PARAMS)
Definition: debuglog.h:479
Hierarchy of classes which Xapian can throw as exceptions.
Wrapper class around a file descriptor to avoid leaks.
Utility functions for testing files.
file_size_type file_size(const char *path)
Returns the size of a file.
Definition: filetests.h:76
double end_time(double timeout)
Return the end time for a timeout in timeout seconds.
Definition: realtime.h:95
double now()
Return the current time.
Definition: realtime.h:49
void to_timeval(double t, struct timeval *tv)
Fill in struct timeval from number of seconds in a double.
Definition: realtime.h:110
void sleep(double t)
Sleep until the time represented by this object.
Definition: realtime.h:127
string str(int value)
Convert int to std::string.
Definition: str.cc:91
Various assertion macros.
#define AssertRel(A, REL, B)
Definition: omassert.h:123
#define Assert(COND)
Definition: omassert.h:122
Arithmetic operations with overflow checks.
std::enable_if_t< std::is_unsigned_v< T1 > &&std::is_unsigned_v< T2 > &&std::is_unsigned_v< R >, bool > add_overflows(T1 a, T2 b, R &res)
Addition with overflow checking.
Definition: overflow.h:58
Pack types into strings and unpack them again.
bool unpack_uint(const char **p, const char *end, U *result)
Decode an unsigned integer from a string.
Definition: pack.h:346
void pack_uint(std::string &s, U value)
Append an encoded unsigned integer to a string.
Definition: pack.h:315
Provides wrappers with POSIXy semantics.
#define posixy_open
Functions for handling a time or time interval in a double.
#define POLLSELECT
static void throw_database_closed()
static void throw_timeout(const char *msg, const string &context)
static void write_all(int fd, const char *p, size_t n)
Write n bytes from block pointed to by p to file descriptor fd.
static constexpr size_t CHUNKSIZE
RemoteConnection class used by the remote backend.
@ MSG_SHUTDOWN
include <fcntl.h>, but working around broken platforms.
#define O_CLOEXEC
Definition: safefcntl.h:89
include <sys/select.h> with portability workarounds.
<unistd.h>, but with compat.
Socket handling utilities.
void close_fd_or_socket(int fd)
Definition: socket_utils.h:119
const char * dummy[]
Definition: version_h.cc:7