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;
298 std::find(dirs.begin(), dirs.end(), path) == dirs.end()) {
299 dirs.push_back(path);
306 bool skipBuiltInPath_;
308 std::string configHome_;
309 std::vector<std::string> configDirs_;
310 std::string pkgconfigHome_;
311 std::vector<std::string> pkgconfigDirs_;
312 std::string dataHome_;
313 std::vector<std::string> dataDirs_;
314 std::string pkgdataHome_;
315 std::vector<std::string> pkgdataDirs_;
316 std::string cacheHome_;
317 std::string runtimeDir_;
318 std::vector<std::string> addonDirs_;
319 std::atomic<mode_t> umask_;
323 const std::string &packageName,
324 const std::unordered_map<std::string, std::string> &builtInPath,
325 bool skipBuiltInPath,
bool skipUserPath)
327 packageName, builtInPath, skipBuiltInPath, skipUserPath)) {}
335 StandardPath::~StandardPath() =
default;
338 static bool skipFcitx = checkBoolEnvVar(
"SKIP_FCITX_PATH");
339 static bool skipUser = checkBoolEnvVar(
"SKIP_FCITX_USER_PATH");
349 static const std::unordered_map<std::string, std::string> pathMap = {
350 std::make_pair<std::string, std::string>(
"datadir",
351 FCITX_INSTALL_DATADIR),
352 std::make_pair<std::string, std::string>(
"pkgdatadir",
353 FCITX_INSTALL_PKGDATADIR),
354 std::make_pair<std::string, std::string>(
"libdir",
355 FCITX_INSTALL_LIBDIR),
356 std::make_pair<std::string, std::string>(
"bindir",
357 FCITX_INSTALL_BINDIR),
358 std::make_pair<std::string, std::string>(
"localedir",
359 FCITX_INSTALL_LOCALEDIR),
360 std::make_pair<std::string, std::string>(
"addondir",
361 FCITX_INSTALL_ADDONDIR),
362 std::make_pair<std::string, std::string>(
"libdatadir",
363 FCITX_INSTALL_LIBDATADIR),
364 std::make_pair<std::string, std::string>(
"libexecdir",
365 FCITX_INSTALL_LIBEXECDIR),
368 auto iter = pathMap.find(path);
369 if (iter != pathMap.end()) {
370 return iter->second.c_str();
377 return stringutils::joinPath(
fcitxPath(path), subPath);
382 return d->userPath(type);
387 return d->directories(type);
392 const std::function<
bool(
const std::string &path,
bool user)> &scanner)
395 std::string userDir = d->userPath(type);
396 std::vector<std::string> list = d->directories(type);
397 if (userDir.empty() && list.empty()) {
404 const std::string &userDir,
const std::vector<std::string> &
directories,
405 const std::function<
bool(
const std::string &path,
bool user)> &scanner)
407 std::string_view userDirView(userDir);
413 if (userDirView.empty() && directories.empty()) {
417 size_t len = (!userDirView.empty() ? 1 : 0) + directories.size();
419 for (
size_t i = 0; i < len; i++) {
421 std::string dirBasePath;
422 if (!userDirView.empty()) {
424 dirBasePath = isUser ? userDirView : directories[i - 1];
426 dirBasePath = directories[i];
430 if (!scanner(dirBasePath, isUser)) {
437 Type type,
const std::string &path,
438 const std::function<
bool(
const std::string &fileName,
439 const std::string &dir,
bool user)> &scanner)
441 auto scanDir = [scanner](
const std::string &fullPath,
bool isUser) {
442 UniqueCPtr<DIR, closedir> scopedDir{opendir(fullPath.c_str())};
443 if (
auto *dir = scopedDir.get()) {
445 while ((drt = readdir(dir)) !=
nullptr) {
446 if (strcmp(drt->d_name,
".") == 0 ||
447 strcmp(drt->d_name,
"..") == 0) {
451 if (!scanner(drt->d_name, fullPath, isUser)) {
458 if (isAbsolutePath(path)) {
459 scanDir(path,
false);
462 type, [&path, &scanDir](
const std::string &dirPath,
bool isUser) {
463 auto fullPath = constructPath(dirPath, path);
464 return scanDir(fullPath, isUser);
471 if (isAbsolutePath(path)) {
477 [&retPath, &path](
const std::string &dirPath,
bool) {
478 std::string fullPath = constructPath(dirPath, path);
482 retPath = std::move(fullPath);
489 std::vector<std::string>
491 std::vector<std::string> retPaths;
492 if (isAbsolutePath(path)) {
494 retPaths.push_back(path);
498 [&retPaths, &path](
const std::string &dirPath,
bool) {
499 auto fullPath = constructPath(dirPath, path);
501 retPaths.push_back(fullPath);
513 if (isAbsolutePath(path)) {
514 int fd =
::open(path.c_str(), flags);
521 &path](
const std::string &dirPath,
bool) {
522 auto fullPath = constructPath(dirPath, path);
523 int fd =
::open(fullPath.c_str(), flags);
528 fdPath = std::move(fullPath);
532 return {retFD, fdPath};
537 std::string fullPath;
538 if (isAbsolutePath(path)) {
542 if (dirPath.empty()) {
545 fullPath = constructPath(dirPath, path);
549 if ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR) {
555 int fd =
::open(fullPath.c_str(), flags, 0600);
557 return {fd, fullPath};
566 if (isAbsolutePath(path)) {
567 int fd =
::open(path.c_str(), flags);
574 &path](
const std::string &dirPath,
bool user) {
578 auto fullPath = constructPath(dirPath, path);
579 int fd =
::open(fullPath.c_str(), flags);
584 fdPath = std::move(fullPath);
588 return {retFD, fdPath};
592 const std::string &path,
594 std::vector<StandardPathFile> result;
595 if (isAbsolutePath(path)) {
596 int fd =
::open(path.c_str(), flags);
598 result.emplace_back(fd, path);
602 type, [flags, &result, &path](
const std::string &dirPath,
bool) {
603 auto fullPath = constructPath(dirPath, path);
604 int fd =
::open(fullPath.c_str(), flags);
608 result.emplace_back(fd, fullPath);
617 std::string path = pathOrig +
"_XXXXXX";
618 std::string fullPath;
619 std::string fullPathOrig;
620 if (isAbsolutePath(pathOrig)) {
621 fullPath = std::move(path);
622 fullPathOrig = pathOrig;
625 if (dirPath.empty()) {
628 fullPath = constructPath(dirPath, path);
629 fullPathOrig = constructPath(dirPath, pathOrig);
632 std::vector<char> cPath(fullPath.data(),
633 fullPath.data() + fullPath.size() + 1);
634 int fd = mkstemp(cPath.data());
636 return {fd, fullPathOrig, cPath.data()};
643 const std::function<
bool(
int)> &callback)
const {
646 if (!file.isValid()) {
650 if (callback(file.fd())) {
652 auto wfile = utf8::UTF8ToUTF16(file.tempPath());
653 ::_wchmod(wfile.data(), 0666 & ~(d->umask()));
656 fchmod(file.fd(), 0666 & ~(d->umask()));
660 }
catch (
const std::exception &) {
667 Type type,
const std::string &path,
668 std::function<
bool(
const std::string &path,
const std::string &dir,
671 std::map<std::string, std::string> result;
673 [&result, &filter](
const std::string &path,
674 const std::string &dir,
bool isUser) {
675 if (!result.contains(path) && filter(path, dir, isUser)) {
676 auto fullPath = constructPath(dir, path);
678 result.emplace(path, std::move(fullPath));
688 Type type,
const std::string &path,
int flags,
689 std::function<
bool(
const std::string &path,
const std::string &dir,
692 StandardPathFileMap result;
694 [&result, flags, &filter](
const std::string &path,
695 const std::string &dir,
bool isUser) {
696 if (!result.contains(path) && filter(path, dir, isUser)) {
697 auto fullPath = constructPath(dir, path);
698 int fd =
::open(fullPath.c_str(), flags);
700 result.emplace(std::piecewise_construct,
701 std::forward_as_tuple(path),
702 std::forward_as_tuple(fd, fullPath));
712 Type type,
const std::string &path,
int flags,
713 std::function<
bool(
const std::string &path,
const std::string &dir,
716 StandardPathFilesMap result;
718 [&result, flags, &filter](
const std::string &path,
719 const std::string &dir,
bool isUser) {
720 if (filter(path, dir, isUser)) {
721 auto fullPath = constructPath(dir, path);
722 int fd =
::open(fullPath.c_str(), flags);
724 result[path].emplace_back(fd, fullPath);
733 int64_t StandardPath::timestamp(
Type type,
const std::string &path)
const {
734 if (isAbsolutePath(path)) {
738 int64_t timestamp = 0;
740 type, [×tamp, &path](
const std::string &dirPath,
bool) {
741 auto fullPath = constructPath(dirPath, path);
753 if (name[0] ==
'/') {
758 if (
auto pEnv = getEnvironment(
"PATH")) {
759 sEnv = std::move(*pEnv);
761 #if defined(_PATH_DEFPATH) 762 sEnv = _PATH_DEFPATH;
763 #elif defined(_CS_PATH) 764 size_t n = confstr(_CS_PATH,
nullptr, 0);
766 std::vector<char> data;
768 confstr(_CS_PATH, data.data(), data.size());
769 data.push_back(
'\0');
775 for (
auto &path : paths) {
777 auto fullPath = constructPath(path, name);
778 if (!fullPath.empty() &&
fs::isexe(fullPath)) {
793 return d->skipBuiltIn();
798 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.