add fs APIs for creating/removing/renaming, mtime and globs

master
Daniel Kolesa 2018-04-18 03:40:03 +02:00
parent 64a529254c
commit 307675731c
4 changed files with 424 additions and 47 deletions

View File

@ -6,15 +6,15 @@
#include <vector>
#include <ostd/io.hh>
#include <ostd/filesystem.hh>
#include <ostd/path.hh>
using namespace ostd;
int main() {
writeln("-- all example sources (examples/*.cc) --\n");
{
auto app = appender<std::vector<filesystem::path>>();
glob_match(app, "examples/*.cc");
auto app = appender<std::vector<path>>();
fs::glob_match(app, "examples/*.cc");
for (auto &ex: app.get()) {
writefln("found: %s", ex);
@ -22,8 +22,8 @@ int main() {
}
writeln("\n-- recursive source files (src/**/*.cc) --\n");
{
auto app = appender<std::vector<filesystem::path>>();
glob_match(app, "src/**/*.cc");
auto app = appender<std::vector<path>>();
fs::glob_match(app, "src/**/*.cc");
for (auto &ex: app.get()) {
writefln("found: %s", ex);
@ -31,8 +31,8 @@ int main() {
}
writeln("\n-- 5-character headers (ostd/?????.hh) --\n");
{
auto app = appender<std::vector<filesystem::path>>();
glob_match(app, "ostd/?????.hh");
auto app = appender<std::vector<path>>();
fs::glob_match(app, "ostd/?????.hh");
for (auto &ex: app.get()) {
writefln("found: %s", ex);
@ -40,8 +40,8 @@ int main() {
}
writeln("\n-- examples starting with f-r (examples/[f-r]*.cc) --\n");
{
auto app = appender<std::vector<filesystem::path>>();
glob_match(app, "examples/[f-r]*.cc");
auto app = appender<std::vector<path>>();
fs::glob_match(app, "examples/[f-r]*.cc");
for (auto &ex: app.get()) {
writefln("found: %s", ex);
@ -49,8 +49,8 @@ int main() {
}
writeln("\n-- examples not starting with f-r (examples/[!f-r]*.cc) --\n");
{
auto app = appender<std::vector<filesystem::path>>();
glob_match(app, "examples/[!f-r]*.cc");
auto app = appender<std::vector<path>>();
fs::glob_match(app, "examples/[!f-r]*.cc");
for (auto &ex: app.get()) {
writefln("found: %s", ex);
@ -58,8 +58,8 @@ int main() {
}
writeln("\n-- headers starting with c, f or s (ostd/[cfs]*.hh) --\n");
{
auto app = appender<std::vector<filesystem::path>>();
glob_match(app, "ostd/[cfs]*.hh");
auto app = appender<std::vector<path>>();
fs::glob_match(app, "ostd/[cfs]*.hh");
for (auto &ex: app.get()) {
writefln("found: %s", ex);

View File

@ -25,6 +25,7 @@
#include <stack>
#include <list>
#include <chrono>
#include <utility>
#include <memory>
#include <initializer_list>
@ -50,6 +51,10 @@ namespace ostd {
namespace detail {
struct path_range;
struct path_parent_range;
OSTD_EXPORT bool glob_match_path_impl(
char const *fname, char const *wname
) noexcept;
}
struct path {
@ -357,6 +362,43 @@ struct path {
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 {
return p_path;
}
@ -1076,6 +1118,8 @@ OSTD_EXPORT path current_path();
OSTD_EXPORT path home_path();
OSTD_EXPORT path temp_path();
OSTD_EXPORT void current_path(path const &p);
OSTD_EXPORT path absolute(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 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 */

View File

@ -3,6 +3,8 @@
* This file is part of libostd. See COPYING.md for futher information.
*/
#include <utility>
#include "ostd/platform.hh"
#if defined(OSTD_PLATFORM_WIN32)
@ -12,3 +14,180 @@
#else
# error "Unsupported platform"
#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 */

View File

@ -4,6 +4,7 @@
*/
#include <cstdlib>
#include <ctime>
#include <pwd.h>
#include <dirent.h>
#include <unistd.h>
@ -17,38 +18,6 @@
namespace ostd {
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) {
switch (mode & S_IFMT) {
case S_IFBLK: return file_type::block;
@ -71,7 +40,7 @@ OSTD_EXPORT file_mode mode(path const &p) {
/* FIXME: throw */
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) {
@ -83,7 +52,7 @@ OSTD_EXPORT file_mode symlink_mode(path const &p) {
/* FIXME: throw */
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 */
@ -268,6 +237,12 @@ OSTD_EXPORT path temp_path() {
return path{"/tmp"};
}
OSTD_EXPORT void current_path(path const &p) {
if (chdir(p.string().data())) {
abort();
}
}
OSTD_EXPORT path absolute(path const &p) {
if (p.is_absolute()) {
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));
}
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 ostd */