construct/ircd/fs_path.cc

547 lines
12 KiB
C++

// The Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2020 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include <RB_INC_SYS_STAT_H
#include <RB_INC_SYS_STATFS_H
#include <RB_INC_SYS_STATVFS_H
/// Default maximum path string length (for all filesystems & platforms).
decltype(ircd::fs::NAME_MAX_LEN)
ircd::fs::NAME_MAX_LEN
{
#ifdef NAME_MAX
NAME_MAX
#elif defined(_POSIX_NAME_MAX)
_POSIX_NAME_MAX
#else
255
#endif
};
/// Default maximum path string length (for all filesystems & platforms).
decltype(ircd::fs::PATH_MAX_LEN)
ircd::fs::PATH_MAX_LEN
{
#ifdef PATH_MAX
PATH_MAX
#elif defined(_POSIX_PATH_MAX)
_POSIX_PATH_MAX
#else
4096
#endif
};
// Convenience scratch buffers for path making.
namespace ircd::fs
{
static thread_local char _name_scratch[2][NAME_MAX_LEN];
static thread_local char _path_scratch[3][PATH_MAX_LEN];
}
// External mutable_buffer to the scratch
decltype(ircd::fs::path_scratch)
ircd::fs::path_scratch
{
_path_scratch[0]
};
// External mutable_buffer to the scratch
decltype(ircd::fs::name_scratch)
ircd::fs::name_scratch
{
_name_scratch[0]
};
/// e.g. / default=RB_PREFIX
/// env=ircd_fs_base_prefix
decltype(ircd::fs::base::prefix)
ircd::fs::base::prefix
{
{ "name", "ircd.fs.base.prefix" },
{ "persist", false },
{ "help", "directory prefix" },
{
"default",
getenv("IRCD_PREFIX")?
getenv("IRCD_PREFIX"):
RB_PREFIX
},
};
/// e.g. /usr/bin default=RB_BIN_DIR
/// env=ircd_fs_base_bin
decltype(ircd::fs::base::bin)
ircd::fs::base::bin
{
{ "name", "ircd.fs.base.bin" },
{ "persist", false },
{ "help", "binary directory" },
{
"default",
getenv("IRCD_BIN_DIR")?
getenv("IRCD_BIN_DIR"):
RB_BIN_DIR
},
};
/// e.g. /etc default=RB_CONF_DIR
/// env=$ircd_fs_base_etc env=$CONFIGURATION_DIRECTORY
decltype(ircd::fs::base::etc)
ircd::fs::base::etc
{
{ "name", "ircd.fs.base.etc" },
{ "persist", false },
{ "help", "configuration directory" },
{
"default",
getenv("CONFIGURATION_DIRECTORY")?
getenv("CONFIGURATION_DIRECTORY"):
getenv("IRCD_CONF_DIR")?
getenv("IRCD_CONF_DIR"):
RB_CONF_DIR
},
};
/// e.g. /include default=RB_INCLUDE_DIR
/// env=$ircd_fs_base_include env=$INCLUDE_DIRECTORY
decltype(ircd::fs::base::include)
ircd::fs::base::include
{
{ "name", "ircd.fs.base.include" },
{ "persist", false },
{ "help", "headers directory" },
{
"default",
getenv("INCLUDE_DIRECTORY")?
getenv("INCLUDE_DIRECTORY"):
getenv("IRCD_INCLUDE_DIR")?
getenv("IRCD_INCLUDE_DIR"):
RB_INCLUDE_DIR
},
};
/// e.g. /usr/lib default=RB_LIB_DIR
/// env=$ircd_fs_base_lib
decltype(ircd::fs::base::lib)
ircd::fs::base::lib
{
{ "name", "ircd.fs.base.lib" },
{ "persist", false },
{ "help", "library directory" },
{
"default",
getenv("IRCD_LIB_DIR")?
getenv("IRCD_LIB_DIR"):
RB_LIB_DIR
},
};
/// e.g. /usr/lib/modules/construct default=RB_MODULE_DIR
/// env=$ircd_fs_base_modules
decltype(ircd::fs::base::modules)
ircd::fs::base::modules
{
{ "name", "ircd.fs.base.modules" },
{ "persist", false },
{ "help", "modules directory" },
{
"default",
getenv("IRCD_MODULE_DIR")?
getenv("IRCD_MODULE_DIR"):
RB_MODULE_DIR
},
};
/// e.g. /usr/share/construct default=RB_DATA_DIR
/// env=$ircd_fs_base_share
decltype(ircd::fs::base::share)
ircd::fs::base::share
{
{ "name", "ircd.fs.base.share" },
{ "persist", false },
{ "help", "read-only data directory" },
{
"default",
getenv("IRCD_DATA_DIR")?
getenv("IRCD_DATA_DIR"):
RB_DATA_DIR
},
};
/// e.g. /var/run/construct default=RB_RUN_DIR
/// env=$ircd_fs_base_run env=$RUNTIME_DIRECTORY
decltype(ircd::fs::base::run)
ircd::fs::base::run
{
{ "name", "ircd.fs.base.run" },
{ "persist", false },
{ "help", "runtime directory" },
{
"default",
getenv("RUNTIME_DIRECTORY")?
getenv("RUNTIME_DIRECTORY"):
getenv("IRCD_RUN_DIR")?
getenv("IRCD_RUN_DIR"):
RB_RUN_DIR
},
};
/// e.g. /var/log/construct default=RB_LOG_DIR
/// env=$ircd_fs_base_log env=$LOGS_DIRECTORY
decltype(ircd::fs::base::log)
ircd::fs::base::log
{
{ "name", "ircd.fs.base.log" },
{ "persist", false },
{ "help", "logging directory" },
{
"default",
getenv("LOGS_DIRECTORY")?
getenv("LOGS_DIRECTORY"):
getenv("IRCD_LOG_DIR")?
getenv("IRCD_LOG_DIR"):
RB_LOG_DIR
},
};
/// e.g. /var/db/construct default=RB_DB_DIR
/// env=$ircd_fs_base_db env=$STATE_DIRECTORY
decltype(ircd::fs::base::db)
ircd::fs::base::db
{
{ "name", "ircd.fs.base.db" },
{ "persist", false },
{ "help", "database directory" },
{
"default",
getenv("STATE_DIRECTORY")?
getenv("STATE_DIRECTORY"):
getenv("IRCD_DB_DIR")?
getenv("IRCD_DB_DIR"):
RB_DB_DIR
},
};
//
// tools
//
bool
ircd::fs::is_parent(const string_view &path,
const string_view &str)
{
return parent(_path_scratch[2], path) == str;
}
bool
ircd::fs::is_filename(const string_view &path,
const string_view &str)
{
return filename(_path_scratch[2], path) == str;
}
bool
ircd::fs::is_extension(const string_view &path,
const string_view &str)
{
return extension(_path_scratch[2], path) == str;
}
ircd::string_view
ircd::fs::canonical(const mutable_buffer &buf,
const string_view &p)
try
{
return path(buf, canonical(_path(p)));
}
catch(const std::filesystem::filesystem_error &e)
{
throw error
{
e, "%s",
rsplit(e.what(), ':').second,
};
}
ircd::string_view
ircd::fs::canonical(const mutable_buffer &buf,
const string_view &root,
const string_view &p)
try
{
return path(buf, std::filesystem::canonical(_path(root) / _path(p)));
}
catch(const std::filesystem::filesystem_error &e)
{
throw error
{
e, "%s",
rsplit(e.what(), ':').second,
};
}
ircd::string_view
ircd::fs::relative(const mutable_buffer &buf,
const string_view &root,
const string_view &p)
{
return path(buf, std::filesystem::relative(_path(root) / _path(p)));
}
ircd::string_view
ircd::fs::absolute(const mutable_buffer &buf,
const string_view &root,
const string_view &p)
{
return path(buf, std::filesystem::absolute(_path(root) / _path(p)));
}
ircd::string_view
ircd::fs::parent(const mutable_buffer &buf,
const string_view &p)
{
return path(buf, _path(p).parent_path());
}
ircd::string_view
ircd::fs::filename(const mutable_buffer &buf,
const string_view &p)
{
return path(buf, _path(p).filename());
}
ircd::string_view
ircd::fs::extension(const mutable_buffer &buf,
const string_view &p)
{
return path(buf, _path(p).extension());
}
ircd::string_view
ircd::fs::extension(const mutable_buffer &buf,
const string_view &p,
const string_view &replace)
{
return path(buf, _path(p).replace_extension(_path(replace)));
}
bool
ircd::fs::is_relative(const string_view &p)
{
return _path(p).is_relative();
}
bool
ircd::fs::is_absolute(const string_view &p)
{
return _path(p).is_absolute();
}
//
// utils
//
std::string
ircd::fs::cwd()
{
const auto &cur
{
std::filesystem::current_path()
};
return cur.string();
}
ircd::string_view
ircd::fs::cwd(const mutable_buffer &buf)
{
const auto &cur
{
std::filesystem::current_path()
};
return strlcpy(buf, cur.native());
}
#ifdef _PC_PATH_MAX
size_t
ircd::fs::path_max_len(const string_view &path)
{
return pathconf(path, _PC_PATH_MAX);
}
#else
size_t
ircd::fs::path_max_len(const string_view &path)
{
return PATH_MAX_LEN;
}
#endif
#ifdef _PC_NAME_MAX
size_t
ircd::fs::name_max_len(const string_view &path)
{
return pathconf(path, _PC_NAME_MAX);
}
#elif defined(HAVE_SYS_STATFS_H)
size_t
ircd::fs::name_max_len(const string_view &path)
{
struct statfs f{0};
syscall(::statfs, path_cstr(path), &f);
return f.f_namelen;
}
#else
size_t
ircd::fs::name_max_len(const string_view &path)
{
return NAME_MAX_LEN;
}
#endif
long
ircd::fs::pathconf(const string_view &path,
const int &arg)
{
return syscall(::pathconf, path_cstr(path), arg);
}
//
// fs::path_cstr()
//
namespace ircd::fs
{
static const size_t _PATH_CSTR_BUFS {4};
static thread_local char _path_cstr[_PATH_CSTR_BUFS][PATH_MAX_LEN];
static thread_local size_t _path_cstr_pos;
}
const char *
ircd::fs::path_cstr(const string_view &s)
{
const auto pos
{
++_path_cstr_pos %= _PATH_CSTR_BUFS
};
strlcpy(_path_cstr[pos], s);
return _path_cstr[pos];
}
//
// fs::path()
//
ircd::string_view
ircd::fs::path(const mutable_buffer &buf,
const string_view &base,
const path_views &list)
{
// If no base is supplied the result is just as unsafe as using the
// other path() overloads. As a precaution we assume an empty base
// argument is the result of an attack on the input somehow.
if(unlikely(!base))
throw std::system_error
{
make_error_code(std::errc::invalid_argument)
};
const string_view supplied_path
{
path(_path_scratch[1], list)
};
// Generate a canonical result into the caller's buffer prefixed by the
// base path. N.B. if the caller used '../' this result *will* have escaped
// the base path, and is now an absolute path to somewhere else.
const string_view ret
{
canonical(buf, base, supplied_path)
};
const string_view canonical_base
{
canonical(_path_scratch[1], base)
};
// Given two absolute and fully resolved paths (canonical), if the result
// is not prefixed by the base it is incontrovertibly not under the base.
//
// Alternatively, we could make an effort to force-smash the supplied path
// onto the base and let other code determine it doesn't exist; however now
// this should only throw for truly malformed and malicious paths; best to
// just throw here.
if(!startswith(ret, canonical_base))
throw std::system_error
{
make_error_code(std::errc::no_such_file_or_directory)
};
return ret;
}
ircd::string_view
ircd::fs::path(const mutable_buffer &buf,
const std::filesystem::path &path)
{
return strlcpy(buf, path.c_str());
}
ircd::string_view
ircd::fs::path(const mutable_buffer &buf,
const path_strings &list)
{
return strlcpy(buf, _path(list).c_str());
}
ircd::string_view
ircd::fs::path(const mutable_buffer &buf,
const path_views &list)
{
return strlcpy(buf, _path(list).c_str());
}
//
// fs::_path()
//
std::filesystem::path
ircd::fs::_path(const path_strings &list)
{
std::filesystem::path ret;
for(const auto &s : list)
ret /= s;
return ret.string();
}
std::filesystem::path
ircd::fs::_path(const path_views &list)
{
std::filesystem::path ret;
for(const auto &s : list)
ret /= _path(s);
return ret.string();
}
std::filesystem::path
ircd::fs::_path(const string_view &s)
{
return _path(std::string{s});
}
std::filesystem::path
ircd::fs::_path(std::string s)
{
return std::filesystem::path{std::move(s)};
}