aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-11-01 15:45:41 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-11-01 15:45:41 +0200
commit3e346a2a845320c19beab6c281fe4b7c14b88a4a (patch)
tree78704702a0f016dec2ee52ddeece5c0536eb19de
parentbc6050a7b42581f0d4fbbe3f4b38b7ed02239d72 (diff)
Add getline_non_blocking(ifdstream&)
-rw-r--r--libbutl/fdstream.cxx61
-rw-r--r--libbutl/fdstream.hxx50
2 files changed, 105 insertions, 6 deletions
diff --git a/libbutl/fdstream.cxx b/libbutl/fdstream.cxx
index 03c007c..b4fe4b6 100644
--- a/libbutl/fdstream.cxx
+++ b/libbutl/fdstream.cxx
@@ -41,7 +41,7 @@
#include <new> // bad_alloc
#include <limits> // numeric_limits
#include <cassert>
-#include <cstring> // memcpy(), memmove()
+#include <cstring> // memcpy(), memmove(), memchr()
#include <iostream> // cin, cout
#include <exception> // uncaught_exception[s]()
#include <stdexcept> // invalid_argument
@@ -846,7 +846,7 @@ namespace butl
}
ifdstream&
- getline (ifdstream& is, string& s, char delim)
+ getline (ifdstream& is, string& l, char delim)
{
ifdstream::iostate eb (is.exceptions ());
assert (eb & ifdstream::badbit);
@@ -863,7 +863,7 @@ namespace butl
if (eb != ifdstream::badbit)
is.exceptions (ifdstream::badbit);
- std::getline (is, s, delim);
+ std::getline (is, l, delim);
// Throw if any of the newly set bits are present in the exception mask.
//
@@ -876,6 +876,55 @@ namespace butl
return is;
}
+ bool
+ getline_non_blocking (ifdstream& is, std::string& l, char delim)
+ {
+ assert (!is.blocking () && (is.exceptions () &ifdstream::badbit) != 0);
+
+ fdstreambuf& sb (*static_cast<fdstreambuf*> (is.rdbuf ()));
+
+ // Read until blocked (0), EOF (-1) or encounter the delimiter.
+ //
+ // Note that here we reasonably assume that any failure in in_avail()
+ // will lead to badbit and thus an exception (see showmanyc()).
+ //
+ streamsize s;
+ while ((s = sb.in_avail ()) > 0)
+ {
+ const char* p (sb.gptr ());
+ size_t n (sb.egptr () - p);
+
+ const char* e (static_cast<const char*> (memchr (p, delim, n)));
+ if (e != nullptr)
+ n = e - p;
+
+ l.append (p, n);
+
+ // Note: consume the delimiter if found.
+ //
+ sb.gbump (static_cast<int> (n + (e != nullptr ? 1 : 0)));
+
+ if (e != nullptr)
+ break;
+ }
+
+ // Here s can be:
+ //
+ // -1 -- EOF.
+ // 0 -- blocked before encountering delimiter/EOF.
+ // >0 -- encountered the delimiter.
+ //
+ if (s == -1 && l.empty ())
+ {
+ // If we couldn't extract anything, not even the delimiter, then this is
+ // a failure per the getline() interface.
+ //
+ is.setstate (ifdstream::failbit);
+ }
+
+ return s != 0;
+ }
+
// ofdstream
//
ofdstream::
@@ -1412,6 +1461,8 @@ namespace butl
for (fdselect_state& s: from)
{
+ s.ready = false;
+
if (s.fd == nullfd)
continue;
@@ -1419,7 +1470,6 @@ namespace butl
throw invalid_argument ("invalid file descriptor");
FD_SET (s.fd, &to);
- s.ready = false;
if (max_fd < s.fd)
max_fd = s.fd;
@@ -1907,13 +1957,14 @@ namespace butl
for (fdselect_state& s: read)
{
+ s.ready = false;
+
if (s.fd == nullfd)
continue;
if (s.fd < 0)
throw invalid_argument ("invalid file descriptor");
- s.ready = false;
++n;
}
diff --git a/libbutl/fdstream.hxx b/libbutl/fdstream.hxx
index 730d4dd..1a8f9a6 100644
--- a/libbutl/fdstream.hxx
+++ b/libbutl/fdstream.hxx
@@ -652,6 +652,54 @@ namespace butl
LIBBUTL_SYMEXPORT ifdstream&
getline (ifdstream&, std::string&, char delim = '\n');
+ // The non-blocking getline() version that reads the line in potentially
+ // multiple calls. Key differences compared to getline():
+ //
+ // - Stream must be in the non-blocking mode and exception mask must have
+ // at least badbit.
+ //
+ // - Return type is bool instead of stream. Return true if the line has been
+ // read or false if it should be called again once the stream has more
+ // data to read. Also return true on failure.
+ //
+ // - The string must be empty on the first call.
+ //
+ // - There could still be data to read in the stream's buffer (as opposed to
+ // file descriptor) after this function returns true and you should be
+ // careful not to block on fdselect() in this case. In fact, the
+ // recommended pattern is to call this function first and only call
+ // fdselect() if it returns false.
+ //
+ // The typical usage in combination with the eof() helper:
+ //
+ // fdselect_set fds {is.fd (), ...};
+ // fdselect_state& ist (fds[0]);
+ // fdselect_state& ...;
+ //
+ // for (string l; ist.fd != nullfd || ...; )
+ // {
+ // if (ist.fd != nullfd && getline_non_blocking (is, l))
+ // {
+ // if (eof (is))
+ // ist.fd = nullfd;
+ // else
+ // {
+ // // Consume line.
+ //
+ // l.clear ();
+ // }
+ //
+ // continue;
+ // }
+ //
+ // ifdselect (fds);
+ //
+ // // Handle other ready fds.
+ // }
+ //
+ LIBBUTL_SYMEXPORT bool
+ getline_non_blocking (ifdstream&, std::string&, char delim = '\n');
+
// Open a file returning an auto_fd that holds its file descriptor on
// success and throwing ios::failure otherwise.
//
@@ -858,7 +906,7 @@ namespace butl
// underlying OS error.
//
// Note that the function clears all the previously-ready entries on each
- // call. Entries with nullfd are ignored.
+ // call. Entries with nullfd are ignored (but cleared).
//
// On Windows only pipes and only their input (read) ends are supported.
//