blob: 74868cd48e6dddb41185814c5b53f3867ce4095c [file] [log] [blame]
// Filesystem operations -*- C++ -*-
// Copyright (C) 2014-2018 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library 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, or (at your option)
// any later version.
// This library 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.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
#ifndef _GLIBCXX_USE_CXX11_ABI
# define _GLIBCXX_USE_CXX11_ABI 1
# define NEED_DO_COPY_FILE
#endif
#include <filesystem>
#include <experimental/filesystem>
#include <functional>
#include <ostream>
#include <stack>
#include <ext/stdio_filebuf.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h> // PATH_MAX
#ifdef _GLIBCXX_HAVE_FCNTL_H
# include <fcntl.h> // AT_FDCWD, AT_SYMLINK_NOFOLLOW
#endif
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
# include <sys/stat.h> // stat, utimensat, fchmodat
#endif
#ifdef _GLIBCXX_HAVE_SYS_STATVFS_H
# include <sys/statvfs.h> // statvfs
#endif
#ifdef _GLIBCXX_USE_SENDFILE
# include <sys/sendfile.h> // sendfile
#endif
#if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H
# include <utime.h> // utime
#endif
#define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem {
#define _GLIBCXX_END_NAMESPACE_FILESYSTEM }
#include "ops-common.h"
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
# undef utime
# define utime _wutime
# undef chmod
# define chmod _wchmod
#endif
namespace fs = std::filesystem;
fs::path
fs::absolute(const path& p)
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
error_code ec;
path ret = absolute(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot make absolute path", p,
std::make_error_code(errc::not_supported)));
return ret;
#else
return current_path() / p;
#endif
}
fs::path
fs::absolute(const path& p, error_code& ec)
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
ec = std::make_error_code(errc::not_supported);
return {};
#else
ec.clear();
return current_path() / p;
#endif
}
namespace
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
inline bool is_dot(wchar_t c) { return c == L'.'; }
#else
inline bool is_dot(char c) { return c == '.'; }
#endif
inline bool is_dot(const fs::path& path)
{
const auto& filename = path.native();
return filename.size() == 1 && is_dot(filename[0]);
}
inline bool is_dotdot(const fs::path& path)
{
const auto& filename = path.native();
return filename.size() == 2 && is_dot(filename[0]) && is_dot(filename[1]);
}
struct free_as_in_malloc
{
void operator()(void* p) const { ::free(p); }
};
using char_ptr = std::unique_ptr<char[], free_as_in_malloc>;
}
fs::path
fs::canonical(const path& p, error_code& ec)
{
path result;
const path pa = absolute(p, ec);
if (ec)
return result;
#ifdef _GLIBCXX_USE_REALPATH
char_ptr buf{ nullptr };
# if _XOPEN_VERSION < 700
// Not safe to call realpath(path, NULL)
buf.reset( (char*)::malloc(PATH_MAX) );
# endif
if (char* rp = ::realpath(pa.c_str(), buf.get()))
{
if (buf == nullptr)
buf.reset(rp);
result.assign(rp);
ec.clear();
return result;
}
if (errno != ENAMETOOLONG)
{
ec.assign(errno, std::generic_category());
return result;
}
#endif
if (!exists(pa, ec))
{
if (!ec)
ec = make_error_code(std::errc::no_such_file_or_directory);
return result;
}
// else: we know there are (currently) no unresolvable symlink loops
result = pa.root_path();
deque<path> cmpts;
for (auto& f : pa.relative_path())
cmpts.push_back(f);
int max_allowed_symlinks = 40;
while (!cmpts.empty() && !ec)
{
path f = std::move(cmpts.front());
cmpts.pop_front();
if (f.empty())
{
// ignore empty element
}
else if (is_dot(f))
{
if (!is_directory(result, ec) && !ec)
ec.assign(ENOTDIR, std::generic_category());
}
else if (is_dotdot(f))
{
auto parent = result.parent_path();
if (parent.empty())
result = pa.root_path();
else
result.swap(parent);
}
else
{
result /= f;
if (is_symlink(result, ec))
{
path link = read_symlink(result, ec);
if (!ec)
{
if (--max_allowed_symlinks == 0)
ec.assign(ELOOP, std::generic_category());
else
{
if (link.is_absolute())
{
result = link.root_path();
link = link.relative_path();
}
else
result = result.parent_path();
cmpts.insert(cmpts.begin(), link.begin(), link.end());
}
}
}
}
}
if (ec || !exists(result, ec))
result.clear();
return result;
}
fs::path
fs::canonical(const path& p)
{
error_code ec;
path res = canonical(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot make canonical path",
p, ec));
return res;
}
void
fs::copy(const path& from, const path& to, copy_options options)
{
error_code ec;
copy(from, to, options, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot copy", from, to, ec));
}
namespace std::filesystem
{
// Need this as there's no 'perm_options::none' enumerator.
inline bool is_set(fs::perm_options obj, fs::perm_options bits)
{
return (obj & bits) != fs::perm_options{};
}
}
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
#ifdef NEED_DO_COPY_FILE
bool
fs::do_copy_file(const char* from, const char* to,
copy_options_existing_file options,
stat_type* from_st, stat_type* to_st,
std::error_code& ec) noexcept
{
stat_type st1, st2;
fs::file_status t, f;
if (to_st == nullptr)
{
if (::stat(to, &st1))
{
const int err = errno;
if (!is_not_found_errno(err))
{
ec.assign(err, std::generic_category());
return false;
}
}
else
to_st = &st1;
}
else if (to_st == from_st)
to_st = nullptr;
if (to_st == nullptr)
t = fs::file_status{fs::file_type::not_found};
else
t = make_file_status(*to_st);
if (from_st == nullptr)
{
if (::stat(from, &st2))
{
ec.assign(errno, std::generic_category());
return false;
}
else
from_st = &st2;
}
f = make_file_status(*from_st);
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2712. copy_file() has a number of unspecified error conditions
if (!is_regular_file(f))
{
ec = std::make_error_code(std::errc::not_supported);
return false;
}
if (exists(t))
{
if (!is_regular_file(t))
{
ec = std::make_error_code(std::errc::not_supported);
return false;
}
if (to_st->st_dev == from_st->st_dev
&& to_st->st_ino == from_st->st_ino)
{
ec = std::make_error_code(std::errc::file_exists);
return false;
}
if (options.skip)
{
ec.clear();
return false;
}
else if (options.update)
{
const auto from_mtime = file_time(*from_st, ec);
if (ec)
return false;
if ((from_mtime <= file_time(*to_st, ec)) || ec)
return false;
}
else if (!options.overwrite)
{
ec = std::make_error_code(std::errc::file_exists);
return false;
}
else if (!is_regular_file(t))
{
ec = std::make_error_code(std::errc::not_supported);
return false;
}
}
struct CloseFD {
~CloseFD() { if (fd != -1) ::close(fd); }
bool close() { return ::close(std::exchange(fd, -1)) == 0; }
int fd;
};
CloseFD in = { ::open(from, O_RDONLY) };
if (in.fd == -1)
{
ec.assign(errno, std::generic_category());
return false;
}
int oflag = O_WRONLY|O_CREAT;
if (options.overwrite || options.update)
oflag |= O_TRUNC;
else
oflag |= O_EXCL;
CloseFD out = { ::open(to, oflag, S_IWUSR) };
if (out.fd == -1)
{
if (errno == EEXIST && options.skip)
ec.clear();
else
ec.assign(errno, std::generic_category());
return false;
}
#ifdef _GLIBCXX_USE_FCHMOD
if (::fchmod(out.fd, from_st->st_mode))
#elif defined _GLIBCXX_USE_FCHMODAT
if (::fchmodat(AT_FDCWD, to, from_st->st_mode, 0))
#else
if (::chmod(to, from_st->st_mode))
#endif
{
ec.assign(errno, std::generic_category());
return false;
}
size_t count = from_st->st_size;
#ifdef _GLIBCXX_USE_SENDFILE
off_t offset = 0;
ssize_t n = ::sendfile(out.fd, in.fd, &offset, count);
if (n < 0 && errno != ENOSYS && errno != EINVAL)
{
ec.assign(errno, std::generic_category());
return false;
}
if ((size_t)n == count)
{
if (!out.close() || !in.close())
{
ec.assign(errno, std::generic_category());
return false;
}
ec.clear();
return true;
}
else if (n > 0)
count -= n;
#endif // _GLIBCXX_USE_SENDFILE
using std::ios;
__gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
__gnu_cxx::stdio_filebuf<char> sbout(out.fd, ios::out|ios::binary);
if (sbin.is_open())
in.fd = -1;
if (sbout.is_open())
out.fd = -1;
#ifdef _GLIBCXX_USE_SENDFILE
if (n != 0)
{
if (n < 0)
n = 0;
const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
const std::streampos errpos(std::streamoff(-1));
if (p1 == errpos || p2 == errpos)
{
ec = std::make_error_code(std::errc::io_error);
return false;
}
}
#endif
if (count && !(std::ostream(&sbout) << &sbin))
{
ec = std::make_error_code(std::errc::io_error);
return false;
}
if (!sbout.close() || !sbin.close())
{
ec.assign(errno, std::generic_category());
return false;
}
ec.clear();
return true;
}
#endif // NEED_DO_COPY_FILE
#endif // _GLIBCXX_HAVE_SYS_STAT_H
void
fs::copy(const path& from, const path& to, copy_options options,
error_code& ec)
{
const bool skip_symlinks = is_set(options, copy_options::skip_symlinks);
const bool create_symlinks = is_set(options, copy_options::create_symlinks);
const bool copy_symlinks = is_set(options, copy_options::copy_symlinks);
const bool use_lstat = create_symlinks || skip_symlinks;
file_status f, t;
stat_type from_st, to_st;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2681. filesystem::copy() cannot copy symlinks
if (use_lstat || copy_symlinks
? ::lstat(from.c_str(), &from_st)
: ::stat(from.c_str(), &from_st))
{
ec.assign(errno, std::generic_category());
return;
}
if (use_lstat
? ::lstat(to.c_str(), &to_st)
: ::stat(to.c_str(), &to_st))
{
if (!is_not_found_errno(errno))
{
ec.assign(errno, std::generic_category());
return;
}
t = file_status{file_type::not_found};
}
else
t = make_file_status(to_st);
f = make_file_status(from_st);
if (exists(t) && !is_other(t) && !is_other(f)
&& to_st.st_dev == from_st.st_dev && to_st.st_ino == from_st.st_ino)
{
ec = std::make_error_code(std::errc::file_exists);
return;
}
if (is_other(f) || is_other(t))
{
ec = std::make_error_code(std::errc::not_supported);
return;
}
if (is_directory(f) && is_regular_file(t))
{
ec = std::make_error_code(std::errc::is_a_directory);
return;
}
if (is_symlink(f))
{
if (skip_symlinks)
ec.clear();
else if (!exists(t) && copy_symlinks)
copy_symlink(from, to, ec);
else
// Not clear what should be done here.
// "Otherwise report an error as specified in Error reporting (7)."
ec = std::make_error_code(std::errc::invalid_argument);
}
else if (is_regular_file(f))
{
if (is_set(options, copy_options::directories_only))
ec.clear();
else if (create_symlinks)
create_symlink(from, to, ec);
else if (is_set(options, copy_options::create_hard_links))
create_hard_link(from, to, ec);
else if (is_directory(t))
do_copy_file(from.c_str(), (to / from.filename()).c_str(),
copy_file_options(options), &from_st, nullptr, ec);
else
{
auto ptr = exists(t) ? &to_st : &from_st;
do_copy_file(from.c_str(), to.c_str(), copy_file_options(options),
&from_st, ptr, ec);
}
}
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2682. filesystem::copy() won't create a symlink to a directory
else if (is_directory(f) && create_symlinks)
ec = std::make_error_code(errc::is_a_directory);
else if (is_directory(f) && (is_set(options, copy_options::recursive)
|| options == copy_options::none))
{
if (!exists(t))
if (!create_directory(to, from, ec))
return;
// set an unused bit in options to disable further recursion
if (!is_set(options, copy_options::recursive))
options |= static_cast<copy_options>(4096);
for (const directory_entry& x : directory_iterator(from))
copy(x.path(), to/x.path().filename(), options, ec);
}
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2683. filesystem::copy() says "no effects"
else
ec.clear();
}
bool
fs::copy_file(const path& from, const path& to, copy_options option)
{
error_code ec;
bool result = copy_file(from, to, option, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot copy file", from, to,
ec));
return result;
}
bool
fs::copy_file(const path& from, const path& to, copy_options options,
error_code& ec)
{
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
return do_copy_file(from.c_str(), to.c_str(), copy_file_options(options),
nullptr, nullptr, ec);
#else
ec = std::make_error_code(std::errc::not_supported);
return false;
#endif
}
void
fs::copy_symlink(const path& existing_symlink, const path& new_symlink)
{
error_code ec;
copy_symlink(existing_symlink, new_symlink, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot copy symlink",
existing_symlink, new_symlink, ec));
}
void
fs::copy_symlink(const path& existing_symlink, const path& new_symlink,
error_code& ec) noexcept
{
auto p = read_symlink(existing_symlink, ec);
if (ec)
return;
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
if (is_directory(p))
{
create_directory_symlink(p, new_symlink, ec);
return;
}
#endif
create_symlink(p, new_symlink, ec);
}
bool
fs::create_directories(const path& p)
{
error_code ec;
bool result = create_directories(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot create directories", p,
ec));
return result;
}
bool
fs::create_directories(const path& p, error_code& ec)
{
if (p.empty())
{
ec = std::make_error_code(errc::invalid_argument);
return false;
}
std::stack<path> missing;
path pp = p;
while (pp.has_filename() && status(pp, ec).type() == file_type::not_found)
{
ec.clear();
const auto& filename = pp.filename();
if (!is_dot(filename) && !is_dotdot(filename))
missing.push(pp);
pp = pp.parent_path();
if (missing.size() > 1000) // sanity check
{
ec = std::make_error_code(std::errc::filename_too_long);
return false;
}
}
if (ec || missing.empty())
return false;
do
{
const path& top = missing.top();
create_directory(top, ec);
if (ec && is_directory(top))
ec.clear();
missing.pop();
}
while (!missing.empty() && !ec);
return missing.empty();
}
namespace
{
bool
create_dir(const fs::path& p, fs::perms perm, std::error_code& ec)
{
bool created = false;
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
::mode_t mode = static_cast<std::underlying_type_t<fs::perms>>(perm);
if (::mkdir(p.c_str(), mode))
{
const int err = errno;
if (err != EEXIST || !is_directory(p, ec))
ec.assign(err, std::generic_category());
}
else
{
ec.clear();
created = true;
}
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
return created;
}
} // namespace
bool
fs::create_directory(const path& p)
{
error_code ec;
bool result = create_directory(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot create directory", p,
ec));
return result;
}
bool
fs::create_directory(const path& p, error_code& ec) noexcept
{
return create_dir(p, perms::all, ec);
}
bool
fs::create_directory(const path& p, const path& attributes)
{
error_code ec;
bool result = create_directory(p, attributes, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot create directory", p,
ec));
return result;
}
bool
fs::create_directory(const path& p, const path& attributes,
error_code& ec) noexcept
{
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
stat_type st;
if (::stat(attributes.c_str(), &st))
{
ec.assign(errno, std::generic_category());
return false;
}
return create_dir(p, static_cast<perms>(st.st_mode), ec);
#else
ec = std::make_error_code(std::errc::not_supported);
return false;
#endif
}
void
fs::create_directory_symlink(const path& to, const path& new_symlink)
{
error_code ec;
create_directory_symlink(to, new_symlink, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot create directory symlink",
to, new_symlink, ec));
}
void
fs::create_directory_symlink(const path& to, const path& new_symlink,
error_code& ec) noexcept
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
ec = std::make_error_code(std::errc::not_supported);
#else
create_symlink(to, new_symlink, ec);
#endif
}
void
fs::create_hard_link(const path& to, const path& new_hard_link)
{
error_code ec;
create_hard_link(to, new_hard_link, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot create hard link",
to, new_hard_link, ec));
}
void
fs::create_hard_link(const path& to, const path& new_hard_link,
error_code& ec) noexcept
{
#ifdef _GLIBCXX_HAVE_UNISTD_H
if (::link(to.c_str(), new_hard_link.c_str()))
ec.assign(errno, std::generic_category());
else
ec.clear();
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
}
void
fs::create_symlink(const path& to, const path& new_symlink)
{
error_code ec;
create_symlink(to, new_symlink, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot create symlink",
to, new_symlink, ec));
}
void
fs::create_symlink(const path& to, const path& new_symlink,
error_code& ec) noexcept
{
#ifdef _GLIBCXX_HAVE_UNISTD_H
if (::symlink(to.c_str(), new_symlink.c_str()))
ec.assign(errno, std::generic_category());
else
ec.clear();
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
}
fs::path
fs::current_path()
{
error_code ec;
path p = current_path(ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot get current path", ec));
return p;
}
fs::path
fs::current_path(error_code& ec)
{
path p;
#ifdef _GLIBCXX_HAVE_UNISTD_H
#ifdef __GLIBC__
if (char_ptr cwd = char_ptr{::getcwd(nullptr, 0)})
{
p.assign(cwd.get());
ec.clear();
}
else
ec.assign(errno, std::generic_category());
#else
long path_max = pathconf(".", _PC_PATH_MAX);
size_t size;
if (path_max == -1)
size = 1024;
else if (path_max > 10240)
size = 10240;
else
size = path_max;
for (char_ptr buf; p.empty(); size *= 2)
{
buf.reset((char*)malloc(size));
if (buf)
{
if (getcwd(buf.get(), size))
{
p.assign(buf.get());
ec.clear();
}
else if (errno != ERANGE)
{
ec.assign(errno, std::generic_category());
return {};
}
}
else
{
ec = std::make_error_code(std::errc::not_enough_memory);
return {};
}
}
#endif // __GLIBC__
#else // _GLIBCXX_HAVE_UNISTD_H
ec = std::make_error_code(std::errc::not_supported);
#endif
return p;
}
void
fs::current_path(const path& p)
{
error_code ec;
current_path(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot set current path", ec));
}
void
fs::current_path(const path& p, error_code& ec) noexcept
{
#ifdef _GLIBCXX_HAVE_UNISTD_H
if (::chdir(p.c_str()))
ec.assign(errno, std::generic_category());
else
ec.clear();
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
}
bool
fs::equivalent(const path& p1, const path& p2)
{
error_code ec;
auto result = equivalent(p1, p2, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot check file equivalence",
p1, p2, ec));
return result;
}
bool
fs::equivalent(const path& p1, const path& p2, error_code& ec) noexcept
{
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
int err = 0;
file_status s1, s2;
stat_type st1, st2;
if (::stat(p1.c_str(), &st1) == 0)
s1 = make_file_status(st1);
else if (is_not_found_errno(errno))
s1.type(file_type::not_found);
else
err = errno;
if (::stat(p2.c_str(), &st2) == 0)
s2 = make_file_status(st2);
else if (is_not_found_errno(errno))
s2.type(file_type::not_found);
else
err = errno;
if (exists(s1) && exists(s2))
{
if (is_other(s1) && is_other(s2))
{
ec = std::make_error_code(std::errc::not_supported);
return false;
}
ec.clear();
if (is_other(s1) || is_other(s2))
return false;
return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
}
else if (!exists(s1) && !exists(s2))
ec = std::make_error_code(std::errc::no_such_file_or_directory);
else if (err)
ec.assign(err, std::generic_category());
else
ec.clear();
return false;
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
return false;
}
std::uintmax_t
fs::file_size(const path& p)
{
error_code ec;
auto sz = file_size(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot get file size", p, ec));
return sz;
}
namespace
{
template<typename Accessor, typename T>
inline T
do_stat(const fs::path& p, std::error_code& ec, Accessor f, T deflt)
{
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
fs::stat_type st;
if (::stat(p.c_str(), &st))
{
ec.assign(errno, std::generic_category());
return deflt;
}
ec.clear();
return f(st);
#else
ec = std::make_error_code(std::errc::not_supported);
return deflt;
#endif
}
}
std::uintmax_t
fs::file_size(const path& p, error_code& ec) noexcept
{
struct S
{
S(const stat_type& st) : type(make_file_type(st)), size(st.st_size) { }
S() : type(file_type::not_found) { }
file_type type;
size_t size;
};
auto s = do_stat(p, ec, [](const auto& st) { return S{st}; }, S{});
if (s.type == file_type::regular)
return s.size;
if (!ec)
{
if (s.type == file_type::directory)
ec = std::make_error_code(std::errc::is_a_directory);
else
ec = std::make_error_code(std::errc::not_supported);
}
return -1;
}
std::uintmax_t
fs::hard_link_count(const path& p)
{
error_code ec;
auto count = hard_link_count(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot get link count", p, ec));
return count;
}
std::uintmax_t
fs::hard_link_count(const path& p, error_code& ec) noexcept
{
return do_stat(p, ec, std::mem_fn(&stat::st_nlink),
static_cast<uintmax_t>(-1));
}
bool
fs::is_empty(const path& p)
{
error_code ec;
bool e = is_empty(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot check if file is empty",
p, ec));
return e;
}
bool
fs::is_empty(const path& p, error_code& ec)
{
auto s = status(p, ec);
if (ec)
return false;
bool empty = fs::is_directory(s)
? fs::directory_iterator(p, ec) == fs::directory_iterator()
: fs::file_size(p, ec) == 0;
return ec ? false : empty;
}
fs::file_time_type
fs::last_write_time(const path& p)
{
error_code ec;
auto t = last_write_time(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot get file time", p, ec));
return t;
}
fs::file_time_type
fs::last_write_time(const path& p, error_code& ec) noexcept
{
return do_stat(p, ec, [&ec](const auto& st) { return file_time(st, ec); },
file_time_type::min());
}
void
fs::last_write_time(const path& p, file_time_type new_time)
{
error_code ec;
last_write_time(p, new_time, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot set file time", p, ec));
}
void
fs::last_write_time(const path& p __attribute__((__unused__)),
file_time_type new_time, error_code& ec) noexcept
{
auto d = new_time.time_since_epoch();
auto s = chrono::duration_cast<chrono::seconds>(d);
#if _GLIBCXX_USE_UTIMENSAT
auto ns = chrono::duration_cast<chrono::nanoseconds>(d - s);
if (ns < ns.zero()) // tv_nsec must be non-negative and less than 10e9.
{
--s;
ns += chrono::seconds(1);
}
struct ::timespec ts[2];
ts[0].tv_sec = 0;
ts[0].tv_nsec = UTIME_OMIT;
ts[1].tv_sec = static_cast<std::time_t>(s.count());
ts[1].tv_nsec = static_cast<long>(ns.count());
if (::utimensat(AT_FDCWD, p.c_str(), ts, 0))
ec.assign(errno, std::generic_category());
else
ec.clear();
#elif _GLIBCXX_HAVE_UTIME_H
::utimbuf times;
times.modtime = s.count();
times.actime = do_stat(p, ec, [](const auto& st) { return st.st_atime; },
times.modtime);
if (::utime(p.c_str(), &times))
ec.assign(errno, std::generic_category());
else
ec.clear();
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
}
void
fs::permissions(const path& p, perms prms, perm_options opts)
{
error_code ec;
permissions(p, prms, opts, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot set permissions", p, ec));
}
void
fs::permissions(const path& p, perms prms, perm_options opts,
error_code& ec) noexcept
{
const bool replace = is_set(opts, perm_options::replace);
const bool add = is_set(opts, perm_options::add);
const bool remove = is_set(opts, perm_options::remove);
const bool nofollow = is_set(opts, perm_options::nofollow);
if (((int)replace + (int)add + (int)remove) != 1)
{
ec = std::make_error_code(std::errc::invalid_argument);
return;
}
prms &= perms::mask;
file_status st;
if (add || remove || nofollow)
{
st = nofollow ? symlink_status(p, ec) : status(p, ec);
if (ec)
return;
auto curr = st.permissions();
if (add)
prms |= curr;
else if (remove)
prms = curr & ~prms;
}
int err = 0;
#if _GLIBCXX_USE_FCHMODAT
const int flag = (nofollow && is_symlink(st)) ? AT_SYMLINK_NOFOLLOW : 0;
if (::fchmodat(AT_FDCWD, p.c_str(), static_cast<mode_t>(prms), flag))
err = errno;
#else
if (nofollow && is_symlink(st))
ec = std::make_error_code(std::errc::operation_not_supported);
else if (::chmod(p.c_str(), static_cast<mode_t>(prms)))
err = errno;
#endif
if (err)
ec.assign(err, std::generic_category());
else
ec.clear();
}
fs::path
fs::proximate(const path& p, const path& base)
{
return weakly_canonical(p).lexically_proximate(weakly_canonical(base));
}
fs::path
fs::proximate(const path& p, const path& base, error_code& ec)
{
path result;
const auto p2 = weakly_canonical(p, ec);
if (!ec)
{
const auto base2 = weakly_canonical(base, ec);
if (!ec)
result = p2.lexically_proximate(base2);
}
return result;
}
fs::path
fs::read_symlink(const path& p)
{
error_code ec;
path tgt = read_symlink(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("read_symlink", p, ec));
return tgt;
}
fs::path fs::read_symlink(const path& p, error_code& ec)
{
path result;
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
stat_type st;
if (::lstat(p.c_str(), &st))
{
ec.assign(errno, std::generic_category());
return result;
}
std::string buf(st.st_size ? st.st_size + 1 : 128, '\0');
do
{
ssize_t len = ::readlink(p.c_str(), buf.data(), buf.size());
if (len == -1)
{
ec.assign(errno, std::generic_category());
return result;
}
else if (len == (ssize_t)buf.size())
{
if (buf.size() > 4096)
{
ec.assign(ENAMETOOLONG, std::generic_category());
return result;
}
buf.resize(buf.size() * 2);
}
else
{
buf.resize(len);
result.assign(buf);
ec.clear();
break;
}
}
while (true);
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
return result;
}
fs::path
fs::relative(const path& p, const path& base)
{
return weakly_canonical(p).lexically_relative(weakly_canonical(base));
}
fs::path
fs::relative(const path& p, const path& base, error_code& ec)
{
auto result = weakly_canonical(p, ec);
fs::path cbase;
if (!ec)
cbase = weakly_canonical(base, ec);
if (!ec)
result = result.lexically_relative(cbase);
if (ec)
result.clear();
return result;
}
bool
fs::remove(const path& p)
{
error_code ec;
const bool result = fs::remove(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove", p, ec));
return result;
}
bool
fs::remove(const path& p, error_code& ec) noexcept
{
if (::remove(p.c_str()) == 0)
{
ec.clear();
return true;
}
else if (errno == ENOENT)
ec.clear();
else
ec.assign(errno, std::generic_category());
return false;
}
std::uintmax_t
fs::remove_all(const path& p)
{
error_code ec;
const auto result = remove_all(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove all", p, ec));
return result;
}
std::uintmax_t
fs::remove_all(const path& p, error_code& ec)
{
const auto s = symlink_status(p, ec);
if (!status_known(s))
return -1;
ec.clear();
if (s.type() == file_type::not_found)
return 0;
uintmax_t count = 0;
if (s.type() == file_type::directory)
{
for (directory_iterator d(p, ec), end; !ec && d != end; d.increment(ec))
count += fs::remove_all(d->path(), ec);
if (ec.value() == ENOENT)
ec.clear();
else if (ec)
return -1;
}
if (fs::remove(p, ec))
++count;
return ec ? -1 : count;
}
void
fs::rename(const path& from, const path& to)
{
error_code ec;
rename(from, to, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot rename", from, to, ec));
}
void
fs::rename(const path& from, const path& to, error_code& ec) noexcept
{
if (::rename(from.c_str(), to.c_str()))
ec.assign(errno, std::generic_category());
else
ec.clear();
}
void
fs::resize_file(const path& p, uintmax_t size)
{
error_code ec;
resize_file(p, size, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot resize file", p, ec));
}
void
fs::resize_file(const path& p, uintmax_t size, error_code& ec) noexcept
{
#ifdef _GLIBCXX_HAVE_UNISTD_H
if (size > static_cast<uintmax_t>(std::numeric_limits<off_t>::max()))
ec.assign(EINVAL, std::generic_category());
else if (::truncate(p.c_str(), size))
ec.assign(errno, std::generic_category());
else
ec.clear();
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
}
fs::space_info
fs::space(const path& p)
{
error_code ec;
space_info s = space(p, ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot get free space", p, ec));
return s;
}
fs::space_info
fs::space(const path& p, error_code& ec) noexcept
{
space_info info = {
static_cast<uintmax_t>(-1),
static_cast<uintmax_t>(-1),
static_cast<uintmax_t>(-1)
};
#ifdef _GLIBCXX_HAVE_SYS_STATVFS_H
struct ::statvfs f;
if (::statvfs(p.c_str(), &f))
ec.assign(errno, std::generic_category());
else
{
uintmax_t fragment_size = f.f_frsize;
info = space_info{
f.f_blocks * fragment_size,
f.f_bfree * fragment_size,
f.f_bavail * fragment_size
};
ec.clear();
}
#else
ec = std::make_error_code(std::errc::not_supported);
#endif
return info;
}
#ifdef _GLIBCXX_HAVE_SYS_STAT_H
fs::file_status
fs::status(const fs::path& p, error_code& ec) noexcept
{
file_status status;
stat_type st;
if (::stat(p.c_str(), &st))
{
int err = errno;
ec.assign(err, std::generic_category());
if (is_not_found_errno(err))
status.type(file_type::not_found);
#ifdef EOVERFLOW
else if (err == EOVERFLOW)
status.type(file_type::unknown);
#endif
}
else
{
status = make_file_status(st);
ec.clear();
}
return status;
}
fs::file_status
fs::symlink_status(const fs::path& p, std::error_code& ec) noexcept
{
file_status status;
stat_type st;
if (::lstat(p.c_str(), &st))
{
int err = errno;
ec.assign(err, std::generic_category());
if (is_not_found_errno(err))
status.type(file_type::not_found);
}
else
{
status = make_file_status(st);
ec.clear();
}
return status;
}
#endif
fs::file_status
fs::status(const fs::path& p)
{
std::error_code ec;
auto result = status(p, ec);
if (result.type() == file_type::none)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("status", p, ec));
return result;
}
fs::file_status
fs::symlink_status(const fs::path& p)
{
std::error_code ec;
auto result = symlink_status(p, ec);
if (result.type() == file_type::none)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("symlink_status", p, ec));
return result;
}
fs::path fs::temp_directory_path()
{
error_code ec;
path tmp = temp_directory_path(ec);
if (ec)
_GLIBCXX_THROW_OR_ABORT(filesystem_error("temp_directory_path", ec));
return tmp;
}
fs::path fs::temp_directory_path(error_code& ec)
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
ec = std::make_error_code(std::errc::not_supported);
return {}; // TODO
#else
const char* tmpdir = nullptr;
const char* env[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr };
for (auto e = env; tmpdir == nullptr && *e != nullptr; ++e)
tmpdir = ::getenv(*e);
path p = tmpdir ? tmpdir : "/tmp";
auto st = status(p, ec);
if (!ec)
{
if (is_directory(st))
{
ec.clear();
return p;
}
else
ec = std::make_error_code(std::errc::not_a_directory);
}
return {};
#endif
}
fs::path
fs::weakly_canonical(const path& p)
{
path result;
if (exists(status(p)))
return canonical(p);
path tmp;
auto iter = p.begin(), end = p.end();
// find leading elements of p that exist:
while (iter != end)
{
tmp = result / *iter;
if (exists(status(tmp)))
swap(result, tmp);
else
break;
++iter;
}
// canonicalize:
result = canonical(result);
// append the non-existing elements:
while (iter != end)
result /= *iter++;
// normalize:
return result.lexically_normal();
}
fs::path
fs::weakly_canonical(const path& p, error_code& ec)
{
path result;
file_status st = status(p, ec);
if (exists(st))
return canonical(p, ec);
else if (status_known(st))
ec.clear();
else
return result;
path tmp;
auto iter = p.begin(), end = p.end();
// find leading elements of p that exist:
while (iter != end)
{
tmp = result / *iter;
st = status(tmp, ec);
if (exists(st))
swap(result, tmp);
else
{
if (status_known(st))
ec.clear();
break;
}
++iter;
}
// canonicalize:
if (!ec)
result = canonical(result, ec);
if (ec)
result.clear();
else
{
// append the non-existing elements:
while (iter != end)
result /= *iter++;
// normalize:
result = result.lexically_normal();
}
return result;
}