From 3e346a2a845320c19beab6c281fe4b7c14b88a4a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 1 Nov 2022 15:45:41 +0200 Subject: Add getline_non_blocking(ifdstream&) --- libbutl/fdstream.cxx | 61 +++++++++++++++++++++++++++++++++++++++++++++++----- libbutl/fdstream.hxx | 50 +++++++++++++++++++++++++++++++++++++++++- 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 // bad_alloc #include // numeric_limits #include -#include // memcpy(), memmove() +#include // memcpy(), memmove(), memchr() #include // cin, cout #include // uncaught_exception[s]() #include // 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 (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 (memchr (p, delim, n))); + if (e != nullptr) + n = e - p; + + l.append (p, n); + + // Note: consume the delimiter if found. + // + sb.gbump (static_cast (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. // -- cgit v1.1