Merge pull request #15323 from obsidiansystems/fix-eio-readline
libutil: treat EIO as EOF in `readLine`
This commit is contained in:
@@ -7,6 +7,11 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <stdlib.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
// BufferedSource with configurable small buffer for precise boundary testing.
|
||||
@@ -114,6 +119,55 @@ TEST(ReadLine, LineWithNullBytes)
|
||||
3));
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
TEST(ReadLine, TreatsEioAsEof)
|
||||
{
|
||||
// Open a pty master. When the slave side is closed (or never opened),
|
||||
// reading from the master returns EIO, which readLine should treat as EOF.
|
||||
int master = posix_openpt(O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(master, -1);
|
||||
ASSERT_EQ(grantpt(master), 0);
|
||||
ASSERT_EQ(unlockpt(master), 0);
|
||||
|
||||
// Open and immediately close the slave to trigger EIO on the master.
|
||||
int slave = open(ptsname(master), O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(slave, -1);
|
||||
close(slave);
|
||||
|
||||
// With eofOk=true, readLine should return empty string (treating EIO as EOF).
|
||||
EXPECT_EQ(readLine(master, /*eofOk=*/true), "");
|
||||
|
||||
// With eofOk=false, readLine should throw EndOfFile.
|
||||
EXPECT_THROW(readLine(master), EndOfFile);
|
||||
|
||||
close(master);
|
||||
}
|
||||
|
||||
// macOS (BSD) discards buffered pty data on slave close and returns normal
|
||||
// EOF (0) instead of EIO, so partial data never reaches the master.
|
||||
# ifdef __linux__
|
||||
TEST(ReadLine, PartialLineBeforeEio)
|
||||
{
|
||||
int master = posix_openpt(O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(master, -1);
|
||||
ASSERT_EQ(grantpt(master), 0);
|
||||
ASSERT_EQ(unlockpt(master), 0);
|
||||
|
||||
int slave = open(ptsname(master), O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(slave, -1);
|
||||
|
||||
// Write a partial line (no terminator) from the slave, then close it.
|
||||
ASSERT_EQ(::write(slave, "partial", 7), 7);
|
||||
close(slave);
|
||||
|
||||
// readLine should return the partial data when eofOk=true.
|
||||
EXPECT_EQ(readLine(master, /*eofOk=*/true), "partial");
|
||||
|
||||
close(master);
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
TEST(BufferedSourceReadLine, ReadsLinesFromPipe)
|
||||
{
|
||||
Pipe pipe;
|
||||
|
||||
@@ -76,8 +76,18 @@ std::string readLine(Descriptor fd, bool eofOk, char terminator)
|
||||
checkInterrupt();
|
||||
char ch;
|
||||
// FIXME: inefficient
|
||||
auto rd =
|
||||
retryOnBlock(fd, PollDirection::In, [&]() { return read(fd, {reinterpret_cast<std::byte *>(&ch), 1}); });
|
||||
auto rd = retryOnBlock(fd, PollDirection::In, [&]() -> size_t {
|
||||
try {
|
||||
return read(fd, {reinterpret_cast<std::byte *>(&ch), 1});
|
||||
} catch (SystemError & e) {
|
||||
// On pty masters, EIO signals that the slave side closed,
|
||||
// which is semantically EOF. Map it to a zero-length read
|
||||
// so the existing EOF path handles it.
|
||||
if (e.is(std::errc::io_error))
|
||||
return 0;
|
||||
throw;
|
||||
}
|
||||
});
|
||||
if (rd == 0) {
|
||||
if (eofOk)
|
||||
return s;
|
||||
|
||||
Reference in New Issue
Block a user