add fs APIs for creating/removing/renaming, mtime and globs
parent
64a529254c
commit
307675731c
|
@ -6,15 +6,15 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <ostd/io.hh>
|
#include <ostd/io.hh>
|
||||||
#include <ostd/filesystem.hh>
|
#include <ostd/path.hh>
|
||||||
|
|
||||||
using namespace ostd;
|
using namespace ostd;
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
writeln("-- all example sources (examples/*.cc) --\n");
|
writeln("-- all example sources (examples/*.cc) --\n");
|
||||||
{
|
{
|
||||||
auto app = appender<std::vector<filesystem::path>>();
|
auto app = appender<std::vector<path>>();
|
||||||
glob_match(app, "examples/*.cc");
|
fs::glob_match(app, "examples/*.cc");
|
||||||
|
|
||||||
for (auto &ex: app.get()) {
|
for (auto &ex: app.get()) {
|
||||||
writefln("found: %s", ex);
|
writefln("found: %s", ex);
|
||||||
|
@ -22,8 +22,8 @@ int main() {
|
||||||
}
|
}
|
||||||
writeln("\n-- recursive source files (src/**/*.cc) --\n");
|
writeln("\n-- recursive source files (src/**/*.cc) --\n");
|
||||||
{
|
{
|
||||||
auto app = appender<std::vector<filesystem::path>>();
|
auto app = appender<std::vector<path>>();
|
||||||
glob_match(app, "src/**/*.cc");
|
fs::glob_match(app, "src/**/*.cc");
|
||||||
|
|
||||||
for (auto &ex: app.get()) {
|
for (auto &ex: app.get()) {
|
||||||
writefln("found: %s", ex);
|
writefln("found: %s", ex);
|
||||||
|
@ -31,8 +31,8 @@ int main() {
|
||||||
}
|
}
|
||||||
writeln("\n-- 5-character headers (ostd/?????.hh) --\n");
|
writeln("\n-- 5-character headers (ostd/?????.hh) --\n");
|
||||||
{
|
{
|
||||||
auto app = appender<std::vector<filesystem::path>>();
|
auto app = appender<std::vector<path>>();
|
||||||
glob_match(app, "ostd/?????.hh");
|
fs::glob_match(app, "ostd/?????.hh");
|
||||||
|
|
||||||
for (auto &ex: app.get()) {
|
for (auto &ex: app.get()) {
|
||||||
writefln("found: %s", ex);
|
writefln("found: %s", ex);
|
||||||
|
@ -40,8 +40,8 @@ int main() {
|
||||||
}
|
}
|
||||||
writeln("\n-- examples starting with f-r (examples/[f-r]*.cc) --\n");
|
writeln("\n-- examples starting with f-r (examples/[f-r]*.cc) --\n");
|
||||||
{
|
{
|
||||||
auto app = appender<std::vector<filesystem::path>>();
|
auto app = appender<std::vector<path>>();
|
||||||
glob_match(app, "examples/[f-r]*.cc");
|
fs::glob_match(app, "examples/[f-r]*.cc");
|
||||||
|
|
||||||
for (auto &ex: app.get()) {
|
for (auto &ex: app.get()) {
|
||||||
writefln("found: %s", ex);
|
writefln("found: %s", ex);
|
||||||
|
@ -49,8 +49,8 @@ int main() {
|
||||||
}
|
}
|
||||||
writeln("\n-- examples not starting with f-r (examples/[!f-r]*.cc) --\n");
|
writeln("\n-- examples not starting with f-r (examples/[!f-r]*.cc) --\n");
|
||||||
{
|
{
|
||||||
auto app = appender<std::vector<filesystem::path>>();
|
auto app = appender<std::vector<path>>();
|
||||||
glob_match(app, "examples/[!f-r]*.cc");
|
fs::glob_match(app, "examples/[!f-r]*.cc");
|
||||||
|
|
||||||
for (auto &ex: app.get()) {
|
for (auto &ex: app.get()) {
|
||||||
writefln("found: %s", ex);
|
writefln("found: %s", ex);
|
||||||
|
@ -58,8 +58,8 @@ int main() {
|
||||||
}
|
}
|
||||||
writeln("\n-- headers starting with c, f or s (ostd/[cfs]*.hh) --\n");
|
writeln("\n-- headers starting with c, f or s (ostd/[cfs]*.hh) --\n");
|
||||||
{
|
{
|
||||||
auto app = appender<std::vector<filesystem::path>>();
|
auto app = appender<std::vector<path>>();
|
||||||
glob_match(app, "ostd/[cfs]*.hh");
|
fs::glob_match(app, "ostd/[cfs]*.hh");
|
||||||
|
|
||||||
for (auto &ex: app.get()) {
|
for (auto &ex: app.get()) {
|
||||||
writefln("found: %s", ex);
|
writefln("found: %s", ex);
|
||||||
|
|
99
ostd/path.hh
99
ostd/path.hh
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <chrono>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
@ -50,6 +51,10 @@ namespace ostd {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct path_range;
|
struct path_range;
|
||||||
struct path_parent_range;
|
struct path_parent_range;
|
||||||
|
|
||||||
|
OSTD_EXPORT bool glob_match_path_impl(
|
||||||
|
char const *fname, char const *wname
|
||||||
|
) noexcept;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct path {
|
struct path {
|
||||||
|
@ -357,6 +362,43 @@ struct path {
|
||||||
return append_concat(p);
|
return append_concat(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Checks if the given path matches the given glob pattern.
|
||||||
|
*
|
||||||
|
* This matches the given filename against POSIX-style glob patterns.
|
||||||
|
* The following patterns are supported:
|
||||||
|
*
|
||||||
|
* | Pattern | Description |
|
||||||
|
* |---------|----------------------------------------------------|
|
||||||
|
* | * | 0 or more characters |
|
||||||
|
* | ? | any single character |
|
||||||
|
* | [abc] | one character in the brackets |
|
||||||
|
* | [a-z] | one character within the range in the brackets |
|
||||||
|
* | [!abc] | one character not in the brackets |
|
||||||
|
* | [!a-z] | one character not within the range in the brackets |
|
||||||
|
*
|
||||||
|
* The behavior is the same as in POSIX. You can combine ranges and
|
||||||
|
* individual characters in the `[]` pattern together as well as define
|
||||||
|
* multiple ranges in one (e.g. `[a-zA-Z_?]` matching alphabetics,
|
||||||
|
* an underscore and a question mark). The behavior of the range varies
|
||||||
|
* by locale. If the second character in the range is lower in value
|
||||||
|
* than the first one, a match will never happen. To match the `]`
|
||||||
|
* character in the brackets, make it the first one. To match the
|
||||||
|
* dash character, make it the first or the last.
|
||||||
|
*
|
||||||
|
* You can also use the brackets to escape metacharacters. So to
|
||||||
|
* match a literal `*`, use `[*]`.
|
||||||
|
*
|
||||||
|
* Keep in mind that an invalid bracket syntax (unterminated) will
|
||||||
|
* always cause this to return `false`.
|
||||||
|
*
|
||||||
|
* This function is used in ostd::glob_match().
|
||||||
|
*/
|
||||||
|
bool match(path const &pattern) noexcept {
|
||||||
|
return detail::glob_match_path_impl(
|
||||||
|
string().data(), pattern.string().data()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
string_range string() const noexcept {
|
string_range string() const noexcept {
|
||||||
return p_path;
|
return p_path;
|
||||||
}
|
}
|
||||||
|
@ -1076,6 +1118,8 @@ OSTD_EXPORT path current_path();
|
||||||
OSTD_EXPORT path home_path();
|
OSTD_EXPORT path home_path();
|
||||||
OSTD_EXPORT path temp_path();
|
OSTD_EXPORT path temp_path();
|
||||||
|
|
||||||
|
OSTD_EXPORT void current_path(path const &p);
|
||||||
|
|
||||||
OSTD_EXPORT path absolute(path const &p);
|
OSTD_EXPORT path absolute(path const &p);
|
||||||
|
|
||||||
OSTD_EXPORT path canonical(path const &p);
|
OSTD_EXPORT path canonical(path const &p);
|
||||||
|
@ -1091,6 +1135,61 @@ OSTD_EXPORT bool exists(path const &p);
|
||||||
|
|
||||||
OSTD_EXPORT bool equivalent(path const &p1, path const &p2);
|
OSTD_EXPORT bool equivalent(path const &p1, path const &p2);
|
||||||
|
|
||||||
|
OSTD_EXPORT bool create_directory(path const &p);
|
||||||
|
OSTD_EXPORT bool create_directory(path const &p, path const &ep);
|
||||||
|
OSTD_EXPORT bool create_directories(path const &p);
|
||||||
|
|
||||||
|
OSTD_EXPORT bool remove(path const &p);
|
||||||
|
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<std::chrono::system_clock>;
|
||||||
|
|
||||||
|
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 *),
|
||||||
|
typename path::range r, path pre, void *data
|
||||||
|
);
|
||||||
|
} /* namespace detail */
|
||||||
|
|
||||||
|
/** @brief Expands a path with glob patterns.
|
||||||
|
*
|
||||||
|
* Individual expanded paths are put in `out` and are of the standard
|
||||||
|
* std::filesystem::path type. It supports standard patterns as defined
|
||||||
|
* in ostd::glob_match_filename().
|
||||||
|
*
|
||||||
|
* So for example, `*.cc` will expand to `one.cc`, `two.cc` and so on.
|
||||||
|
* A pattern like `foo/[cb]at.txt` will match `foo/cat.txt` and `foo/bat.txt`
|
||||||
|
* but not `foo/Cat.txt`. The `foo/?at.txt` will match `foo/cat.txt`,
|
||||||
|
* `foo/Cat.txt`, `foo/pat.txt`, `foo/vat.txt` or any other character
|
||||||
|
* in the place.
|
||||||
|
*
|
||||||
|
* Additionally, a special `**` pattern is also supported which is not
|
||||||
|
* matched by ostd::glob_match_filename(). It's only allowed if the entire
|
||||||
|
* filename or directory name is `**`. When used as a directory name, it
|
||||||
|
* will expand to all directories in the location and all subdirectories
|
||||||
|
* of those directories. If used as a filename (at the end of the path),
|
||||||
|
* then it expands to directories and subdirectories aswell as all files
|
||||||
|
* in the location and in the directories or subdirectories. Keep in mind
|
||||||
|
* that it is not a regular pattern and a `**` when found in a regular
|
||||||
|
* context (i.e. not as entire filename/directory name) will be treated
|
||||||
|
* as two regular `*` patterns.
|
||||||
|
*
|
||||||
|
* @throws std::filesystem_error if a filesystem error occurs.
|
||||||
|
* @returns The forwarded `out`.
|
||||||
|
*/
|
||||||
|
template<typename OutputRange>
|
||||||
|
inline OutputRange &&glob_match(OutputRange &&out, path const &pattern) {
|
||||||
|
detail::glob_match_impl([](path const &p, void *outp) {
|
||||||
|
static_cast<std::remove_reference_t<OutputRange> *>(outp)->put(p);
|
||||||
|
}, pattern.iter(), path{}, &out);
|
||||||
|
return std::forward<OutputRange>(out);
|
||||||
|
}
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
} /* namesapce fs */
|
} /* namesapce fs */
|
||||||
|
|
179
src/path.cc
179
src/path.cc
|
@ -3,6 +3,8 @@
|
||||||
* This file is part of libostd. See COPYING.md for futher information.
|
* This file is part of libostd. See COPYING.md for futher information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "ostd/platform.hh"
|
#include "ostd/platform.hh"
|
||||||
|
|
||||||
#if defined(OSTD_PLATFORM_WIN32)
|
#if defined(OSTD_PLATFORM_WIN32)
|
||||||
|
@ -12,3 +14,180 @@
|
||||||
#else
|
#else
|
||||||
# error "Unsupported platform"
|
# error "Unsupported platform"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace ostd {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline char const *glob_match_brackets(char match, char const *wp) noexcept {
|
||||||
|
bool neg = (*wp == '!');
|
||||||
|
if (neg) {
|
||||||
|
++wp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* grab the first character as it can be ] */
|
||||||
|
auto c = *wp++;
|
||||||
|
if (!c) {
|
||||||
|
/* unterminated */
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make sure it's terminated somewhere */
|
||||||
|
auto *eb = wp;
|
||||||
|
for (; *eb != ']'; ++eb) {
|
||||||
|
if (!*eb) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++eb;
|
||||||
|
|
||||||
|
/* no need to worry about \0 from now on */
|
||||||
|
do {
|
||||||
|
/* character range */
|
||||||
|
if ((*wp == '-') && (*(wp + 1) != ']')) {
|
||||||
|
auto lc = *(wp + 1);
|
||||||
|
wp += 2;
|
||||||
|
if ((match >= c) && (match <= lc)) {
|
||||||
|
return neg ? nullptr : eb;
|
||||||
|
}
|
||||||
|
c = *wp++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* single-char match */
|
||||||
|
if (match == c) {
|
||||||
|
return neg ? nullptr : eb;
|
||||||
|
}
|
||||||
|
c = *wp++;
|
||||||
|
} while (c != ']');
|
||||||
|
|
||||||
|
/* loop ended, so no match */
|
||||||
|
return neg ? eb : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT bool glob_match_path_impl(
|
||||||
|
char const *fname, char const *wname
|
||||||
|
) noexcept {
|
||||||
|
/* skip any matching prefix if present */
|
||||||
|
while (*wname && (*wname != '*')) {
|
||||||
|
if (!*wname || (*wname == '*')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*fname) {
|
||||||
|
/* ? wildcard matches any character */
|
||||||
|
if (*wname == '?') {
|
||||||
|
++wname;
|
||||||
|
++fname;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* [...] wildcard */
|
||||||
|
if (*wname == '[') {
|
||||||
|
wname = glob_match_brackets(*fname, wname + 1);
|
||||||
|
if (!wname) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++fname;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((*wname == '?') && *fname) {
|
||||||
|
++wname;
|
||||||
|
++fname;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*fname++ != *wname++) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* skip * wildcards; a wildcard matches 0 or more */
|
||||||
|
if (*wname == '*') {
|
||||||
|
while (*wname == '*') {
|
||||||
|
++wname;
|
||||||
|
}
|
||||||
|
/* was trailing so everything matches */
|
||||||
|
if (!*wname) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* prefix skipping matched entire filename */
|
||||||
|
if (!*fname) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/* empty pattern and non-empty filename */
|
||||||
|
if (!*wname) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* keep incrementing filename until it matches */
|
||||||
|
while (*fname) {
|
||||||
|
if (glob_match_path_impl(fname, wname)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
++fname;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace detail */
|
||||||
|
} /* namespace ostd */
|
||||||
|
|
||||||
|
namespace ostd {
|
||||||
|
namespace fs {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
OSTD_EXPORT void glob_match_impl(
|
||||||
|
void (*out)(path const &, void *), typename path::range r,
|
||||||
|
path pre, void *data
|
||||||
|
) {
|
||||||
|
while (!r.empty()) {
|
||||||
|
auto cur = std::string{r.front()};
|
||||||
|
auto *cs = cur.c_str();
|
||||||
|
/* this part of the path might contain wildcards */
|
||||||
|
for (auto c = *cs; c; c = *++cs) {
|
||||||
|
/* ** as a name does recursive expansion */
|
||||||
|
if ((c == '*') && (*(cs + 1) == '*') && !*(cs + 2)) {
|
||||||
|
r.pop_front();
|
||||||
|
auto ip = pre.empty() ? "." : pre;
|
||||||
|
if (!is_directory(ip)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recursive_directory_range dr{ip};
|
||||||
|
/* include "current" dir in the match */
|
||||||
|
if (!r.empty()) {
|
||||||
|
glob_match_impl(out, r, pre, data);
|
||||||
|
}
|
||||||
|
for (auto &de: dr) {
|
||||||
|
/* followed by more path, only consider dirs */
|
||||||
|
auto dp = de.path();
|
||||||
|
if (!r.empty() && !is_directory(dp)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* otherwise also match files */
|
||||||
|
glob_match_impl(out, r, dp, data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* wildcards *, ?, [...] */
|
||||||
|
if ((c == '*') || (c == '?') || (c == '[')) {
|
||||||
|
r.pop_front();
|
||||||
|
auto ip = pre.empty() ? "." : pre;
|
||||||
|
if (!is_directory(ip)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
directory_range dr{ip};
|
||||||
|
for (auto &de: dr) {
|
||||||
|
auto p = path{de.path().name()};
|
||||||
|
if (!p.match(cur)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
glob_match_impl(out, r, pre / p, data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pre /= cur;
|
||||||
|
r.pop_front();
|
||||||
|
}
|
||||||
|
out(pre, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace detail */
|
||||||
|
} /* namesapce fs */
|
||||||
|
} /* namespace ostd */
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <ctime>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -17,38 +18,6 @@
|
||||||
namespace ostd {
|
namespace ostd {
|
||||||
namespace fs {
|
namespace fs {
|
||||||
|
|
||||||
static perms mode_to_perms(mode_t mode) {
|
|
||||||
perms ret = perms::none;
|
|
||||||
switch (mode & S_IRWXU) {
|
|
||||||
case S_IRUSR: ret |= perms::owner_read;
|
|
||||||
case S_IWUSR: ret |= perms::owner_write;
|
|
||||||
case S_IXUSR: ret |= perms::owner_exec;
|
|
||||||
case S_IRWXU: ret |= perms::owner_all;
|
|
||||||
}
|
|
||||||
switch (mode & S_IRWXG) {
|
|
||||||
case S_IRGRP: ret |= perms::group_read;
|
|
||||||
case S_IWGRP: ret |= perms::group_write;
|
|
||||||
case S_IXGRP: ret |= perms::group_exec;
|
|
||||||
case S_IRWXG: ret |= perms::group_all;
|
|
||||||
}
|
|
||||||
switch (mode & S_IRWXO) {
|
|
||||||
case S_IROTH: ret |= perms::others_read;
|
|
||||||
case S_IWOTH: ret |= perms::others_write;
|
|
||||||
case S_IXOTH: ret |= perms::others_exec;
|
|
||||||
case S_IRWXO: ret |= perms::others_all;
|
|
||||||
}
|
|
||||||
if (mode & S_ISUID) {
|
|
||||||
ret |= perms::set_uid;
|
|
||||||
}
|
|
||||||
if (mode & S_ISGID) {
|
|
||||||
ret |= perms::set_gid;
|
|
||||||
}
|
|
||||||
if (mode & S_ISVTX) {
|
|
||||||
ret |= perms::sticky_bit;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static file_type mode_to_type(mode_t mode) {
|
static file_type mode_to_type(mode_t mode) {
|
||||||
switch (mode & S_IFMT) {
|
switch (mode & S_IFMT) {
|
||||||
case S_IFBLK: return file_type::block;
|
case S_IFBLK: return file_type::block;
|
||||||
|
@ -71,7 +40,7 @@ OSTD_EXPORT file_mode mode(path const &p) {
|
||||||
/* FIXME: throw */
|
/* FIXME: throw */
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
return file_mode{mode_to_type(sb.st_mode), mode_to_perms(sb.st_mode)};
|
return file_mode{mode_to_type(sb.st_mode), perms(sb.st_mode & 07777)};
|
||||||
}
|
}
|
||||||
|
|
||||||
OSTD_EXPORT file_mode symlink_mode(path const &p) {
|
OSTD_EXPORT file_mode symlink_mode(path const &p) {
|
||||||
|
@ -83,7 +52,7 @@ OSTD_EXPORT file_mode symlink_mode(path const &p) {
|
||||||
/* FIXME: throw */
|
/* FIXME: throw */
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
return file_mode{mode_to_type(sb.st_mode), mode_to_perms(sb.st_mode)};
|
return file_mode{mode_to_type(sb.st_mode), perms(sb.st_mode & 07777)};
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace fs */
|
} /* namespace fs */
|
||||||
|
@ -268,6 +237,12 @@ OSTD_EXPORT path temp_path() {
|
||||||
return path{"/tmp"};
|
return path{"/tmp"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT void current_path(path const &p) {
|
||||||
|
if (chdir(p.string().data())) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OSTD_EXPORT path absolute(path const &p) {
|
OSTD_EXPORT path absolute(path const &p) {
|
||||||
if (p.is_absolute()) {
|
if (p.is_absolute()) {
|
||||||
return p;
|
return p;
|
||||||
|
@ -338,5 +313,129 @@ OSTD_EXPORT bool equivalent(path const &p1, path const &p2) {
|
||||||
return ((sb.st_dev == stdev) && (sb.st_ino == stino));
|
return ((sb.st_dev == stdev) && (sb.st_ino == stino));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool mkdir_p(path const &p, mode_t mode) {
|
||||||
|
if (mkdir(p.string().data(), mode)) {
|
||||||
|
if (errno != EEXIST) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
auto tp = fs::mode(p);
|
||||||
|
if (tp.type() != file_type::directory) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT bool create_directory(path const &p) {
|
||||||
|
return mkdir_p(p, 0777);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT bool create_directory(path const &p, path const &ep) {
|
||||||
|
return mkdir_p(p, mode_t(fs::mode(ep).permissions()));
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT bool create_directories(path const &p) {
|
||||||
|
if (p.has_parent()) {
|
||||||
|
create_directories(p.parent());
|
||||||
|
}
|
||||||
|
return create_directory(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT bool remove(path const &p) {
|
||||||
|
if (!exists(p)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (::remove(p.string().data())) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT std::uintmax_t remove_all(path const &p) {
|
||||||
|
std::uintmax_t ret = 0;
|
||||||
|
if (is_directory(p)) {
|
||||||
|
fs::directory_range ds{p};
|
||||||
|
for (auto &v: ds) {
|
||||||
|
ret += remove_all(v.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret += remove(p);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSTD_EXPORT void rename(path const &op, path const &np) {
|
||||||
|
if (::rename(op.string().data(), np.string().data())) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ugly test for whether nanosecond precision is available in stat
|
||||||
|
* could check for existence of st_mtime macro, but this is more reliable
|
||||||
|
*/
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct test_mtim {
|
||||||
|
template<typename TT, TT> struct test_stat;
|
||||||
|
|
||||||
|
struct fake_stat {
|
||||||
|
struct timespec st_mtim;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stat_test: fake_stat, T {};
|
||||||
|
|
||||||
|
template<typename TT>
|
||||||
|
static char test(test_stat<struct timespec fake_stat::*, &TT::st_mtim> *);
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
static int test(...);
|
||||||
|
|
||||||
|
static constexpr bool value = (sizeof(test<stat_test>(0)) == sizeof(int));
|
||||||
|
};
|
||||||
|
|
||||||
|
template<bool B>
|
||||||
|
struct mtime_impl {
|
||||||
|
template<typename S>
|
||||||
|
static file_time_t get(S const &st) {
|
||||||
|
return std::chrono::system_clock::from_time_t(st.st_mtime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct mtime_impl<true> {
|
||||||
|
template<typename S>
|
||||||
|
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<std::chrono::system_clock::duration>(d)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using mtime = mtime_impl<test_mtim<struct stat>::value>;
|
||||||
|
|
||||||
|
OSTD_EXPORT file_time_t last_write_time(path const &p) {
|
||||||
|
struct stat sb;
|
||||||
|
if (stat(p.string().data(), &sb) < 0) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
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<std::chrono::seconds>(d);
|
||||||
|
auto nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(d - sec);
|
||||||
|
struct timespec times[2] = {
|
||||||
|
{0, UTIME_OMIT}, {time_t(sec.count()), long(nsec.count())}
|
||||||
|
};
|
||||||
|
if (utimensat(0, p.string().data(), times, 0)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace fs */
|
} /* namespace fs */
|
||||||
} /* namespace ostd */
|
} /* namespace ostd */
|
||||||
|
|
Loading…
Reference in New Issue