MDBX Containers
Loading...
Searching...
No Matches
path_utils.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef _MDBX_CONTAINERS_PATH_UTILS_HPP_INCLUDED
3#define _MDBX_CONTAINERS_PATH_UTILS_HPP_INCLUDED
4
8
9#include <string>
10#include <vector>
11#include <stdexcept>
12#if __cplusplus >= 201703L
13#include <filesystem>
14#else
15#include <cctype>
16#endif
17
18#ifdef _WIN32
19// For Windows systems
20#include <direct.h>
21#include <windows.h>
22#if __cplusplus < 202002L
23#include <locale>
24#include <codecvt>
25#endif
26#include <errno.h>
27#else
28// For POSIX systems
29#include <unistd.h>
30#include <limits.h>
31#include <dirent.h>
32#include <sys/stat.h>
33#include <errno.h>
34#endif
35
36namespace mdbxc {
37# if __cplusplus >= 201703L
38 namespace fs = std::filesystem;
39# endif
40
41#if __cplusplus >= 202002L
43 inline std::string u8string_to_string(const std::u8string& s) {
44 return std::string(s.begin(), s.end());
45 }
46#endif
47
51 inline bool is_explicitly_relative(const std::string& s) noexcept {
52 auto sw = [&](const char* p){ return s.rfind(p, 0) == 0; };
53 return sw("./") || sw("../") || sw(".\\") || sw("..\\");
54 }
55
59 inline bool is_absolute_path(const std::string& path) {
60# if __cplusplus >= 201703L
61 return fs::u8path(path).is_absolute();
62# else
63# ifdef _WIN32
64 // On Windows: absolute path starts with drive letter or UNC path (\\‍)
65 return (path.size() >= 2 && std::isalpha(path[0]) && path[1] == ':') ||
66 (path.size() >= 2 && path[0] == '\\' && path[1] == '\\') ||
67 (path.size() >= 2 && path[0] == '/' && path[1] == '/');
68# else
69 // On POSIX: absolute path starts with /
70 return !path.empty() && path[0] == '/';
71# endif
72# endif
73 }
74
78 inline std::string get_parent_path(const std::string& file_path) {
79# if __cplusplus >= 201703L
80# if __cplusplus >= 202002L
81 auto parent = fs::u8path(file_path).parent_path().u8string();
82 return u8string_to_string(parent);
83# else
84 return fs::u8path(file_path).parent_path().u8string();
85# endif
86# else
87 size_t pos = file_path.find_last_of("/\\");
88 if (pos == std::string::npos)
89 return "."; // current dir
90 return file_path.substr(0, pos);
91# endif
92 }
93
96 inline std::string get_exec_dir() {
97# ifdef _WIN32
98 std::vector<wchar_t> buffer(MAX_PATH);
99 HMODULE hModule = GetModuleHandle(NULL);
100
101 // Try to get the path
102 std::size_t size = static_cast<std::size_t>(GetModuleFileNameW(hModule, buffer.data(), static_cast<DWORD>(buffer.size())));
103
104 // If the path is too long, increase the buffer size
105 while (size == buffer.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
106 buffer.resize(buffer.size() * 2); // Double the buffer size
107 size = static_cast<std::size_t>(GetModuleFileNameW(hModule, buffer.data(), static_cast<DWORD>(buffer.size())));
108 }
109
110 if (size == 0) {
111 throw std::runtime_error("Failed to get executable path.");
112 }
113
114 std::wstring exe_path(buffer.begin(), buffer.begin() + size);
115
116 // Trim the path to the directory (remove the file name, keep only the folder path)
117 size_t pos = exe_path.find_last_of(L"\\/");
118 if (pos != std::wstring::npos) {
119 exe_path = exe_path.substr(0, pos);
120 }
121
122# if __cplusplus >= 202002L
123 fs::path path_wide = exe_path;
124 auto tmp = path_wide.u8string();
125 return u8string_to_string(tmp);
126# else
127 // Convert from std::wstring (UTF-16) to std::string (UTF-8)
128 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
129 return converter.to_bytes(exe_path);
130# endif
131
132# else
133 char result[PATH_MAX];
134 ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
135
136 if (count == -1) {
137 throw std::runtime_error("Failed to get executable path.");
138 }
139
140 std::string exe_path(result, count);
141
142 // Trim the path to the directory (remove the file name, keep only the folder path)
143 size_t pos = exe_path.find_last_of("\\/");
144 if (pos != std::string::npos) {
145 exe_path = exe_path.substr(0, pos);
146 }
147
148 return exe_path;
149# endif
150 }
151
155 inline std::string get_file_name(const std::string& file_path) {
156# if __cplusplus >= 201703L
157# if __cplusplus >= 202002L
158 auto name = fs::u8path(file_path).filename().u8string();
159 return u8string_to_string(name);
160# else
161 return fs::u8path(file_path).filename().u8string();
162# endif
163# else
164 size_t pos = file_path.find_last_of("/\\");
165 if (pos == std::string::npos) return file_path;
166 return file_path.substr(pos + 1);
167# endif
168 }
169
173 inline std::string utf8_to_ansi(const std::string& utf8) noexcept {
174#ifdef _WIN32
175 int n_len = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0);
176 if (n_len == 0) return {};
177
178 std::wstring wide_string(n_len + 1, L'\0');
179 MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wide_string[0], n_len);
180
181 n_len = WideCharToMultiByte(CP_ACP, 0, wide_string.c_str(), -1, NULL, 0, NULL, NULL);
182 if (n_len == 0) return {};
183
184 std::string ansi_string(n_len - 1, '\0');
185 WideCharToMultiByte(CP_ACP, 0, wide_string.c_str(), -1, &ansi_string[0], n_len, NULL, NULL);
186 return ansi_string;
187#else
188 return utf8;
189#endif
190 }
191
192#if __cplusplus >= 201703L
193
198 inline std::string make_relative(const std::string& file_path, const std::string& base_path) {
199 if (base_path.empty()) return file_path;
200 fs::path fileP = fs::u8path(file_path);
201 fs::path baseP = fs::u8path(base_path);
202 std::error_code ec; // For exception-safe operation
203 fs::path relativeP = fs::relative(fileP, baseP, ec);
204 if (ec) {
205 // If there is an error, return the original file_path
206 return file_path;
207 } else {
208# if __cplusplus >= 202002L
209 return u8string_to_string(relativeP.u8string());
210# else
211 return relativeP.u8string();
212# endif
213 }
214 }
215
219 inline void create_directories(const std::string& path) {
220# ifdef _WIN32
221# if __cplusplus >= 202002L
222 fs::path parent_dir = fs::u8path(get_parent_path(path));
223# else
224 // Convert UTF-8 string to wide string for Windows
225 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
226 std::wstring wide_path = converter.from_bytes(get_parent_path(path));
227 fs::path parent_dir = fs::path(wide_path);
228# endif
229# else
230 fs::path parent_dir = fs::u8path(get_parent_path(path));
231# endif
232 if (parent_dir.empty()) parent_dir = fs::current_path();
233 if (!fs::exists(parent_dir)) {
234 std::error_code ec;
235 if (!std::filesystem::create_directories(parent_dir, ec)) {
236# if __cplusplus >= 202002L
237 auto p = parent_dir.u8string();
238 throw std::runtime_error("Failed to create directories for path: " + u8string_to_string(p));
239# else
240 throw std::runtime_error("Failed to create directories for path: " + parent_dir.u8string());
241# endif
242 }
243 }
244 }
245
246#else
247 // С++11/14
248
252 inline bool is_path_sep(char c) {
253 return c == '/' || c == '\\';
254 }
255
259 inline std::string lexically_normal_compat(const std::string& in) {
260# ifdef _WIN32
261 const char SEP = '\\';
262# else
263 const char SEP = '/';
264# endif
265 const char* s = in.c_str();
266 const size_t n = in.size();
267 size_t i = 0;
268
269 // ----- parse prefix (root) -----
270# ifdef _WIN32
271 bool is_unc = false;
272 std::string drive; // "C:"
273 std::vector<std::string> root_parts; // for UNC: server, share
274
275 // UNC: \\server\share\...
276 if (n >= 2 && is_path_sep(s[0]) && is_path_sep(s[1])) {
277 is_unc = true;
278 i = 2;
279 // server
280 size_t start = i;
281 while (i < n && !is_path_sep(s[i])) ++i;
282 if (i > start) root_parts.push_back(in.substr(start, i - start));
283 if (i < n && is_path_sep(s[i])) ++i;
284 // share
285 start = i;
286 while (i < n && !is_path_sep(s[i])) ++i;
287 if (i > start) root_parts.push_back(in.substr(start, i - start));
288 // пропускаем дополнительные слеши
289 while (i < n && is_path_sep(s[i])) ++i;
290 }
291 // Drive: "X:" (возможно абсолютный "X:\..." или относительный "X:foo")
292 else if (n >= 2 && std::isalpha(static_cast<unsigned char>(s[0])) && s[1] == ':') {
293 drive.assign(in, 0, 2);
294 i = 2;
295 // пропустим один/несколько разделителей, если есть
296 while (i < n && is_path_sep(s[i])) ++i;
297 }
298 const bool is_abs_root = is_unc || (!drive.empty() && (n >= 3 && is_path_sep(s[2]))) ||
299 (!drive.size() && n >= 1 && is_path_sep(s[0]));
300# else
301 const bool is_abs_root = (n >= 1 && s[0] == '/');
302 if (is_abs_root) i = 1;
303# endif
304
305 // ----- tokenize and fold . / .. -----
306 std::vector<std::string> comps;
307
308 auto push_component = [&](const std::string& token) {
309 if (token.empty() || token == ".") return;
310 if (token == "..") {
311# ifdef _WIN32
312 // Для UNC «корень» — server/share: их нельзя выталкивать назад.
313 size_t protected_root = 0;
314 if (
315 // UNC и есть server/share
316 true
317# ifdef _WIN32
318 && false
319# endif
320 ) { /* защищённая зона настроена ниже */ }
321# endif
322 if (!comps.empty() && comps.back() != "..") {
323 // нельзя уходить выше корня абсолютного пути
324 comps.pop_back();
325 } else {
326 // относительный путь или уже нет куда сворачивать
327 comps.push_back("..");
328 }
329 } else {
330 comps.push_back(token);
331 }
332 };
333
334# ifdef _WIN32
335 // Для UNC защищаем первые два компонента (server/share) от свёртки ".."
336 size_t protected_count = 0;
337 if (is_unc) {
338 for (size_t k = 0; k < root_parts.size(); ++k) {
339 comps.push_back(root_parts[k]);
340 }
341 protected_count = comps.size();
342 }
343# endif
344
345 while (i < n) {
346 // пропускаем повторные разделители
347 while (i < n && is_path_sep(s[i])) ++i;
348 if (i >= n) break;
349 size_t start = i;
350 while (i < n && !is_path_sep(s[i])) ++i;
351 std::string token = in.substr(start, i - start);
352
353 if (token == "..") {
354# ifdef _WIN32
355 if (!comps.empty() && comps.size() > protected_count && comps.back() != "..") {
356 comps.pop_back();
357 } else if (!is_abs_root || (is_unc && comps.size() > protected_count)) {
358 comps.push_back("..");
359 }
360# else
361 if (is_abs_root) {
362 if (!comps.empty() && comps.back() != "..") comps.pop_back();
363 } else {
364 comps.push_back("..");
365 }
366# endif
367 } else if (token != ".") {
368 comps.push_back(token);
369 }
370 }
371
372 // ----- rebuild -----
373 std::string out;
374
375# ifdef _WIN32
376 if (is_unc) {
377 out = "\\\\";
378 for (size_t k = 0; k < comps.size(); ++k) {
379 if (k) out += SEP;
380 out += comps[k];
381 }
382 return out;
383 }
384 if (!drive.empty()) {
385 // Абсолютный drive-root → "X:\", drive-relative → "X:"
386 bool drive_absolute = (n >= 3 && std::isalpha(static_cast<unsigned char>(s[0])) && s[1]==':' && is_path_sep(s[2]));
387 out = drive;
388 if (drive_absolute) out += SEP;
389 } else if (is_abs_root) {
390 out.push_back(SEP);
391 }
392# else
393 if (is_abs_root) out.push_back(SEP);
394# endif
395
396 for (size_t k = 0; k < comps.size(); ++k) {
397 if (!out.empty() && out.back() != SEP) out.push_back(SEP);
398 out += comps[k];
399 }
400
401 if (out.empty()) {
402# ifdef _WIN32
403 out = !drive.empty() ? drive : std::string(".");
404# else
405 out = is_abs_root ? std::string(1, SEP) : std::string(".");
406# endif
407 }
408 return out;
409 }
410
414 std::string root;
415 std::vector<std::string> components;
416 };
417
421 inline PathComponents split_path(const std::string& path) {
422 PathComponents result;
423 size_t i = 0;
424 size_t n = path.size();
425
426 // Handle root paths for Unix and Windows
427 if (n >= 1 && (path[0] == '/' || path[0] == '\\')) {
428 // Unix root "/"
429 result.root = "/";
430 ++i;
431 } else if (n >= 2 && std::isalpha(path[0]) && path[1] == ':') {
432 // Windows drive letter "C:"
433 result.root = path.substr(0, 2);
434 i = 2;
435 if (n >= 3 && (path[2] == '/' || path[2] == '\\')) {
436 // "C:/"
437 ++i;
438 }
439 }
440
441 // Split the path into components
442 while (i < n) {
443 // Skip path separators
444 while (i < n && (path[i] == '/' || path[i] == '\\')) {
445 ++i;
446 }
447 // Find the next separator
448 size_t j = i;
449 while (j < n && path[j] != '/' && path[j] != '\\') {
450 ++j;
451 }
452 if (i < j) {
453 result.components.push_back(path.substr(i, j - i));
454 i = j;
455 }
456 }
457
458 return result;
459 }
460
464 inline void create_directories(const std::string& path) {
465 if (path.empty()) return;
467 auto &components = path_pc.components;
468 size_t components_size = components.size();
469
470 // Build the path incrementally and create directories
471 std::string current_path = path_pc.root;
472 for (size_t i = 0; i < components_size; ++i) {
473 if (!current_path.empty() && current_path.back() != '/' && current_path.back() != '\\') {
474 current_path += '/';
475 }
476 current_path += components[i];
477
478 // Skip special components
479 if (components[i] == ".." ||
480 components[i] == "/" ||
481 components[i] == "~/") continue;
482# ifdef _WIN32
483 int ret = _mkdir(utf8_to_ansi(current_path).c_str());
484# else
485 int ret = mkdir(current_path.c_str(), 0755);
486# endif
487 int errnum = errno;
488 if (ret != 0 && errnum != EEXIST) {
489 throw std::runtime_error("Failed to create directory: " + current_path);
490 }
491 }
492 }
493
494#endif // __cplusplus >= 201703L
495
496}; // namespace mdbxc
497
498#endif // _MDBX_CONTAINERS_PATH_UTILS_HPP_INCLUDED
std::string get_parent_path(const std::string &file_path)
Extracts the parent directory from a full file path.
PathComponents split_path(const std::string &path)
Splits a path into its root and components.
bool is_explicitly_relative(const std::string &s) noexcept
Check if path starts with explicit relative prefix.
void create_directories(const std::string &path)
Creates directories recursively for the given path.
std::string get_exec_dir()
Retrieves the directory of the executable file.
bool is_path_sep(char c)
Check if character is a path separator.
bool is_absolute_path(const std::string &path)
Checks whether the given path is absolute (cross-platform).
std::string utf8_to_ansi(const std::string &utf8) noexcept
Converts a UTF-8 string to an ANSI string (Windows-specific).
std::string get_file_name(const std::string &file_path)
Extracts the file name from a full file path.
std::string lexically_normal_compat(const std::string &in)
Normalize path removing '.
Structure to hold the root and components of a path.
std::vector< std::string > components
The components of the path.
std::string root
The root part of the path (e.g., "/", "C:")