diff --git a/ostd/path.hh b/ostd/path.hh index e105108..bf7c7b8 100644 --- a/ostd/path.hh +++ b/ostd/path.hh @@ -1224,6 +1224,9 @@ namespace fs { * @{ */ +/** @brief The time point used to represent file access times. */ +using file_time_t = std::chrono::time_point; + /** @brief An exception thrown by filesystem operations. * * Unlike ostd::path_error, this is thrown by operations doing syscalls. @@ -1361,22 +1364,54 @@ public: return file_type(p_val >> 16); } - void type(file_type type) noexcept { - p_val = ((p_val & 0xFFFF) | (UT(type) << 16)); - } - perms permissions() const noexcept { return perms(p_val & 0xFFFF); } - - void permissions(perms perm) noexcept { - p_val = ((p_val & ~UT(0xFFFF)) | UT(perm)); - } }; +struct file_status { + file_status( + file_mode mode, file_time_t mtime, + std::uintmax_t size, std::uintmax_t nlinks + ): + p_links(nlinks), p_size(size), p_mtime(mtime), p_mode(mode) + {} + + file_mode mode() const noexcept { + return p_mode; + } + + file_time_t last_write_time() const noexcept { + return p_mtime; + } + + std::uintmax_t hard_link_count() const noexcept { + return p_links; + } + + std::uintmax_t size() const noexcept { + return p_size; + } + +private: + std::uintmax_t p_links; + std::uintmax_t p_size; + file_time_t p_mtime; + file_mode p_mode; +}; + +OSTD_EXPORT file_status status(path const &p); +OSTD_EXPORT file_status symlink_status(path const &p); + OSTD_EXPORT file_mode mode(path const &p); OSTD_EXPORT file_mode symlink_mode(path const &p); +OSTD_EXPORT std::uintmax_t file_size(path const &p); +OSTD_EXPORT std::uintmax_t hard_link_count(path const &p); + +OSTD_EXPORT file_time_t last_write_time(path const &p); +OSTD_EXPORT void last_write_time(path const &p, file_time_t new_time); + inline bool is_block_file(file_mode st) noexcept { return st.type() == file_type::block; } @@ -1449,34 +1484,6 @@ inline bool mode_known(path const &p) { return mode_known(mode(p)); } -struct file_status { - file_status() {} - file_status(ostd::path const &p): p_path(p) {} - - ostd::path const &path() const noexcept { - return p_path; - } - - operator ostd::path const &() const noexcept { - return p_path; - } - - void clear() { - p_path.clear(); - } - - void assign(ostd::path const &p) { - p_path = p; - } - - void assign(ostd::path &&p) { - p_path = std::move(p); - } - -private: - ostd::path p_path{}; -}; - namespace detail { struct dir_range_impl; struct rdir_range_impl; @@ -1684,11 +1691,6 @@ OSTD_EXPORT std::uintmax_t remove_all(path const &p); OSTD_EXPORT void rename(path const &op, path const &np); -using file_time_t = std::chrono::time_point; - -OSTD_EXPORT file_time_t last_write_time(path const &p); -OSTD_EXPORT void last_write_time(path const &p, file_time_t new_time); - namespace detail { OSTD_EXPORT void glob_match_impl( void (*out)(path const &, void *), diff --git a/src/posix/path.cc b/src/posix/path.cc index 691832e..cf637c6 100644 --- a/src/posix/path.cc +++ b/src/posix/path.cc @@ -9,6 +9,9 @@ #undef _POSIX_C_SOURCE #endif +/* only because glibc is awful, does not apply to other libcs */ +#define _FILE_OFFSET_BITS 64 + #define _POSIX_C_SOURCE 200809L #ifndef _ATFILE_SOURCE @@ -51,26 +54,111 @@ static std::error_code ec_from_int(int v) { return std::error_code(v, std::system_category()); } -OSTD_EXPORT file_mode mode(path const &p) { - struct stat sb; - if (stat(p.string().data(), &sb) < 0) { +/* ugly test for whether nanosecond precision is available in stat + * could check for existence of st_mtime macro, but this is more reliable + */ + +template +struct test_mtim { + template struct test_stat; + + struct fake_stat { + struct timespec st_mtim; + }; + + struct stat_test: fake_stat, T {}; + + template + static char test(test_stat *); + + template + static int test(...); + + static constexpr bool value = (sizeof(test(0)) == sizeof(int)); +}; + +template +struct mtime_impl { + template + static file_time_t get(S const &st) { + return std::chrono::system_clock::from_time_t(st.st_mtime); + } +}; + +template<> +struct mtime_impl { + template + static file_time_t get(S const &st) { + struct timespec ts = st.st_mtim; + auto d = std::chrono::seconds{ts.tv_sec} + + std::chrono::nanoseconds{ts.tv_nsec}; + return file_time_t{ + std::chrono::duration_cast(d) + }; + } +}; + +using mtime = mtime_impl::value>; + +static file_status status_get(path const &p, int ret, struct stat &sb) { + if (ret < 0) { if (errno == ENOENT) { - return file_mode{file_type::not_found, perms::none}; + return file_status{ + file_mode{file_type::not_found, perms::none}, + file_time_t{}, 0, 0 + }; } throw fs_error{"stat failure", p, errno_ec()}; } - return file_mode{mode_to_type(sb.st_mode), perms(sb.st_mode & 07777)}; + return file_status{ + file_mode{mode_to_type(sb.st_mode), perms{sb.st_mode & 07777}}, + mtime::get(sb), + std::uintmax_t(sb.st_size), + std::uintmax_t(sb.st_nlink) + }; +} + +OSTD_EXPORT file_status status(path const &p) { + struct stat sb; + return status_get(p, stat(p.string().data(), &sb), sb); +} + +OSTD_EXPORT file_status symlink_status(path const &p) { + struct stat sb; + return status_get(p, lstat(p.string().data(), &sb), sb); +} + +OSTD_EXPORT file_mode mode(path const &p) { + return status(p).mode(); } OSTD_EXPORT file_mode symlink_mode(path const &p) { - struct stat sb; - if (lstat(p.string().data(), &sb) < 0) { - if (errno == ENOENT) { - return file_mode{file_type::not_found, perms::none}; - } - throw fs_error{"lstat failure", p, errno_ec()}; + return symlink_status(p).mode(); +} + +OSTD_EXPORT file_time_t last_write_time(path const &p) { + return status(p).last_write_time(); +} + +OSTD_EXPORT std::uintmax_t file_size(path const &p) { + return status(p).size(); +} + +OSTD_EXPORT std::uintmax_t hard_link_count(path const &p) { + return status(p).hard_link_count(); +} + +/* TODO: somehow feature-test for utimensat and fallback to utimes */ +OSTD_EXPORT void last_write_time(path const &p, file_time_t new_time) { + auto d = new_time.time_since_epoch(); + auto sec = std::chrono::floor(d); + auto nsec = std::chrono::duration_cast(d - sec); + struct timespec times[2] = { + {0, UTIME_OMIT}, {time_t(sec.count()), long(nsec.count())} + }; + if (utimensat(0, p.string().data(), times, 0)) { + throw fs_error{"utimensat failure", p, errno_ec()}; } - return file_mode{mode_to_type(sb.st_mode), perms(sb.st_mode & 07777)}; } } /* namespace fs */ @@ -387,72 +475,5 @@ OSTD_EXPORT void rename(path const &op, path const &np) { } } -/* ugly test for whether nanosecond precision is available in stat - * could check for existence of st_mtime macro, but this is more reliable - */ - -template -struct test_mtim { - template struct test_stat; - - struct fake_stat { - struct timespec st_mtim; - }; - - struct stat_test: fake_stat, T {}; - - template - static char test(test_stat *); - - template - static int test(...); - - static constexpr bool value = (sizeof(test(0)) == sizeof(int)); -}; - -template -struct mtime_impl { - template - static file_time_t get(S const &st) { - return std::chrono::system_clock::from_time_t(st.st_mtime); - } -}; - -template<> -struct mtime_impl { - template - static file_time_t get(S const &st) { - struct timespec ts = st.st_mtim; - auto d = std::chrono::seconds{ts.tv_sec} + - std::chrono::nanoseconds{ts.tv_nsec}; - return file_time_t{ - std::chrono::duration_cast(d) - }; - } -}; - -using mtime = mtime_impl::value>; - -OSTD_EXPORT file_time_t last_write_time(path const &p) { - struct stat sb; - if (stat(p.string().data(), &sb) < 0) { - throw fs_error{"stat failure", p, errno_ec()}; - } - return mtime::get(sb); -} - -/* TODO: somehow feature-test for utimensat and fallback to utimes */ -OSTD_EXPORT void last_write_time(path const &p, file_time_t new_time) { - auto d = new_time.time_since_epoch(); - auto sec = std::chrono::floor(d); - auto nsec = std::chrono::duration_cast(d - sec); - struct timespec times[2] = { - {0, UTIME_OMIT}, {time_t(sec.count()), long(nsec.count())} - }; - if (utimensat(0, p.string().data(), times, 0)) { - throw fs_error{"utimensat failure", p, errno_ec()}; - } -} - } /* namespace fs */ } /* namespace ostd */