Fcitx
fs.cpp
1 /*
2  * SPDX-FileCopyrightText: 2016-2016 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 
8 #include "fs.h"
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 #include <algorithm>
13 #include <cerrno>
14 #include <chrono>
15 #include <cstddef>
16 #include <cstdint>
17 #include <filesystem>
18 #include <iterator>
19 #include <optional>
20 #include <string>
21 #include <string_view>
22 #include <system_error>
23 #include "misc.h"
24 #include "unixfd.h"
25 #include "utf8.h" // IWYU pragma: keep
26 
27 #ifdef _WIN32
28 #include <wchar.h>
29 #endif
30 
31 namespace fcitx::fs {
32 
33 namespace {
34 
35 int makeDir(const std::filesystem::path &name) {
36 #ifdef _WIN32
37  return ::_wmkdir(name.c_str());
38 #else
39  return ::mkdir(name.c_str(), 0777);
40 #endif
41 }
42 
43 bool makePathHelper(const std::filesystem::path &name) {
44  if (makeDir(name) == 0) {
45  return true;
46  }
47  if (errno == EEXIST) {
48  std::error_code ec;
49  return std::filesystem::is_directory(name, ec);
50  }
51 
52  // Check if error is parent not exists.
53  if (errno != ENOENT) {
54  return false;
55  }
56 
57  if (!name.has_parent_path()) {
58  return false;
59  }
60 
61  const auto parent = name.parent_path();
62  if (!makePathHelper(parent)) {
63  return false;
64  }
65 
66  // try again
67  if (makeDir(name) == 0) {
68  return true;
69  }
70  return errno == EEXIST && std::filesystem::is_directory(name);
71 }
72 
73 } // namespace
74 
75 bool isdir(const std::string &path) {
76  struct stat stats;
77  return (stat(path.c_str(), &stats) == 0 && S_ISDIR(stats.st_mode) &&
78  access(path.c_str(), R_OK | X_OK) == 0);
79 }
80 
81 bool isreg(const std::string &path) {
82  struct stat stats;
83  return (stat(path.c_str(), &stats) == 0 && S_ISREG(stats.st_mode) &&
84  access(path.c_str(), R_OK) == 0);
85 }
86 
87 bool isexe(const std::string &path) {
88  struct stat stats;
89  return (stat(path.c_str(), &stats) == 0 && S_ISREG(stats.st_mode) &&
90  access(path.c_str(), R_OK | X_OK) == 0);
91 }
92 
93 bool islnk(const std::string &path) {
94 #ifdef _WIN32
95  FCITX_UNUSED(path);
96  return false;
97 #else
98  struct stat stats;
99  return lstat(path.c_str(), &stats) == 0 && S_ISLNK(stats.st_mode);
100 #endif
101 }
102 
103 std::string cleanPath(const std::string &path) {
104  std::string buf;
105  if (path.empty()) {
106  return {};
107  }
108 
109  // skip first group of continuous slash, for possible future windows support
110  size_t i = 0;
111  while (path[i] == '/') {
112  buf.push_back(path[i]);
113  i++;
114  }
115  const size_t leading = i;
116 
117  int levels = 0;
118  while (true) {
119  size_t dotcount = 0;
120  const size_t last = buf.size();
121  const size_t lasti = i;
122  // We have something already in the path, push a new slash.
123  if (last > leading) {
124  buf.push_back('/');
125  }
126  // Find still next '/' and count '.'
127  while (i < path.size() && path[i] != '/') {
128  if (path[i] == '.') {
129  dotcount++;
130  }
131 
132  buf.push_back(path[i]);
133 
134  i++;
135  }
136 
137  // everything is a dot
138  if (dotcount == i - lasti) {
139  if (dotcount == 1) {
140  buf.erase(last);
141  } else if (dotcount == 2) {
142  // If we already at the beginning, don't go up.
143  if (levels > 0 && last != leading) {
144  size_t k;
145  for (k = last; k > leading; k--) {
146  if (buf[k - 1] == '/') {
147  break;
148  }
149  }
150  if (k == leading) {
151  buf.erase(k);
152  } else if (buf[k - 1] == '/') {
153  buf.erase(k - 1);
154  }
155  levels--;
156  }
157  } else {
158  levels++;
159  }
160  } else {
161  levels++;
162  }
163 
164  while (i < path.size() && path[i] == '/') {
165  i++;
166  }
167 
168  if (i >= path.size()) {
169  break;
170  }
171  }
172  if (buf.starts_with("./")) {
173  return buf.substr(2);
174  }
175  return buf;
176 }
177 
178 bool makePath(const std::filesystem::path &path) {
179  std::error_code ec;
180  if (std::filesystem::is_directory(path, ec)) {
181  return true;
182  }
183  auto opath = path.lexically_normal();
184  if (opath.empty()) {
185  return true;
186  }
187 
188  return makePathHelper(opath);
189 }
190 
191 std::string dirName(const std::string &path) {
192  auto result = path;
193  // remove trailing slash
194  while (result.size() > 1 && result.back() == '/') {
195  result.pop_back();
196  }
197  if (result.size() <= 1) {
198  return result;
199  }
200 
201  auto iter = std::find(result.rbegin(), result.rend(), '/');
202  if (iter != result.rend()) {
203  result.erase(iter.base(), result.end());
204  // remove trailing slash
205  while (result.size() > 1 && result.back() == '/') {
206  result.pop_back();
207  }
208  } else {
209  result = ".";
210  }
211  return result;
212 }
213 
214 std::string baseName(std::string_view path) {
215  // remove trailing slash
216  while (path.size() > 1 && path.back() == '/') {
217  path.remove_suffix(1);
218  }
219  if (path.size() <= 1) {
220  return std::string{path};
221  }
222 
223  auto iter = std::find(path.rbegin(), path.rend(), '/');
224  if (iter != path.rend()) {
225  path.remove_prefix(std::distance(path.begin(), iter.base()));
226  }
227  return std::string{path};
228 }
229 
230 ssize_t safeRead(int fd, void *data, size_t maxlen) {
231  ssize_t ret = 0;
232  do {
233  ret = read(fd, data, maxlen);
234  } while (ret == -1 && errno == EINTR);
235  return ret;
236 }
237 ssize_t safeWrite(int fd, const void *data, size_t maxlen) {
238  ssize_t ret = 0;
239  do {
240  ret = write(fd, data, maxlen);
241  } while (ret == -1 && errno == EINTR);
242  return ret;
243 }
244 
245 std::optional<std::string> readlink(const std::string &path) {
246 #ifdef _WIN32
247  FCITX_UNUSED(path);
248 #else
249  std::string buffer;
250  buffer.resize(256);
251  ssize_t readSize;
252 
253  while (true) {
254  readSize = ::readlink(path.data(), buffer.data(), buffer.size());
255  if (readSize < 0) {
256  return std::nullopt;
257  }
258 
259  if (static_cast<size_t>(readSize) < buffer.size()) {
260  buffer.resize(readSize);
261  return buffer;
262  }
263 
264  buffer.resize(buffer.size() * 2);
265  }
266 #endif
267  return std::nullopt;
268 }
269 
270 int64_t modifiedTime(const std::filesystem::path &path) {
271  std::error_code ec;
272  auto time = std::filesystem::last_write_time(path, ec);
273  auto systime =
274  !ec ? std::filesystem::file_time_type::clock::to_sys(time)
275  : std::chrono::time_point<std::chrono::system_clock>::min();
276  auto timeInSeconds =
277  std::chrono::time_point_cast<std::chrono::seconds>(systime);
278  return timeInSeconds.time_since_epoch().count();
279 }
280 
281 UniqueFilePtr openFD(UnixFD &fd, const char *modes) {
282  if (!fd.isValid()) {
283  return nullptr;
284  }
285  UniqueFilePtr file(fdopen(fd.fd(), modes));
286  if (file) {
287  fd.release();
288  }
289  return file;
290 }
291 
292 } // namespace fcitx::fs
Class wrap around the unix fd.
Definition: unixfd.h:22
std::string baseName(std::string_view path)
Get base file name of path.
Definition: fs.cpp:214
bool isValid() const noexcept
Check if fd is not empty.
Definition: unixfd.cpp:39
bool islnk(const std::string &path)
check whether path is a link.
Definition: fs.cpp:93
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:87
bool isreg(const std::string &path)
check whether path is a regular file.
Definition: fs.cpp:81
bool isdir(const std::string &path)
check whether path is a directory.
Definition: fs.cpp:75
Definition: fs.cpp:31
C++ Utility functions for handling utf8 strings.
Utility class to handle unix file descriptor.
int fd() const noexcept
Get the internal fd.
Definition: unixfd.cpp:41
ssize_t safeWrite(int fd, const void *data, size_t maxlen)
a simple wrapper around write(), ignore EINTR.
Definition: fs.cpp:237
std::string dirName(const std::string &path)
Get directory name of path.
Definition: fs.cpp:191
std::optional< std::string > readlink(const std::string &path)
read symlink.
Definition: fs.cpp:245
bool makePath(const std::filesystem::path &path)
Create directory recursively.
Definition: fs.cpp:178
UniqueFilePtr openFD(UnixFD &fd, const char *modes)
open the unix fd with fdopen.
Definition: fs.cpp:281
int64_t modifiedTime(const std::filesystem::path &path)
Return modified time in seconds of given path.
Definition: fs.cpp:270
ssize_t safeRead(int fd, void *data, size_t maxlen)
a simple wrapper around read(), ignore EINTR.
Definition: fs.cpp:230
std::string cleanPath(const std::string &path)
Get the clean path by removing . , .. , and duplicate / in the path.
Definition: fs.cpp:103
int release() noexcept
Get the internal fd and release the ownership.
Definition: unixfd.cpp:81