From 4f6e76493fb74f5385d5a14dce3a01c9901802ed Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 7 Oct 2025 19:58:28 +0200 Subject: paths: New module Path utilities (doh) --- src/paths.cc | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/paths.hh | 20 ++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/paths.cc create mode 100644 src/paths.hh (limited to 'src') diff --git a/src/paths.cc b/src/paths.cc new file mode 100644 index 0000000..091be6e --- /dev/null +++ b/src/paths.cc @@ -0,0 +1,100 @@ +#include "paths.hh" + +#include "str.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace paths { + +namespace { + +std::vector xdg_read_dirs( + const char* userdir_env_name, std::string_view userdir_home_default, + const char* dirs_env_name, + std::vector const& dirs_default_value) { + std::vector ret; + std::unordered_set tmp; + auto* env_userdir = getenv(userdir_env_name); + if (env_userdir != nullptr && env_userdir[0] != '\0') { + ret.emplace_back(env_userdir); + } else { + ret.emplace_back(home() / userdir_home_default); + } + tmp.insert(ret.back()); + auto* env_dirs = getenv(dirs_env_name); + if (env_dirs != nullptr && env_dirs[0] != '\0') { + for (auto dir : str::split(env_dirs, ':')) { + if (tmp.emplace(dir).second) { + ret.emplace_back(dir); + } + } + } else { + std::ranges::copy_if( + dirs_default_value, std::back_inserter(ret), + [&tmp](auto const& dir) { return tmp.emplace(dir).second; }); + } + return ret; +} + +} // namespace + +std::filesystem::path home() { + { + auto* str = getenv("HOME"); + if (str != nullptr && str[0] != '\0') + return str; + } + + { + auto maybe_size = sysconf(_SC_GETPW_R_SIZE_MAX); + size_t size = maybe_size > 0 ? static_cast(maybe_size) : 1024; + auto buffer = std::make_unique(size); + struct passwd pwd; + struct passwd* ret; + int err; + while (true) { + err = getpwuid_r(geteuid(), &pwd, buffer.get(), size, &ret); + if (err == 0) + break; + if (err != ERANGE) + break; + auto new_size = size * 2; + if (new_size < size) + break; + buffer = std::make_unique(new_size); + size = new_size; + } + if (err == 0 && ret) { + if (ret->pw_dir != nullptr && ret->pw_dir[0] != '\0') { + return ret->pw_dir; + } + } + } + + return "/"; +} + +std::vector config_dirs() { + static const std::vector fallback{"/etc/xdg"}; + return xdg_read_dirs("XDG_CONFIG_HOME", ".config", "XDG_CONFIG_DIRS", + fallback); +} + +std::vector data_dirs() { + static const std::vector fallback{"/usr/local/share/", + "/usr/share/"}; + return xdg_read_dirs("XDG_DATA_HOME", ".local/share", "XDG_DATA_DIRS", + fallback); +} + +} // namespace paths diff --git a/src/paths.hh b/src/paths.hh new file mode 100644 index 0000000..8bd4c75 --- /dev/null +++ b/src/paths.hh @@ -0,0 +1,20 @@ +#ifndef PATHS_HH +#define PATHS_HH + +#include // IWYU pragma: export +#include + +namespace paths { + +// Return home directory, goes HOME, /etc/passwd entry, / in that order. +std::filesystem::path home(); + +// Return config directories for reading, in order of priority. +std::vector config_dirs(); + +// Return data directories for reading, in order of priority. +std::vector data_dirs(); + +} // namespace paths + +#endif // PATHS_HH -- cgit v1.2.3-70-g09d2