/* vim:set ts=2 sw=2 sts=2 et: */
/**
* \author Marcus Holland-Moritz (github@mhxnet.de)
* \copyright Copyright (c) Marcus Holland-Moritz
*
* This file is part of dwarfs.
*
* dwarfs is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* dwarfs is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with dwarfs. If not, see .
*/
#include
#include
#include
#include
#include
#include
#ifdef _WIN32
#include
#else
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace dwarfs {
namespace {
namespace fs = std::filesystem;
#ifdef _WIN32
uint64_t time_from_filetime(FILETIME const& ft) {
static constexpr uint64_t FT_TICKS_PER_SECOND = UINT64_C(10000000);
static constexpr uint64_t FT_EPOCH_OFFSET = UINT64_C(11644473600);
uint64_t ticks =
(static_cast(ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
return (ticks / FT_TICKS_PER_SECOND) - FT_EPOCH_OFFSET;
}
#endif
char get_filetype_label(file_stat::mode_type mode) {
switch (posix_file_type::from_mode(mode)) {
case posix_file_type::regular:
return '-';
case posix_file_type::directory:
return 'd';
case posix_file_type::symlink:
return 'l';
case posix_file_type::block:
return 'b';
case posix_file_type::character:
return 'c';
case posix_file_type::fifo:
return 'p';
case posix_file_type::socket:
return 's';
default:
DWARFS_THROW(runtime_error,
fmt::format("unknown file type: {:#06x}", mode));
}
}
void perms_to_stream(std::ostream& os, file_stat::mode_type mode) {
os << (mode & file_stat::mode_type(fs::perms::owner_read) ? 'r' : '-');
os << (mode & file_stat::mode_type(fs::perms::owner_write) ? 'w' : '-');
os << (mode & file_stat::mode_type(fs::perms::owner_exec) ? 'x' : '-');
os << (mode & file_stat::mode_type(fs::perms::group_read) ? 'r' : '-');
os << (mode & file_stat::mode_type(fs::perms::group_write) ? 'w' : '-');
os << (mode & file_stat::mode_type(fs::perms::group_exec) ? 'x' : '-');
os << (mode & file_stat::mode_type(fs::perms::others_read) ? 'r' : '-');
os << (mode & file_stat::mode_type(fs::perms::others_write) ? 'w' : '-');
os << (mode & file_stat::mode_type(fs::perms::others_exec) ? 'x' : '-');
}
} // namespace
file_stat::file_stat() = default;
#ifdef _WIN32
file_stat::file_stat(fs::path const& path) {
std::error_code ec;
auto status = fs::symlink_status(path, ec);
if (ec) {
status = fs::status(path, ec);
}
if (ec) {
exception_ = std::make_exception_ptr(std::system_error(ec));
return;
}
if (status.type() == fs::file_type::not_found ||
status.type() == fs::file_type::unknown) {
DWARFS_THROW(runtime_error,
fmt::format("{}: {}",
status.type() == fs::file_type::not_found
? "file not found"
: "unknown file type",
u8string_to_string(path.u8string())));
}
valid_fields_ = file_stat::mode_valid;
mode_ = internal::file_status_to_mode(status);
blksize_ = 0;
blocks_ = 0;
auto wps = path.wstring();
if (status.type() == fs::file_type::symlink) {
::WIN32_FILE_ATTRIBUTE_DATA info;
if (::GetFileAttributesExW(wps.c_str(), GetFileExInfoStandard, &info) ==
0) {
exception_ = std::make_exception_ptr(std::system_error(
::GetLastError(), std::system_category(), "GetFileAttributesExW"));
} else {
valid_fields_ = file_stat::all_valid;
dev_ = 0;
ino_ = 0;
nlink_ = 0;
uid_ = 0;
gid_ = 0;
rdev_ = 0;
size_ =
(static_cast(info.nFileSizeHigh) << 32) + info.nFileSizeLow;
atime_ = time_from_filetime(info.ftLastAccessTime);
mtime_ = time_from_filetime(info.ftLastWriteTime);
ctime_ = time_from_filetime(info.ftCreationTime);
}
} else {
struct ::__stat64 st;
if (::_wstat64(wps.c_str(), &st) == 0) {
if (status.type() == fs::file_type::regular) {
::HANDLE hdl =
::CreateFileW(wps.c_str(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (hdl != INVALID_HANDLE_VALUE) {
::BY_HANDLE_FILE_INFORMATION info;
if (::GetFileInformationByHandle(hdl, &info)) {
if (::CloseHandle(hdl)) {
valid_fields_ |= file_stat::ino_valid | file_stat::nlink_valid;
ino_ = (static_cast(info.nFileIndexHigh) << 32) +
info.nFileIndexLow;
nlink_ = info.nNumberOfLinks;
} else {
exception_ = std::make_exception_ptr(std::system_error(
::GetLastError(), std::system_category(), "CloseHandle"));
}
} else {
exception_ = std::make_exception_ptr(
std::system_error(::GetLastError(), std::system_category(),
"GetFileInformationByHandle"));
::CloseHandle(hdl);
}
} else {
exception_ = std::make_exception_ptr(std::system_error(
::GetLastError(), std::system_category(), "CreateFileW"));
}
} else {
valid_fields_ |= file_stat::ino_valid | file_stat::nlink_valid;
ino_ = st.st_ino;
nlink_ = st.st_nlink;
}
valid_fields_ |= file_stat::dev_valid | file_stat::uid_valid |
file_stat::gid_valid | file_stat::rdev_valid |
file_stat::size_valid | file_stat::atime_valid |
file_stat::mtime_valid | file_stat::ctime_valid;
dev_ = st.st_dev;
uid_ = st.st_uid;
gid_ = st.st_gid;
rdev_ = st.st_rdev;
size_ = st.st_size;
atime_ = st.st_atime;
mtime_ = st.st_mtime;
ctime_ = st.st_ctime;
} else {
exception_ = std::make_exception_ptr(
std::system_error(errno, std::generic_category(), "_stat64"));
}
}
}
#else
file_stat::file_stat(fs::path const& path) {
struct ::stat st;
if (::lstat(path.string().c_str(), &st) != 0) {
exception_ = std::make_exception_ptr(
std::system_error(errno, std::generic_category(), "lstat"));
return;
}
valid_fields_ = file_stat::all_valid;
dev_ = st.st_dev;
ino_ = st.st_ino;
nlink_ = st.st_nlink;
mode_ = st.st_mode;
uid_ = st.st_uid;
gid_ = st.st_gid;
rdev_ = st.st_rdev;
size_ = st.st_size;
blksize_ = st.st_blksize;
blocks_ = st.st_blocks;
#ifdef __APPLE__
atime_ = st.st_atimespec.tv_sec;
mtime_ = st.st_mtimespec.tv_sec;
ctime_ = st.st_ctimespec.tv_sec;
#else
atime_ = st.st_atim.tv_sec;
mtime_ = st.st_mtim.tv_sec;
ctime_ = st.st_ctim.tv_sec;
#endif
}
#endif
std::string file_stat::mode_string(mode_type mode) {
std::ostringstream oss;
oss << (mode & mode_type(fs::perms::set_uid) ? 'U' : '-');
oss << (mode & mode_type(fs::perms::set_gid) ? 'G' : '-');
oss << (mode & mode_type(fs::perms::sticky_bit) ? 'S' : '-');
oss << get_filetype_label(mode);
perms_to_stream(oss, mode);
return oss.str();
}
std::string file_stat::perm_string(mode_type mode) {
std::ostringstream oss;
perms_to_stream(oss, mode);
return oss.str();
}
void file_stat::ensure_valid(valid_fields_type fields) const {
if ((valid_fields_ & fields) != fields) {
if (exception_) {
std::rethrow_exception(exception_);
} else {
DWARFS_THROW(runtime_error,
fmt::format("missing stat fields: {:#x} (have: {:#x})",
fields, valid_fields_));
}
}
}
std::filesystem::file_status file_stat::status() const {
ensure_valid(mode_valid);
return internal::file_mode_to_status(mode_);
};
posix_file_type::value file_stat::type() const {
ensure_valid(mode_valid);
return static_cast(mode_ & posix_file_type::mask);
};
file_stat::perms_type file_stat::permissions() const {
ensure_valid(mode_valid);
return mode_ & 07777;
};
void file_stat::set_permissions(perms_type perms) {
mode_ = type() | (perms & 07777);
}
bool file_stat::is_directory() const {
return type() == posix_file_type::directory;
}
bool file_stat::is_regular_file() const {
return type() == posix_file_type::regular;
}
bool file_stat::is_symlink() const {
return type() == posix_file_type::symlink;
}
bool file_stat::is_device() const {
auto t = type();
return t == posix_file_type::block || t == posix_file_type::character;
}
file_stat::dev_type file_stat::dev() const {
ensure_valid(dev_valid);
return dev_;
}
void file_stat::set_dev(dev_type dev) {
valid_fields_ |= dev_valid;
dev_ = dev;
}
file_stat::ino_type file_stat::ino() const {
ensure_valid(ino_valid);
return ino_;
}
void file_stat::set_ino(ino_type ino) {
valid_fields_ |= ino_valid;
ino_ = ino;
}
file_stat::nlink_type file_stat::nlink() const {
ensure_valid(nlink_valid);
return nlink_;
}
void file_stat::set_nlink(nlink_type nlink) {
valid_fields_ |= nlink_valid;
nlink_ = nlink;
}
file_stat::mode_type file_stat::mode() const {
ensure_valid(mode_valid);
return mode_;
}
void file_stat::set_mode(mode_type mode) {
valid_fields_ |= mode_valid;
mode_ = mode;
}
file_stat::uid_type file_stat::uid() const {
ensure_valid(uid_valid);
return uid_;
}
void file_stat::set_uid(uid_type uid) {
valid_fields_ |= uid_valid;
uid_ = uid;
}
file_stat::gid_type file_stat::gid() const {
ensure_valid(gid_valid);
return gid_;
}
void file_stat::set_gid(gid_type gid) {
valid_fields_ |= gid_valid;
gid_ = gid;
}
file_stat::dev_type file_stat::rdev() const {
ensure_valid(rdev_valid);
return rdev_;
}
void file_stat::set_rdev(dev_type rdev) {
valid_fields_ |= rdev_valid;
rdev_ = rdev;
}
file_stat::off_type file_stat::size() const {
ensure_valid(size_valid);
return size_;
}
void file_stat::set_size(off_type size) {
valid_fields_ |= size_valid;
size_ = size;
}
file_stat::blksize_type file_stat::blksize() const {
ensure_valid(blksize_valid);
return blksize_;
}
void file_stat::set_blksize(blksize_type blksize) {
valid_fields_ |= blksize_valid;
blksize_ = blksize;
}
file_stat::blkcnt_type file_stat::blocks() const {
ensure_valid(blocks_valid);
return blocks_;
}
void file_stat::set_blocks(blkcnt_type blocks) {
valid_fields_ |= blocks_valid;
blocks_ = blocks;
}
file_stat::time_type file_stat::atime() const {
ensure_valid(atime_valid);
return atime_;
}
void file_stat::set_atime(time_type atime) {
valid_fields_ |= atime_valid;
atime_ = atime;
}
file_stat::time_type file_stat::mtime() const {
ensure_valid(mtime_valid);
return mtime_;
}
void file_stat::set_mtime(time_type mtime) {
valid_fields_ |= mtime_valid;
mtime_ = mtime;
}
file_stat::time_type file_stat::ctime() const {
ensure_valid(ctime_valid);
return ctime_;
}
void file_stat::set_ctime(time_type ctime) {
valid_fields_ |= ctime_valid;
ctime_ = ctime;
}
} // namespace dwarfs