Fcitx
standardpaths.cpp
1 /*
2  * SPDX-FileCopyrightText: 2025-2025 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 #include "standardpaths.h"
8 #include <dirent.h>
9 #include <fcntl.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <unistd.h>
13 #include <algorithm>
14 #include <cassert>
15 #include <cstdint>
16 #include <cstdio>
17 #include <cstdlib>
18 #include <cstring>
19 #include <exception>
20 #include <filesystem>
21 #include <functional>
22 #include <initializer_list>
23 #include <map>
24 #include <memory>
25 #include <mutex>
26 #include <optional>
27 #include <string>
28 #include <string_view>
29 #include <system_error>
30 #include <unordered_map>
31 #include <utility>
32 #include <vector>
33 #include <span>
34 #include "fcitx-utils/standardpaths_p.h"
35 #include "config.h" // IWYU pragma: keep
36 #include "environ.h"
37 #include "fs.h"
38 #include "log.h"
39 #include "macros.h"
40 #include "misc.h"
41 #include "misc_p.h"
42 #include "standardpaths_p.h"
43 #include "stringutils.h"
44 #include "unixfd.h"
45 
46 namespace fcitx {
47 
48 const std::filesystem::path StandardPathsPrivate::constEmptyPath;
49 const std::vector<std::filesystem::path> StandardPathsPrivate::constEmptyPaths =
50  {std::filesystem::path()};
51 
52 std::mutex StandardPathsPrivate::globalMutex_;
53 std::unique_ptr<StandardPaths> StandardPathsPrivate::global_;
54 
55 namespace {
56 
57 constexpr std::string_view envListSeparator = isWindows() ? ";" : ":";
58 
59 std::vector<std::filesystem::path> pathFromEnvironment() {
60  std::string sEnv;
61  if (auto pEnv = getEnvironment("PATH")) {
62  sEnv = std::move(*pEnv);
63  } else {
64 #if defined(_PATH_DEFPATH)
65  sEnv = _PATH_DEFPATH;
66 #elif defined(_CS_PATH)
67  size_t n = confstr(_CS_PATH, nullptr, 0);
68  if (n) {
69  std::vector<char> data;
70  data.resize(n + 1);
71  confstr(_CS_PATH, data.data(), data.size());
72  data.push_back('\0');
73  sEnv = data.data();
74  }
75 #endif
76  }
77  auto paths = stringutils::split(sEnv, envListSeparator);
78  std::vector<std::filesystem::path> result;
79  result.reserve(paths.size());
80  for (auto &path : paths) {
81  result.push_back(path);
82  }
83  return result;
84 }
85 
86 } // namespace
87 
89  const std::string &packageName,
90  const std::unordered_map<std::string, std::vector<std::filesystem::path>>
91  &builtInPath,
92  StandardPathsOptions options)
93  : d_ptr(std::make_unique<StandardPathsPrivate>(packageName, builtInPath,
94  options)) {}
95 
96 StandardPaths::~StandardPaths() = default;
97 
99  std::lock_guard<std::mutex> lock(StandardPathsPrivate::globalMutex_);
100  if (!StandardPathsPrivate::global_) {
101  bool skipFcitx = checkBoolEnvVar("SKIP_FCITX_PATH");
102  bool skipFcitxSystem = checkBoolEnvVar("SKIP_FCITX_SYSTEM_PATH");
103  bool skipUser = checkBoolEnvVar("SKIP_FCITX_USER_PATH");
105  if (skipUser) {
106  options |= StandardPathsOption::SkipUserPath;
107  }
108  if (skipFcitxSystem) {
109  options |= StandardPathsOption::SkipSystemPath;
110  }
111  if (skipFcitx) {
112  options |= StandardPathsOption::SkipBuiltInPath;
113  }
114  StandardPathsPrivate::global_ = std::make_unique<StandardPaths>(
115  "fcitx5",
116  std::unordered_map<std::string,
117  std::vector<std::filesystem::path>>{},
118  options);
119  }
120 
121  return *StandardPathsPrivate::global_;
122 }
123 
124 std::filesystem::path
125 StandardPaths::fcitxPath(const char *path,
126  const std::filesystem::path &subPath) {
127  return StandardPathsPrivate::fcitxPath(path, subPath);
128 }
129 
130 std::filesystem::path
131 StandardPaths::findExecutable(const std::filesystem::path &name) {
132  if (name.is_absolute()) {
133  return fs::isexe(name.string()) ? name : std::filesystem::path();
134  }
135 
136  for (const auto &path : pathFromEnvironment()) {
137  auto fullPath = path / name;
138  if (fs::isexe(fullPath.string())) {
139  return fullPath;
140  }
141  }
142  return {};
143 }
144 
145 bool StandardPaths::hasExecutable(const std::filesystem::path &name) {
146  return !findExecutable(name).empty();
147 }
148 
149 const std::filesystem::path &
151  FCITX_D();
152  if (skipUserPath()) {
153  return StandardPathsPrivate::constEmptyPath;
154  }
155  auto dirs = d->directories(type, StandardPathsMode::User);
156  return dirs.empty() ? StandardPathsPrivate::constEmptyPath : dirs[0];
157 }
158 
159 std::span<const std::filesystem::path>
161  StandardPathsModes modes) const {
162  FCITX_D();
163  return d->directories(type, modes);
164 }
165 
166 std::filesystem::path StandardPaths::locate(StandardPathsType type,
167  const std::filesystem::path &path,
168  StandardPathsModes modes) const {
169  FCITX_D();
170  std::filesystem::path retPath;
171  d->scanDirectories(type, path, modes,
172  [&retPath](const std::filesystem::path &fullPath) {
173  std::error_code ec;
174  if (!std::filesystem::exists(fullPath, ec)) {
175  return true;
176  }
177  retPath = fullPath;
178  return false;
179  });
180  return retPath;
181 }
182 
183 std::vector<std::filesystem::path>
185  const std::filesystem::path &path,
186  StandardPathsModes modes) const {
187  FCITX_D();
188  std::vector<std::filesystem::path> retPaths;
189  d->scanDirectories(type, path, modes,
190  [&retPaths](std::filesystem::path fullPath) {
191  std::error_code ec;
192  if (!std::filesystem::exists(fullPath, ec)) {
193  return true;
194  }
195  retPaths.push_back(std::move(fullPath));
196  return true;
197  });
198  return retPaths;
199 }
200 
201 std::map<std::filesystem::path, std::filesystem::path>
202 StandardPaths::locate(StandardPathsType type, const std::filesystem::path &path,
203  const StandardPathsFilterCallback &callback,
204  StandardPathsModes modes) const {
205  FCITX_D();
206  std::map<std::filesystem::path, std::filesystem::path> retPath;
207  d->scanDirectories(
208  type, path, modes,
209  [&retPath, &callback](const std::filesystem::path &fullPath) {
210  std::error_code ec;
211  for (auto directoryIterator =
212  std::filesystem::directory_iterator(fullPath, ec);
213  directoryIterator != std::filesystem::directory_iterator();
214  directoryIterator.increment(ec)) {
215  if (ec) {
216  return true;
217  }
218  if (retPath.contains(directoryIterator->path().filename())) {
219  continue;
220  }
221  if (callback(directoryIterator->path())) {
222  retPath[directoryIterator->path().filename()] =
223  directoryIterator->path();
224  }
225  }
226  return true;
227  });
228  return retPath;
229 }
230 
232  const std::filesystem::path &path,
233  StandardPathsModes modes,
234  std::filesystem::path *outPath) const {
235  FCITX_D();
236  UnixFD retFD;
237  d->scanDirectories(type, path, modes,
238  [&retFD, outPath](std::filesystem::path fullPath) {
239  retFD = openPath(fullPath);
240  if (!retFD.isValid()) {
241  return true;
242  }
243  if (outPath) {
244  *outPath = std::move(fullPath);
245  }
246  return false;
247  });
248  return retFD;
249 }
250 
251 UnixFD StandardPaths::openPath(const std::filesystem::path &path,
252  std::optional<int> flags,
253  std::optional<mode_t> mode) {
254  int f = flags.value_or(O_RDONLY);
255 
256  auto openFunc = [](auto path, int flag, auto... extra) {
257 #ifdef _WIN32
258  flag |= _O_BINARY;
259  return ::_wopen(path, flag, extra...);
260 #else
261  return ::open(path, flag, extra...);
262 #endif
263  };
264  if (mode.has_value()) {
265  return UnixFD::own(openFunc(path.c_str(), f, mode.value()));
266  }
267  return UnixFD::own(openFunc(path.c_str(), f));
268 }
269 
270 std::vector<UnixFD>
272  const std::filesystem::path &path,
273  StandardPathsModes modes,
274  std::vector<std::filesystem::path> *outPaths) const {
275  FCITX_D();
276  std::vector<UnixFD> retFDs;
277  if (outPaths) {
278  outPaths->clear();
279  }
280  d->scanDirectories(type, path, modes,
281  [&retFDs, outPaths](std::filesystem::path fullPath) {
282  UnixFD fd = StandardPaths::openPath(fullPath);
283  if (!fd.isValid()) {
284  return true;
285  }
286  retFDs.push_back(std::move(fd));
287  if (outPaths) {
288  outPaths->push_back(std::move(fullPath));
289  }
290  return true;
291  });
292  return retFDs;
293 }
294 
296  const std::filesystem::path &pathOrig,
297  const std::function<bool(int)> &callback) const {
298  FCITX_D();
299  auto [file, path, fullPathOrig] = d->openUserTemp(type, pathOrig);
300  if (!file.isValid()) {
301  return false;
302  }
303  try {
304  if (callback(file.fd())) {
305  // sync first.
306 #ifdef _WIN32
307  ::_wchmod(path.c_str(), 0666 & ~(d->umask()));
308  _commit(file.fd());
309 #else
310  // close it
311  fchmod(file.fd(), 0666 & ~(d->umask()));
312  fsync(file.fd());
313 #endif
314  file.reset();
315  std::filesystem::rename(path, fullPathOrig);
316  return true;
317  }
318  } catch (const std::exception &e) {
319  FCITX_ERROR() << "Failed to write file: " << fullPathOrig << e.what();
320  }
321 #ifdef _WIN32
322  _wunlink(path.c_str());
323 #else
324  unlink(path.c_str());
325 #endif
326  return false;
327 }
328 
329 int64_t StandardPaths::timestamp(StandardPathsType type,
330  const std::filesystem::path &path,
331  StandardPathsModes modes) const {
332  FCITX_D();
333 
334  int64_t timestamp = 0;
335  d->scanDirectories(type, path, modes,
336  [&timestamp](const std::filesystem::path &fullPath) {
337  const auto time = fs::modifiedTime(fullPath);
338  timestamp = std::max(timestamp, time);
339  return true;
340  });
341 
342  return timestamp;
343 }
344 
345 void StandardPaths::syncUmask() const { d_ptr->syncUmask(); }
346 
348  FCITX_D();
349  return d->options() & StandardPathsOption::SkipBuiltInPath;
350 }
351 
353  FCITX_D();
354  return d->options() & StandardPathsOption::SkipUserPath;
355 }
356 
358  FCITX_D();
359  return d->options() & StandardPathsOption::SkipSystemPath;
360 }
361 
363  FCITX_D();
364  return d->options();
365 }
366 
367 } // namespace fcitx
Class wrap around the unix fd.
Definition: unixfd.h:22
std::vector< UnixFD > openAll(StandardPathsType type, const std::filesystem::path &path, StandardPathsModes modes=StandardPathsMode::Default, std::vector< std::filesystem::path > *outPath=nullptr) const
Open the all matched and file for read.
static const StandardPaths & global()
Return the global instance of StandardPath.
bool isValid() const noexcept
Check if fd is not empty.
Definition: unixfd.cpp:39
std::vector< std::filesystem::path > locateAll(StandardPathsType type, const std::filesystem::path &path, StandardPathsModes modes=StandardPathsMode::Default) const
Check if path exists in all directories.
bool skipSystemPath() const
Whether this StandardPath is configured to Skip system path.
StandardPathsType
Enum for location type.
Definition: standardpaths.h:41
Simple file system related API for checking file status.
bool isexe(const std::string &path)
check whether path is an executable regular file.
Definition: fs.cpp:88
Definition: action.cpp:17
bool skipBuiltInPath() const
Whether this StandardPath is configured to Skip built-in path.
Definition: matchrule.h:78
bool safeSave(StandardPathsType type, const std::filesystem::path &pathOrig, const std::function< bool(int)> &callback) const
Save the file safely with write and rename to make sure the operation is atomic.
std::vector< std::string > split(std::string_view str, std::string_view delim, SplitBehavior behavior)
Split the string by delim.
std::span< const std::filesystem::path > directories(StandardPathsType type, StandardPathsModes modes=StandardPathsMode::Default) const
Get all directories in the order of priority.
StandardPathsOptions options() const
Get the options for the StandardPaths.
Utility class to handle unix file decriptor.
void syncUmask() const
Sync system umask to internal state.
bool skipUserPath() const
Whether this StandardPath is configured to Skip user path.
New Utility classes to handle application specific path.
UnixFD open(StandardPathsType type, const std::filesystem::path &path, StandardPathsModes modes=StandardPathsMode::Default, std::filesystem::path *outPath=nullptr) const
Open the first matched and succeeded file for read.
static UnixFD own(int fd)
Create a UnixFD by owning the fd.
Definition: unixfd.h:42
static UnixFD openPath(const std::filesystem::path &path, std::optional< int > flags=std::nullopt, std::optional< mode_t > mode=std::nullopt)
Open the path.
StandardPaths(const std::string &packageName, const std::unordered_map< std::string, std::vector< std::filesystem::path >> &builtInPath, StandardPathsOptions options)
Allow to construct a StandardPath with customized internal value.
Class provides bit flag support for Enum.
Definition: flags.h:33
String handle utilities.
int64_t modifiedTime(const std::filesystem::path &path)
Return modified time in seconds of given path.
Definition: fs.cpp:271
const std::filesystem::path & userDirectory(StandardPathsType type) const
Get user writable directory for given type.
static std::filesystem::path fcitxPath(const char *path, const std::filesystem::path &subPath={})
Return fcitx specific path defined at compile time.
Log utilities.
std::filesystem::path locate(StandardPathsType type, const std::filesystem::path &path, StandardPathsModes modes=StandardPathsMode::Default) const
Check if a file exists.