/* 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