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