summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2025-10-07 19:58:28 +0200
committerJoel Klinghed <the_jk@spawned.biz>2025-10-19 00:13:47 +0200
commit4f6e76493fb74f5385d5a14dce3a01c9901802ed (patch)
treea38722ec832fd44ad34257730e075e8b07825bd0 /src
parentc87f9627efc8b612eb9b000acfcc6731cad15765 (diff)
paths: New module
Path utilities (doh)
Diffstat (limited to 'src')
-rw-r--r--src/paths.cc100
-rw-r--r--src/paths.hh20
2 files changed, 120 insertions, 0 deletions
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 <algorithm>
+#include <cerrno>
+#include <cstddef>
+#include <cstdlib>
+#include <iterator>
+#include <memory>
+#include <pwd.h>
+#include <string_view>
+#include <unistd.h>
+#include <unordered_set>
+#include <vector>
+
+namespace paths {
+
+namespace {
+
+std::vector<std::filesystem::path> xdg_read_dirs(
+ const char* userdir_env_name, std::string_view userdir_home_default,
+ const char* dirs_env_name,
+ std::vector<std::filesystem::path> const& dirs_default_value) {
+ std::vector<std::filesystem::path> ret;
+ std::unordered_set<std::filesystem::path> 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<size_t>(maybe_size) : 1024;
+ auto buffer = std::make_unique<char[]>(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<char[]>(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<std::filesystem::path> config_dirs() {
+ static const std::vector<std::filesystem::path> fallback{"/etc/xdg"};
+ return xdg_read_dirs("XDG_CONFIG_HOME", ".config", "XDG_CONFIG_DIRS",
+ fallback);
+}
+
+std::vector<std::filesystem::path> data_dirs() {
+ static const std::vector<std::filesystem::path> 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 <filesystem> // IWYU pragma: export
+#include <vector>
+
+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<std::filesystem::path> config_dirs();
+
+// Return data directories for reading, in order of priority.
+std::vector<std::filesystem::path> data_dirs();
+
+} // namespace paths
+
+#endif // PATHS_HH