filesystem.cpp
Go to the documentation of this file.
1#include "filesystem.h"
2
3#include <errno.h>
4
5#include <filesystem>
6#include <fstream>
7#include <iostream>
8#include <stdexcept>
9#include <string>
10#include <system_error>
11#include <vector>
12
13#include <fcntl.h>
14#include <sys/stat.h>
15#include <unistd.h>
16
17#include <SimoxUtility/algorithm/string.h>
18
20
23
25{
26
27 namespace detail
28 {
29 std::string
30 escapeName(const std::string& segmentName)
31 {
32 std::string ret = segmentName;
33 //simox::alg::replace_all(ret, Prefix, PrefixEscaped);
34 for (const auto& [s, r] : EscapeTable)
35 {
36 ret = simox::alg::replace_all(ret, s, r);
37 }
38 return ret;
39 }
40
41 std::string
42 unescapeName(const std::string& escapedName)
43 {
44 std::string ret = escapedName;
45 for (
46 const auto& [s, r] :
47 EscapeTable) // Here we assume that noone uses the replaced char usually in the segment name... TODO
48 {
49 ret = simox::alg::replace_all(ret, r, s);
50 }
51 return ret;
52 }
53
54 std::string
55 extractLastDirectoryFromPath(const std::string& path)
56 {
57 size_t pos = path.rfind('/');
58
59 if (pos != std::string::npos)
60 {
61 return path.substr(pos + 1);
62 }
63 else
64 {
65 return path;
66 }
67 }
68
69 std::string
70 toDayString(const Time& t)
71 {
72 return t.toDateString();
73 }
74
75 std::string
77 {
78 return std::to_string(t.toSecondsSinceEpoch());
79 }
80
81 bool
82 isNumberString(const std::string& s)
83 {
84 for (char const& ch : s)
85 {
86 if (std::isdigit(ch) == 0)
87 {
88 return false;
89 }
90 }
91 return true;
92 }
93
94 bool
95 isDateString(const std::string& s)
96 {
97 auto split = simox::alg::split(s, "-");
98 if (split.size() != 3)
99 {
100 return false;
101 }
102
104 }
105 } // namespace detail
106
107 std::filesystem::path
108 toPath(const std::filesystem::path& base, const armem::MemoryID& id)
109 {
110 ARMARX_CHECK(id.isWellDefined());
111
112 std::filesystem::path p = base;
113 if (id.hasMemoryName())
114 {
115 p /= detail::escapeName(id.memoryName);
116 }
117 if (id.hasCoreSegmentName())
118 {
119 p /= detail::escapeName(id.coreSegmentName);
120 }
121 if (id.hasProviderSegmentName())
122 {
123 p /= detail::escapeName(id.providerSegmentName);
124 }
125 if (id.hasEntityName())
126 {
127 p /= detail::escapeName(id.entityName);
128 }
129 if (id.hasTimestamp())
130 {
133 p /= id.timestampStr();
134 }
135 if (id.hasInstanceIndex())
136 {
137 p /= id.instanceIndexStr();
138 }
139
140 return p;
141 }
142
143 bool
144 directoryExists(const std::filesystem::path& p)
145 {
146 return std::filesystem::exists(p) and std::filesystem::is_directory(p);
147 }
148
149 bool
150 fileExists(const std::filesystem::path& p)
151 {
152 return std::filesystem::exists(p) && std::filesystem::is_regular_file(p);
153 }
154
155 void
156 ensureDirectoryExists(const std::filesystem::path& p, bool createIfNotExistent)
157 {
158 if (createIfNotExistent)
159 {
160 // Use error_code version to avoid exceptions for already-exists case
161 std::error_code ec;
162 std::filesystem::create_directories(p, ec);
163
164 // Only error if creation failed AND directory still doesn't exist
165 if (ec && !std::filesystem::is_directory(p))
166 {
167 throw armarx::LocalException("Failed to create directory '" + p.string() +
168 "': " + ec.message() + " (code: " +
169 std::to_string(ec.value()) + ")");
170 }
171 // If directory exists now (either we created it or someone else did), success!
172 }
173 else if (!directoryExists(p))
174 {
175 throw armarx::LocalException("Directory existence cannot be ensured: " + p.string());
176 }
177 }
178
179 void
180 ensureFileExists(const std::filesystem::path& p, bool createIfNotExistent)
181 {
182 ensureDirectoryExists(p.parent_path(), createIfNotExistent);
183
184 // Don't create the file here - just verify parent directory exists
185 // The actual file will be created by writeDataToFile
186
187 if (!createIfNotExistent && !fileExists(p))
188 {
189 throw armarx::LocalException("File existence cannot be ensured: " + p.string());
190 }
191 }
192
193 void
194 writeDataToFile(const std::filesystem::path& p,
195 const std::vector<unsigned char>& data,
196 bool write_atomic)
197 {
198 namespace fs = std::filesystem;
199
200 if (!write_atomic)
201 {
202 std::ofstream dataofs;
203 dataofs.open(p);
204 if (!dataofs)
205 {
206 throw armarx::LocalException("Could not write data to filesystem file '" +
207 p.string() + "'. Skipping this file.");
208 }
209 dataofs.write(reinterpret_cast<const char*>(data.data()), data.size());
210 dataofs.close();
211 return;
212 }
213
214 const fs::path dir = p.parent_path().empty() ? fs::current_path() : p.parent_path();
215 const std::string filename = p.filename().string();
216
217 // mkstemp template must end with XXXXXX and be mutable
218 fs::path tmpl = dir / (filename + ".tmpXXXXXX");
219 std::string tmpl_str = tmpl.string();
220
221 // 1) Create a unique temp file in the same directory
222 int fd = ::mkstemp(tmpl_str.data());
223 if (fd == -1)
224 {
225 throw std::system_error(errno, std::generic_category(), "mkstemp failed");
226 }
227
228 // 2) Write the whole buffer
229 const unsigned char* buf = data.data();
230 size_t left = data.size();
231 while (left > 0)
232 {
233 ssize_t n = ::write(fd, buf, left);
234 if (n < 0)
235 {
236 int e = errno;
237 ::close(fd);
238 ::unlink(tmpl_str.c_str());
239 throw std::system_error(e, std::generic_category(), "write failed");
240 }
241 buf += n;
242 left -= static_cast<size_t>(n);
243 }
244
245 // 3) Flush file contents to disk
246 if (::fdatasync(fd) != 0)
247 {
248 int e = errno;
249 ::close(fd);
250 ::unlink(tmpl_str.c_str());
251 throw std::system_error(e, std::generic_category(), "fdatasync failed");
252 }
253
254 if (::close(fd) != 0)
255 {
256 int e = errno;
257 ::unlink(tmpl_str.c_str());
258 throw std::system_error(e, std::generic_category(), "close failed");
259 }
260
261 // 4) Atomically replace the destination
262 if (::rename(tmpl_str.c_str(), p.c_str()) != 0)
263 {
264 int e = errno;
265 ::unlink(tmpl_str.c_str());
266 throw std::system_error(e, std::generic_category(), "rename failed");
267 }
268
269 // 5) Make the rename durable by syncing the directory
270 int dfd = ::open(dir.c_str(), O_DIRECTORY | O_RDONLY);
271 if (dfd >= 0)
272 {
273 (void)::fsync(dfd);
274 (void)::close(dfd);
275 }
276 }
277
278 std::vector<unsigned char>
279 readDataFromFile(const std::filesystem::path& p)
280 {
281
282 if (!std::filesystem::exists(p))
283 {
284 throw std::runtime_error("File not found: " + p.string());
285 }
286
287 std::ifstream dataifs(p);
288
289 if (!dataifs)
290 {
291 throw std::runtime_error("Could not open file: " + p.string());
292 }
293
294 std::vector<unsigned char> datafilecontent((std::istreambuf_iterator<char>(dataifs)),
295 (std::istreambuf_iterator<char>()));
296
297
298 if (dataifs.bad())
299 {
300 throw std::runtime_error("Error reading file: " + p.string());
301 }
302
303 dataifs.close();
304 return datafilecontent;
305 }
306
307 std::vector<std::filesystem::path>
308 getAllDirectories(const std::filesystem::path& p)
309 {
310 std::vector<std::filesystem::path> ret;
311 for (const auto& subdir : std::filesystem::directory_iterator(p))
312 {
313 std::filesystem::path subdirPath = subdir.path();
314 if (std::filesystem::is_directory(subdirPath))
315 {
316 ret.push_back(subdirPath);
317 }
318 }
319 std::sort(ret.begin(),
320 ret.end(),
321 [](const std::filesystem::path& a, const std::filesystem::path& b) -> bool
322 { return a.string() < b.string(); });
323 return ret;
324 }
325
326 std::vector<std::filesystem::path>
327 getAllFiles(const std::filesystem::path& p)
328 {
329 std::vector<std::filesystem::path> ret;
330 for (const auto& subdir : std::filesystem::directory_iterator(p))
331 {
332 std::filesystem::path subdirPath = subdir.path();
333 if (std::filesystem::is_regular_file(subdirPath))
334 {
335 ret.push_back(subdirPath);
336 }
337 }
338 std::sort(ret.begin(),
339 ret.end(),
340 [](const std::filesystem::path& a, const std::filesystem::path& b) -> bool
341 { return a.string() > b.string(); });
342 return ret;
343 }
344
345 bool
346 hasWritePermission(const std::filesystem::path& p)
347 {
348 namespace fs = std::filesystem;
349
350 if (!fs::exists(p))
351 {
352 return false; // Can't write to non-existent directory
353 }
354
355 if (!fs::is_directory(p))
356 {
357 return false; // Not a directory
358 }
359
360 // Try to get permissions
361 std::error_code ec;
362 auto perms = fs::status(p, ec).permissions();
363
364 if (ec)
365 {
366 return false; // Can't determine permissions
367 }
368
369 // Check for owner write permission
370 // Note: This is a basic check, doesn't account for group/other or ACLs
371 using fs::perms;
372 return (perms & perms::owner_write) != perms::none;
373 }
374
375 bool
376 canCreateFiles(const std::filesystem::path& dir)
377 {
378 namespace fs = std::filesystem;
379
380 if (!fs::exists(dir) || !fs::is_directory(dir))
381 {
382 return false;
383 }
384
385 // Try to create a temp file
386 fs::path testFile = dir / ".ltm_write_test_XXXXXX";
387 std::string testPath = testFile.string();
388
389 int fd = ::mkstemp(testPath.data());
390 if (fd == -1)
391 {
392 return false; // Can't create files
393 }
394
395 ::close(fd);
396 ::unlink(testPath.c_str());
397 return true;
398 }
399} // namespace armarx::armem::server::ltm::util::fs
std::string timestamp()
std::int64_t toSecondsSinceEpoch() const
Definition DateTime.cpp:99
std::string toDateString() const
Definition DateTime.cpp:63
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
std::string unescapeName(const std::string &escapedName)
std::string escapeName(const std::string &segmentName)
std::string toSecondsString(const Time &t)
std::string extractLastDirectoryFromPath(const std::string &path)
bool isNumberString(const std::string &s)
std::vector< unsigned char > readDataFromFile(const std::filesystem::path &p)
bool directoryExists(const std::filesystem::path &p)
std::vector< std::filesystem::path > getAllFiles(const std::filesystem::path &p)
void ensureDirectoryExists(const std::filesystem::path &p, bool createIfNotExistent)
bool canCreateFiles(const std::filesystem::path &dir)
std::filesystem::path toPath(const std::filesystem::path &base, const armem::MemoryID &id)
bool hasWritePermission(const std::filesystem::path &p)
void writeDataToFile(const std::filesystem::path &p, const std::vector< unsigned char > &data, bool write_atomic)
void ensureFileExists(const std::filesystem::path &p, bool createIfNotExistent)
bool fileExists(const std::filesystem::path &p)
std::vector< std::filesystem::path > getAllDirectories(const std::filesystem::path &p)
armarx::core::time::DateTime Time
std::vector< std::string > split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)