/* A handle to a directory
(C) 2017 Niall Douglas (20 commits)
File Created: Aug 2017
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License in the accompanying file
Licence.txt or at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file Licence.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
#include "../../../directory_handle.hpp"
#include "import.hpp"
#ifdef QUICKCPPLIB_ENABLE_VALGRIND
#include "quickcpplib/valgrind/memcheck.h" // from quickcpplib include directory
#define LLFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(a, b) VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE((a), (b))
#else
#define LLFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(a, b)
#endif
#include /* Defines DT_* constants */
#include
#include
#include
LLFIO_V2_NAMESPACE_BEGIN
result directory_handle::directory(const path_handle &base, path_view_type path, mode _mode, creation _creation, caching _caching,
flag flags) noexcept
{
if(flags & flag::unlink_on_first_close)
{
return errc::invalid_argument;
}
result ret(directory_handle(native_handle_type(), 0, 0, _caching, flags));
native_handle_type &nativeh = ret.value()._v;
LLFIO_LOG_FUNCTION_CALL(&ret);
nativeh.behaviour |= native_handle_type::disposition::directory;
// POSIX does not permit directory opens with O_RDWR like Windows, so silently convert to read
if(_mode == mode::attr_write)
{
_mode = mode::attr_read;
}
else if(_mode == mode::write || _mode == mode::append)
{
_mode = mode::read;
}
// Also trying to truncate a directory returns EISDIR
if(_creation == creation::truncate_existing)
{
return errc::is_a_directory;
}
OUTCOME_TRY(auto &&attribs, attribs_from_handle_mode_caching_and_flags(nativeh, _mode, _creation, _caching, flags));
attribs &= ~O_NONBLOCK;
nativeh.behaviour &= ~native_handle_type::disposition::nonblocking;
nativeh.behaviour &= ~native_handle_type::disposition::seekable; // not seekable
#ifdef O_DIRECTORY
attribs |= O_DIRECTORY;
#endif
#ifdef O_SEARCH
attribs |= O_SEARCH;
#endif
if(base.is_valid() && path.empty())
{
// It can happen that we are passed a base fd and no leafname, in which case he
// really ought to be cloning the handle. But let's humour him.
path = ".";
}
path_view::c_str<> zpath(path, path_view::zero_terminated);
auto rename_random_dir_over_existing_dir = [_mode, _caching, flags](const path_handle &base, path_view_type path) -> result {
// Take a path handle to the directory containing the file
auto path_parent = path.parent_path();
path_handle dirh;
if(base.is_valid() && path_parent.empty())
{
OUTCOME_TRY(auto &&dh, base.clone());
dirh = path_handle(std::move(dh));
}
else if(!path_parent.empty())
{
OUTCOME_TRY(auto &&dh, path_handle::path(base, path_parent));
dirh = std::move(dh);
}
// Create a randomly named directory, and rename it over
OUTCOME_TRY(auto &&rfh, uniquely_named_directory(dirh, _mode, _caching, flags));
auto r = rfh.relink(dirh, path.filename());
if(r)
{
return std::move(rfh);
}
// If failed to rename, remove
(void) rfh.unlink();
return std::move(r).error();
};
if(base.is_valid())
{
if(_creation == creation::if_needed || _creation == creation::only_if_not_exist || _creation == creation::always_new)
{
if(-1 == ::mkdirat(base.native_handle().fd, zpath.buffer, 0x1f8 /*770*/))
{
if(EEXIST != errno || _creation == creation::only_if_not_exist)
{
return posix_error();
}
if(_creation == creation::always_new)
{
auto r = rename_random_dir_over_existing_dir(base, path);
if(!r)
{
return std::move(r).error();
}
ret = std::move(r).value();
goto opened;
}
}
attribs &= ~(O_CREAT | O_EXCL);
}
nativeh.fd = ::openat(base.native_handle().fd, zpath.buffer, attribs);
}
else
{
if(_creation == creation::if_needed || _creation == creation::only_if_not_exist || _creation == creation::always_new)
{
if(-1 == ::mkdir(zpath.buffer, 0x1f8 /*770*/))
{
if(EEXIST != errno || _creation == creation::only_if_not_exist)
{
return posix_error();
}
if(_creation == creation::always_new)
{
auto r = rename_random_dir_over_existing_dir(base, path);
if(!r)
{
return std::move(r).error();
}
ret = std::move(r).value();
goto opened;
}
}
attribs &= ~(O_CREAT | O_EXCL);
}
nativeh.fd = ::open(zpath.buffer, attribs);
}
if(-1 == nativeh.fd)
{
return posix_error();
}
opened:
if(!(flags & flag::disable_safety_unlinks))
{
if(!ret.value()._fetch_inode())
{
// If fetching inode failed e.g. were opening device, disable safety unlinks
ret.value()._flags &= ~flag::disable_safety_unlinks;
}
}
if(ret.value().are_safety_barriers_issued())
{
fsync(nativeh.fd);
}
return ret;
}
result directory_handle::reopen(mode mode_, caching caching_, deadline d) const noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
// Fast path
if(mode_ == mode::unchanged && caching_ == caching::unchanged)
{
result ret(directory_handle(native_handle_type(), _devid, _inode, kernel_caching(), _flags));
ret.value()._v.behaviour = _v.behaviour;
ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC, 0);
if(-1 == ret.value()._v.fd)
{
return posix_error();
}
return ret;
}
// Slow path
std::chrono::steady_clock::time_point began_steady;
std::chrono::system_clock::time_point end_utc;
if(d)
{
if(d.steady)
{
began_steady = std::chrono::steady_clock::now();
}
else
{
end_utc = d.to_time_point();
}
}
for(;;)
{
// Get the current path of myself
OUTCOME_TRY(auto &¤tpath, current_path());
// Open myself
auto fh = directory({}, currentpath, mode_, creation::open_existing, caching_, _flags);
if(fh)
{
if(fh.value().unique_id() == unique_id())
{
return fh;
}
}
else
{
if(fh.error() != errc::no_such_file_or_directory)
{
return std::move(fh).error();
}
}
// Check timeout
if(d)
{
if(d.steady)
{
if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs)))
{
return errc::timed_out;
}
}
else
{
if(std::chrono::system_clock::now() >= end_utc)
{
return errc::timed_out;
}
}
}
}
}
LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result directory_handle::clone_to_path_handle() const noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
result ret(path_handle(native_handle_type(), kernel_caching(), _flags));
ret.value()._v.behaviour = _v.behaviour;
ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC, 0);
if(-1 == ret.value()._v.fd)
{
return posix_error();
}
return ret;
}
result directory_handle::read(io_request req, deadline /*unused*/) const noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
if(req.buffers.empty())
{
return std::move(req.buffers);
}
// Is glob a single entry match? If so, this is really a stat call
path_view_type::c_str<> zglob(req.glob, path_view::zero_terminated);
if(!req.glob.empty() && !req.glob.contains_glob())
{
struct stat s
{
};
if(-1 == ::fstatat(_v.fd, zglob.buffer, &s, AT_SYMLINK_NOFOLLOW))
{
return posix_error();
}
req.buffers[0].stat.st_dev = s.st_dev;
req.buffers[0].stat.st_ino = s.st_ino;
req.buffers[0].stat.st_type = to_st_type(s.st_mode);
req.buffers[0].stat.st_perms = s.st_mode & 0xfff;
req.buffers[0].stat.st_nlink = s.st_nlink;
req.buffers[0].stat.st_uid = s.st_uid;
req.buffers[0].stat.st_gid = s.st_gid;
req.buffers[0].stat.st_rdev = s.st_rdev;
#ifdef __ANDROID__
req.buffers[0].stat.st_atim = to_timepoint(*((struct timespec *) &s.st_atime));
req.buffers[0].stat.st_mtim = to_timepoint(*((struct timespec *) &s.st_mtime));
req.buffers[0].stat.st_ctim = to_timepoint(*((struct timespec *) &s.st_ctime));
#elif defined(__APPLE__)
req.buffers[0].stat.st_atim = to_timepoint(s.st_atimespec);
req.buffers[0].stat.st_mtim = to_timepoint(s.st_mtimespec);
req.buffers[0].stat.st_ctim = to_timepoint(s.st_ctimespec);
#else // Linux and BSD
req.buffers[0].stat.st_atim = to_timepoint(s.st_atim);
req.buffers[0].stat.st_mtim = to_timepoint(s.st_mtim);
req.buffers[0].stat.st_ctim = to_timepoint(s.st_ctim);
#endif
req.buffers[0].stat.st_size = s.st_size;
req.buffers[0].stat.st_allocated = static_cast(s.st_blocks) * 512;
req.buffers[0].stat.st_blocks = s.st_blocks;
req.buffers[0].stat.st_blksize = s.st_blksize;
#ifdef HAVE_STAT_FLAGS
req.buffers[0].stat.st_flags = s.st_flags;
#endif
#ifdef HAVE_STAT_GEN
req.buffers[0].stat.st_gen = s.st_gen;
#endif
#ifdef HAVE_BIRTHTIMESPEC
#if defined(__APPLE__)
req.buffers[0].stat.st_birthtim = to_timepoint(s.st_birthtimespec);
#else
req.buffers[0].stat.st_birthtim = to_timepoint(s.st_birthtim);
#endif
#endif
req.buffers[0].stat.st_sparse =
static_cast((static_cast(s.st_blocks) * 512) < static_cast(s.st_size));
req.buffers._resize(1);
static constexpr stat_t::want default_stat_contents = stat_t::want::dev | stat_t::want::ino | stat_t::want::type | stat_t::want::perms |
stat_t::want::nlink | stat_t::want::uid | stat_t::want::gid | stat_t::want::rdev |
stat_t::want::atim | stat_t::want::mtim | stat_t::want::ctim | stat_t::want::size |
stat_t::want::allocated | stat_t::want::blocks | stat_t::want::blksize
#ifdef HAVE_STAT_FLAGS
| stat_t::want::flags
#endif
#ifdef HAVE_STAT_GEN
| stat_t::want::gen
#endif
#ifdef HAVE_BIRTHTIMESPEC
| stat_t::want::birthtim
#endif
| stat_t::want::sparse;
req.buffers._metadata = default_stat_contents;
req.buffers._done = true;
return std::move(req.buffers);
}
#ifdef __linux__
// Unlike FreeBSD, Linux doesn't define a getdents() function, so we'll do that here.
using getdents64_t = int (*)(int, char *, unsigned int);
static auto getdents = static_cast([](int fd, char *buf, unsigned count) -> int { return syscall(SYS_getdents64, fd, buf, count); });
using dirent = dirent64;
#endif
#ifdef __APPLE__
// OS X defines a getdirentries64() kernel syscall which can emulate getdents
typedef int (*getdents_emulation_t)(int, char *, unsigned);
static getdents_emulation_t getdents = static_cast([](int fd, char *buf, unsigned count) -> int {
off_t foo;
return syscall(SYS_getdirentries64, fd, buf, count, &foo);
});
#endif
if(!req.buffers._kernel_buffer && req.kernelbuffer.empty())
{
// Let's assume the average leafname will be 64 characters long.
size_t toallocate = (sizeof(dirent) + 64) * req.buffers.size();
auto *mem = (char *) operator new[](toallocate, std::nothrow); // don't initialise
if(mem == nullptr)
{
return errc::not_enough_memory;
}
req.buffers._kernel_buffer = std::unique_ptr(mem);
req.buffers._kernel_buffer_size = toallocate;
}
stat_t::want default_stat_contents = stat_t::want::ino | stat_t::want::type;
dirent *buffer;
size_t bytesavailable, bytes;
bool done = false;
do
{
buffer = req.kernelbuffer.empty() ? reinterpret_cast(req.buffers._kernel_buffer.get()) : reinterpret_cast(req.kernelbuffer.data());
bytesavailable = req.kernelbuffer.empty() ? req.buffers._kernel_buffer_size : req.kernelbuffer.size();
while(_lock.exchange(1, std::memory_order_relaxed) != 0)
{
std::this_thread::yield();
}
auto unlock = make_scope_exit([this]() noexcept { _lock.store(0, std::memory_order_release); });
(void) unlock;
// Seek to start
#ifdef __linux__
if(-1 == ::lseek64(_v.fd, 0, SEEK_SET))
{
return posix_error();
}
#else
if(-1 == ::lseek(_v.fd, 0, SEEK_SET))
return posix_error();
#endif
bytes = 0;
int _bytes;
do
{
assert(bytes <= bytesavailable);
_bytes = getdents(_v.fd, reinterpret_cast(buffer) + bytes, bytesavailable - bytes);
if(_bytes == 0)
{
done = true;
break;
}
if(req.kernelbuffer.empty() && _bytes == -1 && EINVAL == errno)
{
size_t toallocate = req.buffers._kernel_buffer_size * 2;
auto *mem = (char *) operator new[](toallocate, std::nothrow); // don't initialise
if(mem == nullptr)
{
return errc::not_enough_memory;
}
req.buffers._kernel_buffer.reset();
req.buffers._kernel_buffer = std::unique_ptr(mem);
req.buffers._kernel_buffer_size = toallocate;
// We need to reset and do the whole thing against to ensure single shot atomicity
break;
}
else if(_bytes == -1)
{
return posix_error();
}
else
{
assert(_bytes > 0);
bytes += _bytes;
}
} while(!done);
} while(!done);
if(bytes == 0)
{
req.buffers._resize(0);
req.buffers._metadata = default_stat_contents;
req.buffers._done = true;
return std::move(req.buffers);
}
LLFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(buffer, bytes); // NOLINT
size_t n = 0;
for(dirent *dent = buffer;; dent = reinterpret_cast(reinterpret_cast(dent) + dent->d_reclen))
{
if(dent->d_ino != 0u)
{
size_t length = strchr(dent->d_name, 0) - dent->d_name;
if(length <= 2 && '.' == dent->d_name[0])
{
if(1 == length || '.' == dent->d_name[1])
{
goto cont;
}
}
if(!req.glob.empty() && fnmatch(zglob.buffer, dent->d_name, 0) != 0)
{
goto cont;
}
directory_entry &item = req.buffers[n];
item.leafname = path_view(dent->d_name, length, path_view::zero_terminated);
item.stat = stat_t(nullptr);
item.stat.st_ino = dent->d_ino;
char d_type = dent->d_type;
switch(d_type)
{
case DT_BLK:
item.stat.st_type = filesystem::file_type::block;
break;
case DT_CHR:
item.stat.st_type = filesystem::file_type::character;
break;
case DT_DIR:
item.stat.st_type = filesystem::file_type::directory;
break;
case DT_FIFO:
item.stat.st_type = filesystem::file_type::fifo;
break;
case DT_LNK:
item.stat.st_type = filesystem::file_type::symlink;
break;
case DT_REG:
item.stat.st_type = filesystem::file_type::regular;
break;
case DT_SOCK:
item.stat.st_type = filesystem::file_type::socket;
break;
case DT_UNKNOWN:
// Don't say we return type
default_stat_contents = default_stat_contents & ~stat_t::want::type;
break;
}
n++;
}
cont:
if((bytes -= dent->d_reclen) <= 0)
{
// Fill is complete
req.buffers._resize(n);
req.buffers._metadata = default_stat_contents;
req.buffers._done = true;
return std::move(req.buffers);
}
if(n >= req.buffers.size())
{
// Fill is incomplete
req.buffers._metadata = default_stat_contents;
req.buffers._done = false;
return std::move(req.buffers);
}
}
}
LLFIO_V2_NAMESPACE_END