const DirEntry::stat_t&
DirEntry::do_stat() const
{
- if (!m_initialized) {
- m_exists = false;
- m_is_symlink = false;
+ if (m_initialized) {
+ return m_stat;
+ }
- auto path = pstr(m_path);
+ m_exists = false;
+ m_is_symlink = false;
- int result = lstat_func(path, &m_stat);
+ int result = 0;
+
+#ifndef _WIN32
+ if (m_fd != -1) {
+ result = fstat(m_fd, &m_stat);
+ if (result == 0) {
+ m_exists = true;
+ }
+ } else
+#endif
+ {
+ auto path = pstr(m_path);
+ result = lstat_func(path, &m_stat);
if (result == 0) {
- m_errno = 0;
if (S_ISLNK(m_stat.st_mode)
#ifdef _WIN32
|| (m_stat.st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
} else {
m_exists = true;
}
- } else {
- m_errno = errno;
- if (m_log_on_error == LogOnError::yes) {
- LOG("Failed to lstat {}: {}", m_path, strerror(m_errno));
- }
}
+ }
- if (!m_exists) {
- // The file is missing, so just zero fill the stat structure. This will
- // make e.g. the is_*() methods return false and mtime() will be 0, etc.
- memset(&m_stat, '\0', sizeof(m_stat));
+ if (result == 0) {
+ m_errno = 0;
+ } else {
+ m_errno = errno;
+ if (m_log_on_error == LogOnError::yes) {
+#ifdef _WIN32
+ LOG("Failed to stat {}: {}", m_path, strerror(m_errno));
+#else
+ LOG("Failed to {} {}: {}",
+ m_fd == -1 ? "stat" : "fstat",
+ m_path,
+ strerror(m_errno));
+#endif
}
+ }
- m_initialized = true;
+ if (!m_exists) {
+ // The file is missing, so just zero fill the stat structure. This will make
+ // the is_*() methods return false and mtime() will be 0, etc.
+ memset(&m_stat, '\0', sizeof(m_stat));
}
+ m_initialized = true;
return m_stat;
}
// 0.
DirEntry() = default;
+ // Stat a path.
+ //
// The underlying (l)stat(2) call will not be made by the constructor but
// on-demand when calling the first query function. That (l)stat result is
// then cached. See also the refresh method.
DirEntry(const std::filesystem::path& path,
LogOnError log_on_error = LogOnError::no);
+#ifndef _WIN32
+ // Stat an open file descriptor.
+ //
+ // The underlying fstat(2) call will not be made by the constructor but
+ // on-demand when calling the first query function. That fstat result is then
+ // cached. See also the refresh method.
+ DirEntry(const std::filesystem::path& path,
+ int fd,
+ LogOnError log_on_error = LogOnError::no);
+#endif
+
// Return true if the file could be lstat(2)-ed (i.e., the directory entry
// exists without following symlinks), otherwise false.
operator bool() const;
private:
std::filesystem::path m_path;
+#ifndef _WIN32
+ int m_fd = -1;
+#endif
LogOnError m_log_on_error = LogOnError::no;
mutable stat_t m_stat;
mutable int m_errno = -1;
{
}
+#ifndef _WIN32
+inline DirEntry::DirEntry(const std::filesystem::path& path,
+ int fd,
+ LogOnError log_on_error)
+ : m_path(path),
+ m_fd(fd),
+ m_log_on_error(log_on_error)
+{
+}
+#endif
+
inline DirEntry::operator bool() const
{
do_stat();
#include "TestUtil.hpp"
#include <ccache/util/DirEntry.hpp>
+#include <ccache/util/Fd.hpp>
#include <ccache/util/Finalizer.hpp>
#include <ccache/util/environment.hpp>
#include <ccache/util/file.hpp>
#include <ccache/util/wincompat.hpp>
#include <doctest.h>
+#include <fcntl.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
}
+#ifndef _WIN32
+TEST_CASE("Stat file descriptor")
+{
+ TestContext test_context;
+
+ util::write_file("a", "123");
+
+ util::Fd fd(open("a", O_RDONLY));
+ DirEntry entry("a", *fd);
+ CHECK(entry);
+ CHECK(entry.exists());
+ CHECK(!entry.is_symlink());
+ CHECK(entry.size() == 3);
+ CHECK(entry.path() == "a");
+}
+#endif
+
TEST_CASE("Caching and refresh")
{
TestContext test_context;
CHECK(!(entry.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
CHECK(entry.reparse_tag() == 0);
}
-#endif
+#endif // _WIN32
TEST_SUITE_END();