libutil: guess or invent a path from file descriptors
This is useful for certain error recovery paths (no pun intended) that does not thread through the original path name. Change-Id: I2d800740cb4f9912e64c923120d3f977c58ccb7e Signed-off-by: Raito Bezarius <raito@lix.systems>
This commit is contained in:
@@ -227,6 +227,11 @@ configdata += {
|
||||
'HAVE_SECCOMP': seccomp.found().to_int(),
|
||||
}
|
||||
|
||||
# fcntl(F_GETPATH) returns the path of an fd on macOS and BSDs
|
||||
configdata += {
|
||||
'HAVE_F_GETPATH': cxx.has_header_symbol('fcntl.h', 'F_GETPATH').to_int(),
|
||||
}
|
||||
|
||||
libarchive = dependency('libarchive', required : true, include_type : 'system')
|
||||
|
||||
brotli = [
|
||||
|
||||
@@ -156,6 +156,29 @@ int AutoCloseFD::get() const
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::string guessOrInventPathFromFD(int fd)
|
||||
{
|
||||
assert(fd >= 0);
|
||||
/* On Linux, there's no F_GETPATH available.
|
||||
* But we can read /proc/ */
|
||||
#if __linux__
|
||||
try {
|
||||
return readLink(fmt("/proc/self/fd/%1%", fd).c_str());
|
||||
} catch (...) {
|
||||
}
|
||||
#elif defined (HAVE_F_GETPATH) && HAVE_F_GETPATH
|
||||
std::string fdName(PATH_MAX, '\0');
|
||||
if (fcntl(fd, F_GETPATH, fdName.data()) != -1) {
|
||||
fdName.resize(strlen(fdName.c_str()));
|
||||
return fdName;
|
||||
}
|
||||
#else
|
||||
#error "No implementation for retrieving file descriptors path."
|
||||
#endif
|
||||
|
||||
return fmt("<fd %i>", fd);
|
||||
}
|
||||
|
||||
|
||||
void AutoCloseFD::close()
|
||||
{
|
||||
|
||||
@@ -36,6 +36,15 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts = true);
|
||||
*/
|
||||
std::string drainFD(int fd, bool block = true, const size_t reserveSize=0);
|
||||
|
||||
|
||||
/*
|
||||
* Will attempt to guess *A* path associated that might lead to the same file as used by this
|
||||
* file descriptor.
|
||||
*
|
||||
* The returned string should NEVER be used as a valid path.
|
||||
*/
|
||||
std::string guessOrInventPathFromFD(int fd);
|
||||
|
||||
Generator<Bytes> drainFDSource(int fd, bool block = true);
|
||||
|
||||
class AutoCloseFD
|
||||
@@ -50,6 +59,15 @@ public:
|
||||
AutoCloseFD& operator =(const AutoCloseFD & fd) = delete;
|
||||
AutoCloseFD& operator =(AutoCloseFD&& fd) noexcept(false);
|
||||
int get() const;
|
||||
|
||||
/*
|
||||
* Will attempt to guess *A* path associated that might lead to the same file as used by this
|
||||
* file descriptor.
|
||||
*
|
||||
* The returned string should NEVER be used as a valid path.
|
||||
*/
|
||||
std::string guessOrInventPath() const { return guessOrInventPathFromFD(fd); }
|
||||
|
||||
explicit operator bool() const;
|
||||
int release();
|
||||
void close();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "test-data.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
Path getUnitTestData()
|
||||
{
|
||||
return getEnv("_NIX_TEST_UNIT_DATA").value();
|
||||
}
|
||||
|
||||
Path getUnitTestDataPath(std::string_view path)
|
||||
{
|
||||
return absPath(getUnitTestData() + "/" + path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "file-system.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* The path to the unit test data directory. See the contributing guide
|
||||
* in the manual for further details.
|
||||
*/
|
||||
Path getUnitTestData();
|
||||
|
||||
/**
|
||||
* Resolve a path under the unit test data directory to an absolute path.
|
||||
*/
|
||||
Path getUnitTestDataPath(std::string_view path);
|
||||
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "strings.hh"
|
||||
#include "types.hh"
|
||||
#include "terminal.hh"
|
||||
#include "unix-domain-socket.hh"
|
||||
#include "tests/test-data.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -207,6 +209,85 @@ namespace nix {
|
||||
ASSERT_FALSE(pathExists("/schnitzel/darmstadt/pommes"));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* AutoCloseFD::guessOrInventPath
|
||||
* --------------------------------------------------------------------------*/
|
||||
void testGuessOrInventPathPrePostDeletion(AutoCloseFD & fd, Path & path) {
|
||||
{
|
||||
SCOPED_TRACE(fmt("guessing path before deletion of '%1%'", path));
|
||||
ASSERT_TRUE(fd);
|
||||
/* We cannot predict what the platform will return here.
|
||||
* But it cannot fail. */
|
||||
ASSERT_TRUE(fd.guessOrInventPath().size() >= 0);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE(fmt("guessing path after deletion of '%1%'", path));
|
||||
deletePath(path);
|
||||
/* We cannot predict what the platform will return here.
|
||||
* But it cannot fail. */
|
||||
ASSERT_TRUE(fd.guessOrInventPath().size() >= 0);
|
||||
}
|
||||
}
|
||||
TEST(guessOrInventPath, files) {
|
||||
Path filePath = getUnitTestDataPath("guess-or-invent/test.txt");
|
||||
createDirs(dirOf(filePath));
|
||||
writeFile(filePath, "some text");
|
||||
AutoCloseFD file{open(filePath.c_str(), O_RDONLY, 0666)};
|
||||
testGuessOrInventPathPrePostDeletion(file, filePath);
|
||||
}
|
||||
|
||||
TEST(guessOrInventPath, directories) {
|
||||
Path dirPath = getUnitTestDataPath("guess-or-invent/test-dir");
|
||||
createDirs(dirPath);
|
||||
AutoCloseFD directory{open(dirPath.c_str(), O_DIRECTORY, 0666)};
|
||||
testGuessOrInventPathPrePostDeletion(directory, dirPath);
|
||||
}
|
||||
|
||||
#ifdef O_PATH
|
||||
TEST(guessOrInventPath, symlinks) {
|
||||
Path symlinkPath = getUnitTestDataPath("guess-or-invent/test-symlink");
|
||||
Path targetPath = getUnitTestDataPath("guess-or-invent/nowhere");
|
||||
createDirs(dirOf(symlinkPath));
|
||||
createSymlink(targetPath, symlinkPath);
|
||||
AutoCloseFD symlink{open(symlinkPath.c_str(), O_PATH | O_NOFOLLOW, 0666)};
|
||||
testGuessOrInventPathPrePostDeletion(symlink, symlinkPath);
|
||||
}
|
||||
|
||||
TEST(guessOrInventPath, fifos) {
|
||||
Path fifoPath = getUnitTestDataPath("guess-or-invent/fifo");
|
||||
createDirs(dirOf(fifoPath));
|
||||
ASSERT_TRUE(mkfifo(fifoPath.c_str(), 0666) == 0);
|
||||
AutoCloseFD fifo{open(fifoPath.c_str(), O_PATH | O_NOFOLLOW, 0666)};
|
||||
testGuessOrInventPathPrePostDeletion(fifo, fifoPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(guessOrInventPath, pipes) {
|
||||
int pipefd[2];
|
||||
|
||||
ASSERT_TRUE(pipe(pipefd) == 0);
|
||||
|
||||
AutoCloseFD pipe_read{pipefd[0]};
|
||||
ASSERT_TRUE(pipe_read);
|
||||
AutoCloseFD pipe_write{pipefd[1]};
|
||||
ASSERT_TRUE(pipe_write);
|
||||
|
||||
/* We cannot predict what the platform will return here.
|
||||
* But it cannot fail. */
|
||||
ASSERT_TRUE(pipe_read.guessOrInventPath().size() >= 0);
|
||||
ASSERT_TRUE(pipe_write.guessOrInventPath().size() >= 0);
|
||||
pipe_write.close();
|
||||
ASSERT_TRUE(pipe_read.guessOrInventPath().size() >= 0);
|
||||
pipe_read.close();
|
||||
}
|
||||
|
||||
TEST(guessOrInventPath, sockets) {
|
||||
Path socketPath = getUnitTestDataPath("guess-or-invent/socket");
|
||||
createDirs(dirOf(socketPath));
|
||||
AutoCloseFD socket = createUnixDomainSocket(socketPath, 0666);
|
||||
testGuessOrInventPathPrePostDeletion(socket, socketPath);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* concatStringsSep
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
@@ -19,6 +19,7 @@ libutil_test_support_sources = files(
|
||||
'libutil-support/tests/cli-literate-parser.cc',
|
||||
'libutil-support/tests/hash.cc',
|
||||
'libutil-support/tests/terminal-code-eater.cc',
|
||||
'libutil-support/tests/test-data.cc',
|
||||
)
|
||||
libutil_test_support = library(
|
||||
'lixutil-test-support',
|
||||
|
||||
Reference in New Issue
Block a user