12 #include <sys/types.h> 28 #include <string_view> 30 #include <unordered_map> 31 #include <unordered_set> 47 #if __has_include(<paths.h>) 52 #define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) 58 bool isAbsolutePath(
const std::string &path) {
59 return !path.empty() && path[0] ==
'/';
62 std::string constructPath(
const std::string &basepath,
63 const std::string &subpath) {
64 if (basepath.empty()) {
67 return fs::cleanPath(stringutils::joinPath(basepath, subpath));
72 StandardPathFile::~StandardPathFile() =
default;
74 int StandardPathFile::release() {
return fd_.
release(); }
76 StandardPathTempFile::~StandardPathTempFile() { close(); }
78 int StandardPathTempFile::release() {
return fd_.
release(); }
80 void StandardPathTempFile::removeTemp() {
84 unlink(tempPath_.c_str());
88 void StandardPathTempFile::close() {
97 if (rename(tempPath_.c_str(), path_.c_str()) < 0) {
98 unlink(tempPath_.c_str());
106 const std::string &packageName,
107 const std::unordered_map<std::string, std::string> &builtInPathMap,
108 bool skipBuiltInPath,
bool skipUserPath)
109 : skipBuiltInPath_(skipBuiltInPath), skipUserPath_(skipUserPath) {
110 bool isFcitx = (packageName ==
"fcitx5");
112 configHome_ = defaultPath(
"XDG_CONFIG_HOME",
".config");
114 defaultPath((isFcitx ?
"FCITX_CONFIG_HOME" :
nullptr),
115 constructPath(configHome_, packageName).c_str());
116 configDirs_ = defaultPaths(
"XDG_CONFIG_DIRS",
"/etc/xdg",
117 builtInPathMap,
nullptr);
118 auto pkgconfigDirFallback = configDirs_;
119 for (
auto &path : pkgconfigDirFallback) {
120 path = constructPath(path, packageName);
123 defaultPaths((isFcitx ?
"FCITX_CONFIG_DIRS" :
nullptr),
125 builtInPathMap,
nullptr);
127 dataHome_ = defaultPath(
"XDG_DATA_HOME",
".local/share");
129 defaultPath((isFcitx ?
"FCITX_DATA_HOME" :
nullptr),
130 constructPath(dataHome_, packageName).c_str());
131 dataDirs_ = defaultPaths(
"XDG_DATA_DIRS",
"/usr/local/share:/usr/share",
133 skipBuiltInPath_ ?
nullptr :
"datadir");
134 auto pkgdataDirFallback = dataDirs_;
135 for (
auto &path : pkgdataDirFallback) {
136 path = constructPath(path, packageName);
138 pkgdataDirs_ = defaultPaths(
139 (isFcitx ?
"FCITX_DATA_DIRS" :
nullptr),
141 skipBuiltInPath_ ?
nullptr :
"pkgdatadir");
142 cacheHome_ = defaultPath(
"XDG_CACHE_HOME",
".cache");
143 auto tmpdir = getEnvironment(
"TMPDIR");
145 defaultPath(
"XDG_RUNTIME_DIR",
146 (!tmpdir || tmpdir->empty()) ?
"/tmp" : tmpdir->data());
149 addonDirs_ = defaultPaths(
"FCITX_ADDON_DIRS", FCITX_INSTALL_ADDONDIR,
150 builtInPathMap,
nullptr);
163 return pkgconfigHome_;
182 return pkgconfigDirs_;
194 bool skipUser()
const {
return skipUserPath_; }
195 bool skipBuiltIn()
const {
return skipBuiltInPath_; }
200 mode_t old = ::umask(022);
203 umask_.store(old, std::memory_order_relaxed);
206 mode_t umask()
const {
return umask_.load(std::memory_order_relaxed); }
210 static std::string defaultPath(
const char *env,
const char *defaultPath) {
211 std::optional<std::string> cdir;
213 cdir = getEnvironment(env);
216 if (cdir && !cdir->empty()) {
220 if (defaultPath[0] !=
'/') {
221 auto home = getEnvironment(
"HOME");
223 throw std::runtime_error(
"Home is not set");
225 dir = stringutils::joinPath(*home, defaultPath);
227 if (env && strcmp(env,
"XDG_RUNTIME_DIR") == 0) {
229 dir = stringutils::joinPath(
230 defaultPath, stringutils::concat(
"fcitx-runtime"));
232 dir = stringutils::joinPath(
234 stringutils::concat(
"fcitx-runtime-", geteuid()));
236 if (mkdir(dir.c_str(), 0700) != 0) {
248 if (!dir.empty() && env && strcmp(env,
"XDG_RUNTIME_DIR") == 0) {
250 if (stat(dir.c_str(), &buf) != 0 || buf.st_uid != geteuid() ||
251 (buf.st_mode & 0777) != S_IRWXU) {
259 static std::vector<std::string> defaultPaths(
260 const char *env,
const char *defaultPath,
261 const std::unordered_map<std::string, std::string> &builtInPathMap,
262 const char *builtInPathType) {
263 std::vector<std::string> dirs;
265 std::optional<std::string> dir;
267 dir = getEnvironment(env);
272 assert(dir.has_value());
275 for (
auto &rawDir : rawDirs) {
278 std::unordered_set<std::string> uniqueDirs(rawDirs.begin(),
281 for (
auto &s : rawDirs) {
282 auto iter = uniqueDirs.find(s);
283 if (iter != uniqueDirs.end()) {
284 uniqueDirs.erase(iter);
288 if (builtInPathType) {
289 std::string builtInPath;
290 if (
const auto *value =
291 findValue(builtInPathMap, builtInPathType)) {
292 builtInPath = *value;
297 if (!path.empty() && std::ranges::find(dirs, path) == dirs.end()) {
298 dirs.push_back(path);
305 bool skipBuiltInPath_;
307 std::string configHome_;
308 std::vector<std::string> configDirs_;
309 std::string pkgconfigHome_;
310 std::vector<std::string> pkgconfigDirs_;
311 std::string dataHome_;
312 std::vector<std::string> dataDirs_;
313 std::string pkgdataHome_;
314 std::vector<std::string> pkgdataDirs_;
315 std::string cacheHome_;
316 std::string runtimeDir_;
317 std::vector<std::string> addonDirs_;
318 std::atomic<mode_t> umask_;
322 const std::string &packageName,
323 const std::unordered_map<std::string, std::string> &builtInPath,
324 bool skipBuiltInPath,
bool skipUserPath)
326 packageName, builtInPath, skipBuiltInPath, skipUserPath)) {}
334 StandardPath::~StandardPath() =
default;
337 static bool skipFcitx = checkBoolEnvVar(
"SKIP_FCITX_PATH");
338 static bool skipUser = checkBoolEnvVar(
"SKIP_FCITX_USER_PATH");
348 static const std::unordered_map<std::string, std::string> pathMap = {
349 std::make_pair<std::string, std::string>(
"datadir",
350 FCITX_INSTALL_DATADIR),
351 std::make_pair<std::string, std::string>(
"pkgdatadir",
352 FCITX_INSTALL_PKGDATADIR),
353 std::make_pair<std::string, std::string>(
"libdir",
354 FCITX_INSTALL_LIBDIR),
355 std::make_pair<std::string, std::string>(
"bindir",
356 FCITX_INSTALL_BINDIR),
357 std::make_pair<std::string, std::string>(
"localedir",
358 FCITX_INSTALL_LOCALEDIR),
359 std::make_pair<std::string, std::string>(
"addondir",
360 FCITX_INSTALL_ADDONDIR),
361 std::make_pair<std::string, std::string>(
"libdatadir",
362 FCITX_INSTALL_LIBDATADIR),
363 std::make_pair<std::string, std::string>(
"libexecdir",
364 FCITX_INSTALL_LIBEXECDIR),
367 auto iter = pathMap.find(path);
368 if (iter != pathMap.end()) {
369 return iter->second.c_str();
376 return stringutils::joinPath(
fcitxPath(path), subPath);
381 return d->userPath(type);
386 return d->directories(type);
391 const std::function<
bool(
const std::string &path,
bool user)> &scanner)
394 std::string userDir = d->userPath(type);
395 std::vector<std::string> list = d->directories(type);
396 if (userDir.empty() && list.empty()) {
403 const std::string &userDir,
const std::vector<std::string> &
directories,
404 const std::function<
bool(
const std::string &path,
bool user)> &scanner)
406 std::string_view userDirView(userDir);
412 if (userDirView.empty() && directories.empty()) {
416 size_t len = (!userDirView.empty() ? 1 : 0) + directories.size();
418 for (
size_t i = 0; i < len; i++) {
420 std::string dirBasePath;
421 if (!userDirView.empty()) {
423 dirBasePath = isUser ? userDirView : directories[i - 1];
425 dirBasePath = directories[i];
429 if (!scanner(dirBasePath, isUser)) {
436 Type type,
const std::string &path,
437 const std::function<
bool(
const std::string &fileName,
438 const std::string &dir,
bool user)> &scanner)
440 auto scanDir = [scanner](
const std::string &fullPath,
bool isUser) {
441 UniqueCPtr<DIR, closedir> scopedDir{opendir(fullPath.c_str())};
442 if (
auto *dir = scopedDir.get()) {
444 while ((drt = readdir(dir)) !=
nullptr) {
445 if (strcmp(drt->d_name,
".") == 0 ||
446 strcmp(drt->d_name,
"..") == 0) {
450 if (!scanner(drt->d_name, fullPath, isUser)) {
457 if (isAbsolutePath(path)) {
458 scanDir(path,
false);
461 type, [&path, &scanDir](
const std::string &dirPath,
bool isUser) {
462 auto fullPath = constructPath(dirPath, path);
463 return scanDir(fullPath, isUser);
470 if (isAbsolutePath(path)) {
476 [&retPath, &path](
const std::string &dirPath,
bool) {
477 std::string fullPath = constructPath(dirPath, path);
481 retPath = std::move(fullPath);
488 std::vector<std::string>
490 std::vector<std::string> retPaths;
491 if (isAbsolutePath(path)) {
493 retPaths.push_back(path);
497 [&retPaths, &path](
const std::string &dirPath,
bool) {
498 auto fullPath = constructPath(dirPath, path);
500 retPaths.push_back(fullPath);
512 if (isAbsolutePath(path)) {
513 int fd =
::open(path.c_str(), flags);
520 &path](
const std::string &dirPath,
bool) {
521 auto fullPath = constructPath(dirPath, path);
522 int fd =
::open(fullPath.c_str(), flags);
527 fdPath = std::move(fullPath);
531 return {retFD, fdPath};
536 std::string fullPath;
537 if (isAbsolutePath(path)) {
541 if (dirPath.empty()) {
544 fullPath = constructPath(dirPath, path);
548 if ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR) {
554 int fd =
::open(fullPath.c_str(), flags, 0600);
556 return {fd, fullPath};
565 if (isAbsolutePath(path)) {
566 int fd =
::open(path.c_str(), flags);
573 &path](
const std::string &dirPath,
bool user) {
577 auto fullPath = constructPath(dirPath, path);
578 int fd =
::open(fullPath.c_str(), flags);
583 fdPath = std::move(fullPath);
587 return {retFD, fdPath};
591 const std::string &path,
593 std::vector<StandardPathFile> result;
594 if (isAbsolutePath(path)) {
595 int fd =
::open(path.c_str(), flags);
597 result.emplace_back(fd, path);
601 type, [flags, &result, &path](
const std::string &dirPath,
bool) {
602 auto fullPath = constructPath(dirPath, path);
603 int fd =
::open(fullPath.c_str(), flags);
607 result.emplace_back(fd, fullPath);
616 std::string path = pathOrig +
"_XXXXXX";
617 std::string fullPath;
618 std::string fullPathOrig;
619 if (isAbsolutePath(pathOrig)) {
620 fullPath = std::move(path);
621 fullPathOrig = pathOrig;
624 if (dirPath.empty()) {
627 fullPath = constructPath(dirPath, path);
628 fullPathOrig = constructPath(dirPath, pathOrig);
631 std::vector<char> cPath(fullPath.data(),
632 fullPath.data() + fullPath.size() + 1);
633 int fd = mkstemp(cPath.data());
635 return {fd, fullPathOrig, cPath.data()};
642 const std::function<
bool(
int)> &callback)
const {
645 if (!file.isValid()) {
649 if (callback(file.fd())) {
651 auto wfile = utf8::UTF8ToUTF16(file.tempPath());
652 ::_wchmod(wfile.data(), 0666 & ~(d->umask()));
655 fchmod(file.fd(), 0666 & ~(d->umask()));
659 }
catch (
const std::exception &) {
666 Type type,
const std::string &path,
667 std::function<
bool(
const std::string &path,
const std::string &dir,
670 std::map<std::string, std::string> result;
672 [&result, &filter](
const std::string &path,
673 const std::string &dir,
bool isUser) {
674 if (!result.contains(path) && filter(path, dir, isUser)) {
675 auto fullPath = constructPath(dir, path);
677 result.emplace(path, std::move(fullPath));
687 Type type,
const std::string &path,
int flags,
688 std::function<
bool(
const std::string &path,
const std::string &dir,
691 StandardPathFileMap result;
693 [&result, flags, &filter](
const std::string &path,
694 const std::string &dir,
bool isUser) {
695 if (!result.contains(path) && filter(path, dir, isUser)) {
696 auto fullPath = constructPath(dir, path);
697 int fd =
::open(fullPath.c_str(), flags);
699 result.emplace(std::piecewise_construct,
700 std::forward_as_tuple(path),
701 std::forward_as_tuple(fd, fullPath));
711 Type type,
const std::string &path,
int flags,
712 std::function<
bool(
const std::string &path,
const std::string &dir,
715 StandardPathFilesMap result;
717 [&result, flags, &filter](
const std::string &path,
718 const std::string &dir,
bool isUser) {
719 if (filter(path, dir, isUser)) {
720 auto fullPath = constructPath(dir, path);
721 int fd =
::open(fullPath.c_str(), flags);
723 result[path].emplace_back(fd, fullPath);
732 int64_t StandardPath::timestamp(
Type type,
const std::string &path)
const {
733 if (isAbsolutePath(path)) {
737 int64_t timestamp = 0;
739 type, [×tamp, &path](
const std::string &dirPath,
bool) {
740 auto fullPath = constructPath(dirPath, path);
752 if (name[0] ==
'/') {
757 if (
auto pEnv = getEnvironment(
"PATH")) {
758 sEnv = std::move(*pEnv);
760 #if defined(_PATH_DEFPATH) 761 sEnv = _PATH_DEFPATH;
762 #elif defined(_CS_PATH) 763 size_t n = confstr(_CS_PATH,
nullptr, 0);
765 std::vector<char> data;
767 confstr(_CS_PATH, data.data(), data.size());
768 data.push_back(
'\0');
774 for (
auto &path : paths) {
776 auto fullPath = constructPath(path, name);
777 if (!fullPath.empty() &&
fs::isexe(fullPath)) {
792 return d->skipBuiltIn();
797 return d->skipUser();
StandardPathTempFile openUserTemp(Type type, const std::string &pathOrig) const
Open user file, but create file with mktemp.
static bool hasExecutable(const std::string &name)
Check if certain executable presents in PATH.
StandardPath(const std::string &packageName, const std::unordered_map< std::string, std::string > &builtInPath, bool skipBuiltInPath, bool skipUserPath)
Allow to construct a StandardPath with customized internal value.
void scanFiles(Type type, const std::string &path, const std::function< bool(const std::string &path, const std::string &dir, bool user)> &scanner) const
Scan files scan file under [directory]/[path].
Simple file system related API for checking file status.
bool isexe(const std::string &path)
check whether path is an executable regular file.
bool isreg(const std::string &path)
check whether path is a regular file.
std::vector< StandardPathFile > openAll(Type type, const std::string &path, int flags) const
Open all files match the first [directory]/[path].
bool skipBuiltInPath() const
Whether this StandardPath is configured to Skip built-in path.
bool isdir(const std::string &path)
check whether path is a directory.
static const StandardPath & global()
Return the global instance of StandardPath.
std::vector< std::string > directories(Type type) const
Get all directories in the order of priority.
StandardPathFile openUser(Type type, const std::string &path, int flags) const
Open the user file.
C++ Utility functions for handling utf8 strings.
StandardPathFile openSystem(Type type, const std::string &path, int flags) const
Open the non-user file.
Utility class that wraps around UnixFD.
std::vector< std::string > split(std::string_view str, std::string_view delim, SplitBehavior behavior)
Split the string by delim.
std::map< std::string, std::string > locateWithFilter(Type type, const std::string &path, std::function< bool(const std::string &path, const std::string &dir, bool user)> filter) const
Locate all files match the filter under first [directory]/[path].
static std::string findExecutable(const std::string &name)
Check if certain executable presents in PATH.
static const char * fcitxPath(const char *path)
Return fcitx specific path defined at compile time.
std::vector< std::string > locateAll(Type type, const std::string &path) const
list all matched files.
bool safeSave(Type type, const std::string &pathOrig, const std::function< bool(int)> &callback) const
Save the file safely with write and rename to make sure the operation is atomic.
StandardPathFilesMap multiOpenAllFilter(Type type, const std::string &path, int flags, std::function< bool(const std::string &path, const std::string &dir, bool user)> filter) const
Open all files match the filter under all [directory]/[path].
void syncUmask() const
Sync system umask to internal state.
int fd() const noexcept
Get the internal fd.
std::string dirName(const std::string &path)
Get directory name of path.
bool skipUserPath() const
Whether this StandardPath is configured to Skip user path.
std::string locate(Type type, const std::string &path) const
Check if a file exists.
Utility classes to handle XDG file path.
bool makePath(const std::filesystem::path &path)
Create directory recursively.
Utility class to open, locate, list files based on XDG standard.
void reset() noexcept
Clear the FD and close it.
int64_t modifiedTime(const std::filesystem::path &path)
Return modified time in seconds of given path.
std::string join(Iter start, Iter end, T &&delim)
Join a range of string with delim.
Type
Enum for location type.
void scanDirectories(Type type, const std::function< bool(const std::string &path, bool user)> &scanner) const
Scan the directories of given type.
StandardPathFile open(Type type, const std::string &path, int flags) const
Open the first matched and succeeded file.
StandardPathFileMap multiOpenFilter(Type type, const std::string &path, int flags, std::function< bool(const std::string &path, const std::string &dir, bool user)> filter) const
Open all files match the filter under first [directory]/[path].
std::string userDirectory(Type type) const
Get user writable directory for given type.
addon shared library dir.
std::string cleanPath(const std::string &path)
Get the clean path by removing . , .. , and duplicate / in the path.
File descriptor wrapper that handles file descriptor and rename automatically.
int release() noexcept
Get the internal fd and release the ownership.