Files
nix/src/libutil/file-descriptor.cc
John Ericson b489c8ea15 More IO portability cleanups
- options structs for `drainFD` (both versions)
- portable `read` wrapper
- portable `GetFileSize` wrapper
- dedup `readFile` and `drainFD` using the above
- Use `drainFD` in `PosixSourceAccessor`, avoiding manual IO
- Remove `fromDescriptorReadOnly` entirely!
2026-02-04 15:49:21 -05:00

236 lines
5.4 KiB
C++

#include "nix/util/serialise.hh"
#include "nix/util/util.hh"
#include "nix/util/signals.hh"
#include <span>
#include <fcntl.h>
#include <unistd.h>
#ifdef _WIN32
# include <winnt.h>
# include <fileapi.h>
#endif
namespace nix {
void writeLine(Descriptor fd, std::string s)
{
s += '\n';
writeFull(fd, s);
}
std::string readFile(Descriptor fd)
{
auto size = getFileSize(fd);
// We can't rely on size being correct, most files in /proc have a nominal size of 0
return drainFD(fd, {.size = size, .expected = false});
}
void drainFD(Descriptor fd, Sink & sink, DrainFdSinkOpts opts)
{
#ifndef _WIN32
// silence GCC maybe-uninitialized warning in finally
int saved = 0;
if (!opts.block) {
saved = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
throw SysError("making file descriptor non-blocking");
}
Finally finally([&]() {
if (!opts.block) {
if (fcntl(fd, F_SETFL, saved) == -1)
throw SysError("making file descriptor blocking");
}
});
#endif
size_t bytesRead = 0;
std::array<std::byte, 64 * 1024> buf;
while (1) {
checkInterrupt();
size_t toRead = buf.size();
if (opts.expectedSize) {
size_t remaining = *opts.expectedSize - bytesRead;
if (remaining == 0)
break;
toRead = std::min(toRead, remaining);
}
size_t n;
try {
n = read(fd, std::span(buf.data(), toRead));
} catch (SystemError & e) {
#ifndef _WIN32
if (!opts.block
&& (e.is(std::errc::resource_unavailable_try_again) || e.is(std::errc::operation_would_block)))
break;
#endif
if (e.is(std::errc::interrupted))
continue;
throw;
}
if (n == 0) {
if (opts.expectedSize && bytesRead < *opts.expectedSize)
throw EndOfFile("unexpected end-of-file");
break;
}
bytesRead += n;
sink(std::string_view(reinterpret_cast<const char *>(buf.data()), n));
}
}
std::string drainFD(Descriptor fd, DrainFdOpts opts)
{
// the parser needs two extra bytes to append terminating characters, other users will
// not care very much about the extra memory.
size_t reserveSize = opts.expected ? 0 : opts.size;
StringSink sink(reserveSize + 2);
DrainFdSinkOpts sinkOpts{
.expectedSize = opts.expected ? std::optional<size_t>(opts.size) : std::nullopt,
#ifndef _WIN32
.block = opts.block,
#endif
};
drainFD(fd, sink, sinkOpts);
return std::move(sink.s);
}
void copyFdRange(Descriptor fd, off_t offset, size_t nbytes, Sink & sink)
{
auto left = nbytes;
std::array<std::byte, 64 * 1024> buf;
while (left) {
checkInterrupt();
auto limit = std::min<size_t>(left, buf.size());
/* Should be initialized before we read, because the `catch`
block either throws or continues. */
size_t n;
try {
n = readOffset(fd, offset, std::span(buf.data(), limit));
} catch (SystemError & e) {
if (e.is(std::errc::interrupted))
continue;
throw;
}
if (n == 0)
throw EndOfFile("unexpected end-of-file");
assert(n <= left);
sink(std::string_view(reinterpret_cast<const char *>(buf.data()), n));
offset += n;
left -= n;
}
}
//////////////////////////////////////////////////////////////////////
AutoCloseFD::AutoCloseFD()
: fd{INVALID_DESCRIPTOR}
{
}
AutoCloseFD::AutoCloseFD(Descriptor fd)
: fd{fd}
{
}
// NOTE: This can be noexcept since we are just copying a value and resetting
// the file descriptor in the rhs.
AutoCloseFD::AutoCloseFD(AutoCloseFD && that) noexcept
: fd{that.fd}
{
that.fd = INVALID_DESCRIPTOR;
}
AutoCloseFD & AutoCloseFD::operator=(AutoCloseFD && that)
{
close();
fd = that.fd;
that.fd = INVALID_DESCRIPTOR;
return *this;
}
AutoCloseFD::~AutoCloseFD()
{
try {
close();
} catch (...) {
ignoreExceptionInDestructor();
}
}
Descriptor AutoCloseFD::get() const
{
return fd;
}
void AutoCloseFD::close()
{
if (fd != INVALID_DESCRIPTOR) {
if (
#ifdef _WIN32
::CloseHandle(fd)
#else
::close(fd)
#endif
== -1)
/* This should never happen. */
throw NativeSysError("closing file descriptor %1%", fd);
fd = INVALID_DESCRIPTOR;
}
}
void AutoCloseFD::fsync() const
{
if (fd != INVALID_DESCRIPTOR) {
int result;
result =
#ifdef _WIN32
::FlushFileBuffers(fd)
#elif defined(__APPLE__)
::fcntl(fd, F_FULLFSYNC)
#else
::fsync(fd)
#endif
;
if (result == -1)
throw NativeSysError("fsync file descriptor %1%", fd);
}
}
void AutoCloseFD::startFsync() const
{
#ifdef __linux__
if (fd != -1) {
/* Ignore failure, since fsync must be run later anyway. This is just a performance optimization. */
::sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE);
}
#endif
}
AutoCloseFD::operator bool() const
{
return fd != INVALID_DESCRIPTOR;
}
Descriptor AutoCloseFD::release()
{
Descriptor oldFD = fd;
fd = INVALID_DESCRIPTOR;
return oldFD;
}
//////////////////////////////////////////////////////////////////////
void Pipe::close()
{
readSide.close();
writeSide.close();
}
} // namespace nix