Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/common/file_system.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "file_system.h"
5
#include "assert.h"
6
#include "error.h"
7
#include "log.h"
8
#include "path.h"
9
#include "string_util.h"
10
#include "timer.h"
11
12
#include "fmt/format.h"
13
14
#include <algorithm>
15
#include <cstdlib>
16
#include <cstring>
17
#include <limits>
18
#include <numeric>
19
#include <utility>
20
21
#ifdef __APPLE__
22
#include <mach-o/dyld.h>
23
#include <stdlib.h>
24
#include <sys/param.h>
25
#endif
26
27
#ifdef __FreeBSD__
28
#include <sys/sysctl.h>
29
#endif
30
31
#if defined(_WIN32)
32
#include "windows_headers.h"
33
#include <io.h>
34
#include <malloc.h>
35
#include <pathcch.h>
36
#include <share.h>
37
#include <shlobj.h>
38
#include <winioctl.h>
39
#else
40
#include <dirent.h>
41
#include <errno.h>
42
#include <fcntl.h>
43
#include <limits.h>
44
#include <sys/stat.h>
45
#include <sys/types.h>
46
#include <unistd.h>
47
#endif
48
49
LOG_CHANNEL(FileSystem);
50
51
#ifdef _WIN32
52
static std::time_t ConvertFileTimeToUnixTime(const FILETIME& ft)
53
{
54
// based off https://stackoverflow.com/a/6161842
55
static constexpr s64 WINDOWS_TICK = 10000000;
56
static constexpr s64 SEC_TO_UNIX_EPOCH = 11644473600LL;
57
58
const s64 full = static_cast<s64>((static_cast<u64>(ft.dwHighDateTime) << 32) | static_cast<u64>(ft.dwLowDateTime));
59
return static_cast<std::time_t>(full / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
60
}
61
template<class T>
62
static bool IsUNCPath(const T& path)
63
{
64
return (path.length() >= 3 && path[0] == '\\' && path[1] == '\\');
65
}
66
#endif
67
68
static inline bool FileSystemCharacterIsSane(char32_t c, bool strip_slashes)
69
{
70
// no newlines, don't be silly. or other control characters...
71
if (c <= static_cast<char32_t>(31))
72
return false;
73
74
#ifdef _WIN32
75
// https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#naming-conventions
76
if ((c == U'/' || c == U'\\') && strip_slashes)
77
return false;
78
79
if (c == U'<' || c == U'>' || c == U':' || c == U'"' || c == U'|' || c == U'?' || c == U'*')
80
return false;
81
#else
82
if (c == '/' && strip_slashes)
83
return false;
84
85
// drop asterisks too, they make globbing annoying
86
if (c == '*')
87
return false;
88
89
// macos doesn't allow colons, apparently
90
#ifdef __APPLE__
91
if (c == U':')
92
return false;
93
#endif
94
#endif
95
96
return true;
97
}
98
99
std::string Path::SanitizeFileName(std::string_view str, bool strip_slashes /* = true */)
100
{
101
std::string ret;
102
ret.reserve(str.length());
103
104
size_t pos = 0;
105
while (pos < str.length())
106
{
107
char32_t ch;
108
pos += StringUtil::DecodeUTF8(str, pos, &ch);
109
ch = FileSystemCharacterIsSane(ch, strip_slashes) ? ch : U'_';
110
StringUtil::EncodeAndAppendUTF8(ret, ch);
111
}
112
113
#ifdef _WIN32
114
// Windows: Can't end filename with a period.
115
if (ret.length() > 0 && ret.back() == '.')
116
ret.back() = '_';
117
#endif
118
119
return ret;
120
}
121
122
void Path::SanitizeFileName(std::string* str, bool strip_slashes /* = true */)
123
{
124
const size_t len = str->length();
125
126
char small_buf[128];
127
std::unique_ptr<char[]> large_buf;
128
char* str_copy = small_buf;
129
if (len >= std::size(small_buf))
130
{
131
large_buf = std::make_unique<char[]>(len + 1);
132
str_copy = large_buf.get();
133
}
134
std::memcpy(str_copy, str->c_str(), sizeof(char) * (len + 1));
135
str->clear();
136
137
size_t pos = 0;
138
while (pos < len)
139
{
140
char32_t ch;
141
pos += StringUtil::DecodeUTF8(str_copy + pos, pos - len, &ch);
142
ch = FileSystemCharacterIsSane(ch, strip_slashes) ? ch : U'_';
143
StringUtil::EncodeAndAppendUTF8(*str, ch);
144
}
145
146
#ifdef _WIN32
147
// Windows: Can't end filename with a period.
148
if (str->length() > 0 && str->back() == '.')
149
str->back() = '_';
150
#endif
151
}
152
153
bool Path::IsFileNameValid(std::string_view str, bool allow_slashes)
154
{
155
size_t pos = 0;
156
while (pos < str.length())
157
{
158
char32_t ch;
159
pos += StringUtil::DecodeUTF8(str, pos, &ch);
160
if (!FileSystemCharacterIsSane(ch, !allow_slashes))
161
return false;
162
}
163
164
#ifdef _WIN32
165
// Windows: Can't end filename with a period.
166
if (str.length() > 0 && str.back() == '.')
167
return false;
168
#endif
169
170
return true;
171
}
172
173
std::string Path::RemoveLengthLimits(std::string_view str)
174
{
175
std::string ret;
176
#ifdef _WIN32
177
ret.reserve(str.length() + (IsUNCPath(str) ? 4 : 6));
178
#endif
179
ret.append(str);
180
RemoveLengthLimits(&ret);
181
return ret;
182
}
183
184
void Path::RemoveLengthLimits(std::string* path)
185
{
186
DebugAssert(IsAbsolute(*path));
187
#ifdef _WIN32
188
// Any forward slashes should be backslashes.
189
for (char& ch : *path)
190
ch = (ch == '/') ? '\\' : ch;
191
192
if (IsUNCPath(*path))
193
{
194
// \\server\path => \\?\UNC\server\path
195
DebugAssert((*path)[0] == '\\' && (*path)[1] == '\\');
196
path->erase(0, 2);
197
path->insert(0, "\\\\?\\UNC\\");
198
}
199
else
200
{
201
// C:\file => \\?\C:\file
202
path->insert(0, "\\\\?\\");
203
}
204
#endif
205
}
206
207
#ifdef _WIN32
208
209
bool FileSystem::GetWin32Path(std::wstring* dest, std::string_view str)
210
{
211
// Just convert to wide if it's a relative path, MAX_PATH still applies.
212
if (!Path::IsAbsolute(str))
213
return StringUtil::UTF8StringToWideString(*dest, str);
214
215
// PathCchCanonicalizeEx() thankfully takes care of everything.
216
// But need to widen the string first, avoid the stack allocation.
217
int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), nullptr, 0);
218
if (wlen <= 0) [[unlikely]]
219
return false;
220
221
// So copy it to a temp wide buffer first.
222
wchar_t* wstr_buf = static_cast<wchar_t*>(_malloca(sizeof(wchar_t) * (static_cast<size_t>(wlen) + 1)));
223
wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), wstr_buf, wlen);
224
if (wlen <= 0) [[unlikely]]
225
{
226
_freea(wstr_buf);
227
return false;
228
}
229
230
// And use PathCchCanonicalizeEx() to fix up any non-direct elements.
231
wstr_buf[wlen] = '\0';
232
dest->resize(std::max<size_t>(static_cast<size_t>(wlen) + (IsUNCPath(str) ? 9 : 5), 16));
233
for (;;)
234
{
235
const HRESULT hr =
236
PathCchCanonicalizeEx(dest->data(), dest->size(), wstr_buf, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH);
237
if (SUCCEEDED(hr))
238
{
239
dest->resize(std::wcslen(dest->data()));
240
_freea(wstr_buf);
241
return true;
242
}
243
else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
244
{
245
dest->resize(dest->size() * 2);
246
continue;
247
}
248
else [[unlikely]]
249
{
250
ERROR_LOG("PathCchCanonicalizeEx() returned {:08X}", static_cast<unsigned>(hr));
251
_freea(wstr_buf);
252
return false;
253
}
254
}
255
}
256
257
std::wstring FileSystem::GetWin32Path(std::string_view str)
258
{
259
std::wstring ret;
260
if (!GetWin32Path(&ret, str))
261
ret.clear();
262
return ret;
263
}
264
265
#endif
266
267
#ifndef __ANDROID__
268
269
template<typename T>
270
static inline void PathAppendString(std::string& dst, const T& src)
271
{
272
if (dst.capacity() < (dst.length() + src.length()))
273
dst.reserve(dst.length() + src.length());
274
275
bool last_separator = (!dst.empty() && dst.back() == FS_OSPATH_SEPARATOR_CHARACTER);
276
277
size_t index = 0;
278
279
#ifdef _WIN32
280
// special case for UNC paths here
281
if (dst.empty() && IsUNCPath(src))
282
{
283
dst.append("\\\\");
284
index = 2;
285
}
286
#endif
287
288
for (; index < src.length(); index++)
289
{
290
const char ch = src[index];
291
292
#ifdef _WIN32
293
// convert forward slashes to backslashes
294
if (ch == '\\' || ch == '/')
295
#else
296
if (ch == '/')
297
#endif
298
{
299
if (last_separator)
300
continue;
301
last_separator = true;
302
dst.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
303
}
304
else
305
{
306
last_separator = false;
307
dst.push_back(ch);
308
}
309
}
310
}
311
312
bool Path::IsAbsolute(std::string_view path)
313
{
314
#ifdef _WIN32
315
return (path.length() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) &&
316
path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
317
IsUNCPath(path);
318
#else
319
return (path.length() >= 1 && path[0] == '/');
320
#endif
321
}
322
323
std::string Path::RealPath(std::string_view path)
324
{
325
// Resolve non-absolute paths first.
326
std::vector<std::string_view> components;
327
if (!IsAbsolute(path))
328
components = Path::SplitNativePath(Path::Combine(FileSystem::GetWorkingDirectory(), path));
329
else
330
components = Path::SplitNativePath(path);
331
332
std::string realpath;
333
if (components.empty())
334
return realpath;
335
336
// Different to path because relative.
337
realpath.reserve(std::accumulate(components.begin(), components.end(), static_cast<size_t>(0),
338
[](size_t l, const std::string_view& s) { return l + s.length(); }) +
339
components.size() + 1);
340
341
#ifdef _WIN32
342
std::wstring wrealpath;
343
std::vector<WCHAR> symlink_buf;
344
wrealpath.reserve(realpath.size());
345
symlink_buf.resize(path.size() + 1);
346
347
// Check for any symbolic links throughout the path while adding components.
348
const bool skip_first = IsUNCPath(path);
349
bool test_symlink = true;
350
for (const std::string_view& comp : components)
351
{
352
if (!realpath.empty())
353
{
354
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
355
realpath.append(comp);
356
}
357
else if (skip_first)
358
{
359
realpath.append(comp);
360
continue;
361
}
362
else
363
{
364
realpath.append(comp);
365
}
366
if (test_symlink)
367
{
368
DWORD attribs;
369
if (FileSystem::GetWin32Path(&wrealpath, realpath) &&
370
(attribs = GetFileAttributesW(wrealpath.c_str())) != INVALID_FILE_ATTRIBUTES)
371
{
372
// if not a link, go to the next component
373
if (attribs & FILE_ATTRIBUTE_REPARSE_POINT)
374
{
375
const HANDLE hFile =
376
CreateFileW(wrealpath.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
377
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
378
if (hFile != INVALID_HANDLE_VALUE)
379
{
380
// is a link! resolve it.
381
DWORD ret = GetFinalPathNameByHandleW(hFile, symlink_buf.data(), static_cast<DWORD>(symlink_buf.size()),
382
FILE_NAME_NORMALIZED);
383
if (ret > symlink_buf.size())
384
{
385
symlink_buf.resize(ret);
386
ret = GetFinalPathNameByHandleW(hFile, symlink_buf.data(), static_cast<DWORD>(symlink_buf.size()),
387
FILE_NAME_NORMALIZED);
388
}
389
if (ret != 0)
390
StringUtil::WideStringToUTF8String(realpath, std::wstring_view(symlink_buf.data(), ret));
391
else
392
test_symlink = false;
393
394
CloseHandle(hFile);
395
}
396
}
397
}
398
else
399
{
400
// not a file or link
401
test_symlink = false;
402
}
403
}
404
}
405
406
// GetFinalPathNameByHandleW() adds a \\?\ prefix, so remove it.
407
if (realpath.starts_with("\\\\?\\") && IsAbsolute(std::string_view(realpath.data() + 4, realpath.size() - 4)))
408
{
409
realpath.erase(0, 4);
410
}
411
else if (realpath.starts_with("\\\\?\\UNC\\"))
412
{
413
realpath.erase(0, 7);
414
realpath.insert(realpath.begin(), '\\');
415
}
416
417
#else
418
// Why this monstrosity instead of calling realpath()? realpath() only works on files that exist.
419
std::string basepath;
420
std::string symlink;
421
422
basepath.reserve(realpath.capacity());
423
symlink.resize(realpath.capacity());
424
425
// Check for any symbolic links throughout the path while adding components.
426
bool test_symlink = true;
427
for (const std::string_view& comp : components)
428
{
429
if (!test_symlink)
430
{
431
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
432
realpath.append(comp);
433
continue;
434
}
435
436
basepath = realpath;
437
if (realpath.empty() || realpath.back() != FS_OSPATH_SEPARATOR_CHARACTER)
438
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
439
realpath.append(comp);
440
441
// Check if the last component added is a symlink
442
struct stat sb;
443
if (lstat(realpath.c_str(), &sb) != 0)
444
{
445
// Don't bother checking any further components once we error out.
446
test_symlink = false;
447
continue;
448
}
449
else if (!S_ISLNK(sb.st_mode))
450
{
451
// Nope, keep going.
452
continue;
453
}
454
455
for (;;)
456
{
457
ssize_t sz = readlink(realpath.c_str(), symlink.data(), symlink.size());
458
if (sz < 0)
459
{
460
// shouldn't happen, due to the S_ISLNK check above.
461
test_symlink = false;
462
break;
463
}
464
else if (static_cast<size_t>(sz) == symlink.size())
465
{
466
// need a larger buffer
467
symlink.resize(symlink.size() * 2);
468
continue;
469
}
470
else
471
{
472
// is a link, and we resolved it. gotta check if the symlink itself is relative :(
473
symlink.resize(static_cast<size_t>(sz));
474
if (!Path::IsAbsolute(symlink))
475
{
476
// symlink is relative to the directory of the symlink
477
realpath = basepath;
478
if (realpath.empty() || realpath.back() != FS_OSPATH_SEPARATOR_CHARACTER)
479
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
480
realpath.append(symlink);
481
}
482
else
483
{
484
// Use the new, symlinked path.
485
realpath = symlink;
486
}
487
488
break;
489
}
490
}
491
}
492
#endif
493
494
// Get rid of any current/parent directory components before returning.
495
// This should be fine on Linux, since any symbolic links have already replaced the leading portion.
496
Path::Canonicalize(&realpath);
497
498
return realpath;
499
}
500
501
std::string Path::ToNativePath(std::string_view path)
502
{
503
std::string ret;
504
PathAppendString(ret, path);
505
506
// remove trailing slashes
507
if (ret.length() > 1)
508
{
509
while (ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
510
ret.pop_back();
511
}
512
513
return ret;
514
}
515
516
void Path::ToNativePath(std::string* path)
517
{
518
*path = Path::ToNativePath(*path);
519
}
520
521
std::string Path::Canonicalize(std::string_view path)
522
{
523
std::vector<std::string_view> components = Path::SplitNativePath(path);
524
std::vector<std::string_view> new_components;
525
new_components.reserve(components.size());
526
for (const std::string_view& component : components)
527
{
528
if (component == ".")
529
{
530
// current directory, so it can be skipped, unless it's the only component
531
if (components.size() == 1)
532
new_components.push_back(component);
533
}
534
else if (component == "..")
535
{
536
// parent directory, pop one off if we're not at the beginning, otherwise preserve.
537
if (!new_components.empty())
538
new_components.pop_back();
539
else
540
new_components.push_back(component);
541
}
542
else
543
{
544
// anything else, preserve
545
new_components.push_back(component);
546
}
547
}
548
549
return Path::JoinNativePath(new_components);
550
}
551
552
void Path::Canonicalize(std::string* path)
553
{
554
*path = Canonicalize(*path);
555
}
556
557
std::string Path::MakeRelative(std::string_view path, std::string_view relative_to)
558
{
559
// simple algorithm, we just work on the components. could probably be better, but it'll do for now.
560
std::vector<std::string_view> path_components(SplitNativePath(path));
561
std::vector<std::string_view> relative_components(SplitNativePath(relative_to));
562
std::vector<std::string_view> new_components;
563
564
// both must be absolute paths
565
if (Path::IsAbsolute(path) && Path::IsAbsolute(relative_to))
566
{
567
// find the number of same components
568
size_t num_same = 0;
569
for (size_t i = 0; i < path_components.size() && i < relative_components.size(); i++)
570
{
571
if (path_components[i] == relative_components[i])
572
num_same++;
573
else
574
break;
575
}
576
577
// we need at least one same component
578
if (num_same > 0)
579
{
580
// from the relative_to directory, back up to the start of the common components
581
const size_t num_ups = relative_components.size() - num_same;
582
for (size_t i = 0; i < num_ups; i++)
583
new_components.emplace_back("..");
584
585
// and add the remainder of the path components
586
for (size_t i = num_same; i < path_components.size(); i++)
587
new_components.push_back(std::move(path_components[i]));
588
}
589
else
590
{
591
// no similarity
592
new_components = std::move(path_components);
593
}
594
}
595
else
596
{
597
// not absolute
598
new_components = std::move(path_components);
599
}
600
601
return JoinNativePath(new_components);
602
}
603
604
std::string_view Path::GetExtension(std::string_view path)
605
{
606
const std::string_view::size_type pos = path.rfind('.');
607
if (pos == std::string_view::npos)
608
return std::string_view();
609
else
610
return path.substr(pos + 1);
611
}
612
613
std::string Path::ReplaceExtension(std::string_view path, std::string_view new_extension)
614
{
615
const std::string_view::size_type pos = path.rfind('.');
616
if (pos == std::string_view::npos)
617
return std::string(path);
618
619
std::string ret(path, 0, pos + 1);
620
ret.append(new_extension);
621
return ret;
622
}
623
624
static std::string_view::size_type GetLastSeperatorPosition(std::string_view path, bool include_separator)
625
{
626
std::string_view::size_type last_separator = path.rfind('/');
627
if (include_separator && last_separator != std::string_view::npos)
628
last_separator++;
629
630
#if defined(_WIN32)
631
std::string_view::size_type other_last_separator = path.rfind('\\');
632
if (other_last_separator != std::string_view::npos)
633
{
634
if (include_separator)
635
other_last_separator++;
636
if (last_separator == std::string_view::npos || other_last_separator > last_separator)
637
last_separator = other_last_separator;
638
}
639
#endif
640
641
return last_separator;
642
}
643
644
std::string FileSystem::GetDisplayNameFromPath(std::string_view path)
645
{
646
return std::string(Path::GetFileName(path));
647
}
648
649
std::string_view Path::GetDirectory(std::string_view path)
650
{
651
const std::string::size_type pos = GetLastSeperatorPosition(path, false);
652
if (pos == std::string_view::npos)
653
return {};
654
655
return path.substr(0, pos);
656
}
657
658
std::string_view Path::GetFileName(std::string_view path)
659
{
660
const std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
661
if (pos == std::string_view::npos)
662
return path;
663
664
return path.substr(pos);
665
}
666
667
std::string_view Path::GetFileTitle(std::string_view path)
668
{
669
const std::string_view filename(GetFileName(path));
670
const std::string::size_type pos = filename.rfind('.');
671
if (pos == std::string_view::npos)
672
return filename;
673
674
return filename.substr(0, pos);
675
}
676
677
std::string Path::ChangeFileName(std::string_view path, std::string_view new_filename)
678
{
679
std::string ret;
680
PathAppendString(ret, path);
681
682
const std::string_view::size_type pos = GetLastSeperatorPosition(ret, true);
683
if (pos == std::string_view::npos)
684
{
685
ret.clear();
686
PathAppendString(ret, new_filename);
687
}
688
else
689
{
690
if (!new_filename.empty())
691
{
692
ret.erase(pos);
693
PathAppendString(ret, new_filename);
694
}
695
else
696
{
697
ret.erase(pos - 1);
698
}
699
}
700
701
return ret;
702
}
703
704
void Path::ChangeFileName(std::string* path, std::string_view new_filename)
705
{
706
*path = ChangeFileName(*path, new_filename);
707
}
708
709
std::string Path::AppendDirectory(std::string_view path, std::string_view new_dir)
710
{
711
std::string ret;
712
if (!new_dir.empty())
713
{
714
const std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
715
716
ret.reserve(path.length() + new_dir.length() + 1);
717
if (pos != std::string_view::npos)
718
PathAppendString(ret, path.substr(0, pos));
719
720
while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
721
ret.pop_back();
722
723
if (!ret.empty())
724
ret += FS_OSPATH_SEPARATOR_CHARACTER;
725
726
PathAppendString(ret, new_dir);
727
728
if (pos != std::string_view::npos)
729
{
730
const std::string_view filepart(path.substr(pos));
731
if (!filepart.empty())
732
{
733
ret += FS_OSPATH_SEPARATOR_CHARACTER;
734
PathAppendString(ret, filepart);
735
}
736
}
737
else if (!path.empty())
738
{
739
ret += FS_OSPATH_SEPARATOR_CHARACTER;
740
PathAppendString(ret, path);
741
}
742
}
743
else
744
{
745
PathAppendString(ret, path);
746
}
747
748
return ret;
749
}
750
751
void Path::AppendDirectory(std::string* path, std::string_view new_dir)
752
{
753
*path = AppendDirectory(*path, new_dir);
754
}
755
756
std::vector<std::string_view> Path::SplitWindowsPath(std::string_view path)
757
{
758
std::vector<std::string_view> parts;
759
760
std::string::size_type start = 0;
761
std::string::size_type pos = 0;
762
763
// preserve unc paths
764
if (path.size() > 2 && path[0] == '\\' && path[1] == '\\')
765
pos = 2;
766
767
while (pos < path.size())
768
{
769
if (path[pos] != '/' && path[pos] != '\\')
770
{
771
pos++;
772
continue;
773
}
774
775
// skip consecutive separators
776
if (pos != start)
777
parts.push_back(path.substr(start, pos - start));
778
779
pos++;
780
start = pos;
781
}
782
783
if (start != pos)
784
parts.push_back(path.substr(start));
785
786
return parts;
787
}
788
789
std::string Path::JoinWindowsPath(const std::vector<std::string_view>& components)
790
{
791
return StringUtil::JoinString(components.begin(), components.end(), '\\');
792
}
793
794
std::vector<std::string_view> Path::SplitNativePath(std::string_view path)
795
{
796
#ifdef _WIN32
797
return SplitWindowsPath(path);
798
#else
799
std::vector<std::string_view> parts;
800
801
std::string::size_type start = 0;
802
std::string::size_type pos = 0;
803
while (pos < path.size())
804
{
805
if (path[pos] != '/')
806
{
807
pos++;
808
continue;
809
}
810
811
// skip consecutive separators
812
// for unix, we create an empty element at the beginning when it's an absolute path
813
// that way, when it's re-joined later, we preserve the starting slash.
814
if (pos != start || pos == 0)
815
parts.push_back(path.substr(start, pos - start));
816
817
pos++;
818
start = pos;
819
}
820
821
if (start != pos)
822
parts.push_back(path.substr(start));
823
824
return parts;
825
#endif
826
}
827
828
std::string Path::JoinNativePath(const std::vector<std::string_view>& components)
829
{
830
return StringUtil::JoinString(components.begin(), components.end(), FS_OSPATH_SEPARATOR_CHARACTER);
831
}
832
833
std::vector<std::string> FileSystem::GetRootDirectoryList()
834
{
835
std::vector<std::string> results;
836
837
#if defined(_WIN32)
838
char buf[256];
839
const DWORD size = GetLogicalDriveStringsA(sizeof(buf), buf);
840
if (size != 0 && size < (sizeof(buf) - 1))
841
{
842
const char* ptr = buf;
843
while (*ptr != '\0')
844
{
845
const std::size_t len = std::strlen(ptr);
846
results.emplace_back(ptr, len);
847
ptr += len + 1u;
848
}
849
}
850
#else
851
const char* home_path = std::getenv("HOME");
852
if (home_path)
853
results.push_back(home_path);
854
855
results.push_back("/");
856
#endif
857
858
return results;
859
}
860
861
std::string Path::BuildRelativePath(std::string_view path, std::string_view new_filename)
862
{
863
std::string new_string;
864
865
std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
866
if (pos != std::string_view::npos)
867
new_string.assign(path, 0, pos);
868
new_string.append(new_filename);
869
return new_string;
870
}
871
872
std::string Path::Combine(std::string_view base, std::string_view next)
873
{
874
std::string ret;
875
ret.reserve(base.length() + next.length() + 1);
876
877
PathAppendString(ret, base);
878
while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
879
ret.pop_back();
880
881
ret += FS_OSPATH_SEPARATOR_CHARACTER;
882
PathAppendString(ret, next);
883
while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
884
ret.pop_back();
885
886
return ret;
887
}
888
889
std::FILE* FileSystem::OpenCFile(const char* path, const char* mode, Error* error)
890
{
891
#ifdef _WIN32
892
const std::wstring wfilename = GetWin32Path(path);
893
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
894
if (!wfilename.empty() && !wmode.empty())
895
{
896
std::FILE* fp;
897
const errno_t err = _wfopen_s(&fp, wfilename.c_str(), wmode.c_str());
898
if (err != 0)
899
{
900
Error::SetErrno(error, err);
901
return nullptr;
902
}
903
904
return fp;
905
}
906
907
std::FILE* fp;
908
const errno_t err = fopen_s(&fp, path, mode);
909
if (err != 0)
910
{
911
Error::SetErrno(error, err);
912
return nullptr;
913
}
914
915
return fp;
916
#else
917
std::FILE* fp = std::fopen(path, mode);
918
if (!fp)
919
Error::SetErrno(error, errno);
920
return fp;
921
#endif
922
}
923
924
std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* path, s32 retry_ms, Error* error /*= nullptr*/)
925
{
926
#ifdef _WIN32
927
const std::wstring wpath = GetWin32Path(path);
928
if (wpath.empty())
929
{
930
Error::SetStringView(error, "Invalid path.");
931
return nullptr;
932
}
933
934
HANDLE file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
935
936
// if there's a sharing violation, keep retrying
937
if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION && retry_ms >= 0)
938
{
939
Timer timer;
940
while (retry_ms == 0 || timer.GetTimeMilliseconds() <= retry_ms)
941
{
942
Sleep(1);
943
file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
944
if (file != INVALID_HANDLE_VALUE || GetLastError() != ERROR_SHARING_VIOLATION)
945
break;
946
}
947
}
948
949
if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND)
950
{
951
// try creating it
952
file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, NULL);
953
if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_EXISTS)
954
{
955
// someone else beat us in the race, try again with existing.
956
file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
957
}
958
}
959
960
// done?
961
if (file == INVALID_HANDLE_VALUE)
962
{
963
Error::SetWin32(error, "CreateFile() failed: ", GetLastError());
964
return nullptr;
965
}
966
967
// convert to C FILE
968
const int fd = _open_osfhandle(reinterpret_cast<intptr_t>(file), 0);
969
if (fd < 0)
970
{
971
Error::SetErrno(error, "_open_osfhandle() failed: ", errno);
972
CloseHandle(file);
973
return nullptr;
974
}
975
976
// convert to a stream
977
std::FILE* cfile = _fdopen(fd, "r+b");
978
if (!cfile)
979
{
980
Error::SetErrno(error, "_fdopen() failed: ", errno);
981
_close(fd);
982
}
983
984
return cfile;
985
#else
986
std::FILE* fp = std::fopen(path, "r+b");
987
if (fp)
988
return fp;
989
990
// don't try creating for any error other than "not exist"
991
if (errno != ENOENT)
992
{
993
Error::SetErrno(error, errno);
994
return nullptr;
995
}
996
997
// try again, but create the file. mode "x" exists on all platforms.
998
fp = std::fopen(path, "w+bx");
999
if (fp)
1000
return fp;
1001
1002
// if it already exists, someone else beat us in the race. try again with existing.
1003
if (errno == EEXIST)
1004
fp = std::fopen(path, "r+b");
1005
if (!fp)
1006
{
1007
Error::SetErrno(error, errno);
1008
return nullptr;
1009
}
1010
1011
return fp;
1012
#endif
1013
}
1014
1015
int FileSystem::OpenFDFile(const char* path, int flags, int mode, Error* error)
1016
{
1017
#ifdef _WIN32
1018
const std::wstring wpath = GetWin32Path(path);
1019
if (!wpath.empty())
1020
return _wopen(wpath.c_str(), flags, mode);
1021
1022
return -1;
1023
#else
1024
const int fd = open(path, flags, mode);
1025
if (fd < 0)
1026
Error::SetErrno(error, errno);
1027
return fd;
1028
#endif
1029
}
1030
1031
std::FILE* FileSystem::OpenSharedCFile(const char* path, const char* mode, FileShareMode share_mode, Error* error)
1032
{
1033
#ifdef _WIN32
1034
const std::wstring wpath = GetWin32Path(path);
1035
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
1036
if (wpath.empty() || wmode.empty())
1037
return nullptr;
1038
1039
int share_flags = 0;
1040
switch (share_mode)
1041
{
1042
case FileShareMode::DenyNone:
1043
share_flags = _SH_DENYNO;
1044
break;
1045
case FileShareMode::DenyRead:
1046
share_flags = _SH_DENYRD;
1047
break;
1048
case FileShareMode::DenyWrite:
1049
share_flags = _SH_DENYWR;
1050
break;
1051
case FileShareMode::DenyReadWrite:
1052
default:
1053
share_flags = _SH_DENYRW;
1054
break;
1055
}
1056
1057
std::FILE* fp = _wfsopen(wpath.c_str(), wmode.c_str(), share_flags);
1058
if (fp)
1059
return fp;
1060
1061
Error::SetErrno(error, errno);
1062
return nullptr;
1063
#else
1064
std::FILE* fp = std::fopen(path, mode);
1065
if (!fp)
1066
Error::SetErrno(error, errno);
1067
return fp;
1068
#endif
1069
}
1070
1071
#endif // __ANDROID__
1072
1073
std::string Path::URLEncode(std::string_view str)
1074
{
1075
std::string ret;
1076
ret.reserve(str.length() + ((str.length() + 3) / 4) * 3);
1077
1078
for (size_t i = 0, l = str.size(); i < l; i++)
1079
{
1080
const char c = str[i];
1081
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' ||
1082
c == '.' || c == '~')
1083
{
1084
ret.push_back(c);
1085
}
1086
else
1087
{
1088
ret.push_back('%');
1089
1090
const unsigned char n1 = static_cast<unsigned char>(c) >> 4;
1091
const unsigned char n2 = static_cast<unsigned char>(c) & 0x0F;
1092
ret.push_back((n1 >= 10) ? ('A' + (n1 - 10)) : ('0' + n1));
1093
ret.push_back((n2 >= 10) ? ('A' + (n2 - 10)) : ('0' + n2));
1094
}
1095
}
1096
1097
return ret;
1098
}
1099
1100
std::string Path::URLDecode(std::string_view str)
1101
{
1102
std::string ret;
1103
ret.reserve(str.length());
1104
1105
for (size_t i = 0, l = str.size(); i < l;)
1106
{
1107
const char c = str[i++];
1108
if (c == '%')
1109
{
1110
if ((i + 2) > str.length())
1111
break;
1112
1113
// return -1 which will be negative when or'ed with anything else, so it becomes invalid.
1114
static constexpr auto to_nibble = [](char ch) -> int {
1115
return (ch >= '0' && ch <= '9') ?
1116
static_cast<int>(ch - '0') :
1117
((ch >= 'a' && ch <= 'f') ? (static_cast<int>(ch - 'a') + 0xa) :
1118
((ch >= 'A' && ch <= 'F') ? (static_cast<int>(ch - 'A') + 0xa) : -1));
1119
};
1120
1121
const int upper = to_nibble(str[i++]);
1122
const int lower = to_nibble(str[i++]);
1123
const int dch = lower | (upper << 4);
1124
if (dch < 0)
1125
break;
1126
1127
ret.push_back(static_cast<char>(dch));
1128
}
1129
else
1130
{
1131
ret.push_back(c);
1132
}
1133
}
1134
1135
return ret;
1136
}
1137
1138
std::string Path::CreateFileURL(std::string_view path)
1139
{
1140
DebugAssert(IsAbsolute(path));
1141
1142
std::string ret;
1143
ret.reserve(path.length() + 10);
1144
ret.append("file://");
1145
1146
const std::vector<std::string_view> components = SplitNativePath(path);
1147
Assert(!components.empty());
1148
1149
const std::string_view& first = components.front();
1150
#ifdef _WIN32
1151
// Windows doesn't urlencode the drive letter.
1152
// UNC paths should be omit the leading slash.
1153
if (first.starts_with("\\\\"))
1154
{
1155
// file://hostname/...
1156
ret.append(first.substr(2));
1157
}
1158
else
1159
{
1160
// file:///c:/...
1161
fmt::format_to(std::back_inserter(ret), "/{}", first);
1162
}
1163
#else
1164
// Don't append a leading slash for the first component.
1165
ret.append(first);
1166
#endif
1167
1168
for (size_t comp = 1; comp < components.size(); comp++)
1169
{
1170
fmt::format_to(std::back_inserter(ret), "/{}", URLEncode(components[comp]));
1171
}
1172
1173
return ret;
1174
}
1175
1176
FileSystem::AtomicRenamedFileDeleter::AtomicRenamedFileDeleter(std::string temp_path, std::string final_path)
1177
: m_temp_path(std::move(temp_path)), m_final_path(std::move(final_path))
1178
{
1179
}
1180
1181
FileSystem::AtomicRenamedFileDeleter::~AtomicRenamedFileDeleter() = default;
1182
1183
void FileSystem::AtomicRenamedFileDeleter::operator()(std::FILE* fp)
1184
{
1185
if (!fp)
1186
return;
1187
1188
Error error;
1189
1190
// final filename empty => discarded.
1191
if (!m_final_path.empty())
1192
{
1193
if (!commit(fp, &error))
1194
{
1195
ERROR_LOG("Failed to commit temporary file '{}', discarding. Error was {}.", Path::GetFileName(m_temp_path),
1196
error.GetDescription());
1197
}
1198
1199
return;
1200
}
1201
1202
// we're discarding the file, don't care if it fails.
1203
std::fclose(fp);
1204
1205
if (!DeleteFile(m_temp_path.c_str(), &error))
1206
ERROR_LOG("Failed to delete temporary file '{}': {}", Path::GetFileName(m_temp_path), error.GetDescription());
1207
}
1208
1209
bool FileSystem::AtomicRenamedFileDeleter::commit(std::FILE* fp, Error* error)
1210
{
1211
if (!fp) [[unlikely]]
1212
{
1213
Error::SetStringView(error, "File pointer is null.");
1214
return false;
1215
}
1216
1217
if (std::fclose(fp) != 0)
1218
{
1219
Error::SetErrno(error, "fclose() failed: ", errno);
1220
m_final_path.clear();
1221
}
1222
1223
// Should not have been discarded.
1224
if (!m_final_path.empty())
1225
{
1226
return RenamePath(m_temp_path.c_str(), m_final_path.c_str(), error);
1227
}
1228
else
1229
{
1230
Error::SetStringView(error, "File has already been discarded.");
1231
return DeleteFile(m_temp_path.c_str(), error);
1232
}
1233
}
1234
1235
void FileSystem::AtomicRenamedFileDeleter::discard()
1236
{
1237
m_final_path = {};
1238
}
1239
1240
FileSystem::AtomicRenamedFile FileSystem::CreateAtomicRenamedFile(std::string path, Error* error /*= nullptr*/)
1241
{
1242
std::string temp_path;
1243
std::FILE* fp = nullptr;
1244
if (!path.empty())
1245
{
1246
// this is disgusting, but we need null termination, and std::string::data() does not guarantee it.
1247
const size_t path_length = path.length();
1248
const size_t name_buf_size = path_length + 8;
1249
std::unique_ptr<char[]> name_buf = std::make_unique<char[]>(name_buf_size);
1250
std::memcpy(name_buf.get(), path.c_str(), path_length);
1251
StringUtil::Strlcpy(name_buf.get() + path_length, ".XXXXXX", name_buf_size);
1252
1253
#ifdef _WIN32
1254
const errno_t err = _mktemp_s(name_buf.get(), name_buf_size);
1255
if (err == 0)
1256
fp = OpenCFile(name_buf.get(), "w+b", error);
1257
else
1258
Error::SetErrno(error, "_mktemp_s() failed: ", err);
1259
1260
#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__FreeBSD__)
1261
const int fd = mkstemp(name_buf.get());
1262
if (fd >= 0)
1263
{
1264
fp = fdopen(fd, "w+b");
1265
if (!fp)
1266
Error::SetErrno(error, "fdopen() failed: ", errno);
1267
}
1268
else
1269
{
1270
Error::SetErrno(error, "mkstemp() failed: ", errno);
1271
}
1272
#else
1273
mktemp(name_buf.get());
1274
fp = OpenCFile(name_buf.get(), "w+b", error);
1275
#endif
1276
1277
if (fp)
1278
temp_path.assign(name_buf.get(), name_buf_size - 1);
1279
else
1280
path.clear();
1281
}
1282
1283
return AtomicRenamedFile(fp, AtomicRenamedFileDeleter(std::move(temp_path), std::move(path)));
1284
}
1285
1286
bool FileSystem::WriteAtomicRenamedFile(std::string path, const void* data, size_t data_length,
1287
Error* error /*= nullptr*/)
1288
{
1289
AtomicRenamedFile fp = CreateAtomicRenamedFile(std::move(path), error);
1290
if (!fp)
1291
return false;
1292
1293
if (data_length > 0 && std::fwrite(data, 1u, data_length, fp.get()) != data_length) [[unlikely]]
1294
{
1295
Error::SetErrno(error, "fwrite() failed: ", errno);
1296
DiscardAtomicRenamedFile(fp);
1297
return false;
1298
}
1299
1300
return true;
1301
}
1302
1303
bool FileSystem::WriteAtomicRenamedFile(std::string path, const std::span<const u8> data, Error* error /* = nullptr */)
1304
{
1305
return WriteAtomicRenamedFile(std::move(path), data.empty() ? nullptr : data.data(), data.size(), error);
1306
}
1307
1308
void FileSystem::DiscardAtomicRenamedFile(AtomicRenamedFile& file)
1309
{
1310
file.get_deleter().discard();
1311
}
1312
1313
bool FileSystem::CommitAtomicRenamedFile(AtomicRenamedFile& file, Error* error)
1314
{
1315
if (file.get_deleter().commit(file.release(), error))
1316
return true;
1317
1318
Error::AddPrefix(error, "Failed to commit file: ");
1319
return false;
1320
}
1321
1322
FileSystem::ManagedCFilePtr FileSystem::OpenManagedCFile(const char* path, const char* mode, Error* error)
1323
{
1324
return ManagedCFilePtr(OpenCFile(path, mode, error));
1325
}
1326
1327
FileSystem::ManagedCFilePtr FileSystem::OpenExistingOrCreateManagedCFile(const char* path, s32 retry_ms, Error* error)
1328
{
1329
return ManagedCFilePtr(OpenExistingOrCreateCFile(path, retry_ms, error));
1330
}
1331
1332
FileSystem::ManagedCFilePtr FileSystem::OpenManagedSharedCFile(const char* path, const char* mode,
1333
FileShareMode share_mode, Error* error)
1334
{
1335
return ManagedCFilePtr(OpenSharedCFile(path, mode, share_mode, error));
1336
}
1337
1338
int FileSystem::FSeek64(std::FILE* fp, s64 offset, int whence)
1339
{
1340
#ifdef _WIN32
1341
return _fseeki64(fp, offset, whence);
1342
#else
1343
// Prevent truncation on platforms which don't have a 64-bit off_t.
1344
if constexpr (sizeof(off_t) != sizeof(s64))
1345
{
1346
if (offset < std::numeric_limits<off_t>::min() || offset > std::numeric_limits<off_t>::max())
1347
return -1;
1348
}
1349
1350
return fseeko(fp, static_cast<off_t>(offset), whence);
1351
#endif
1352
}
1353
1354
bool FileSystem::FSeek64(std::FILE* fp, s64 offset, int whence, Error* error)
1355
{
1356
#ifdef _WIN32
1357
const int res = _fseeki64(fp, offset, whence);
1358
#else
1359
// Prevent truncation on platforms which don't have a 64-bit off_t.
1360
if constexpr (sizeof(off_t) != sizeof(s64))
1361
{
1362
if (offset < std::numeric_limits<off_t>::min() || offset > std::numeric_limits<off_t>::max())
1363
{
1364
Error::SetStringView(error, "Invalid offset.");
1365
return false;
1366
}
1367
}
1368
1369
const int res = fseeko(fp, static_cast<off_t>(offset), whence);
1370
#endif
1371
1372
if (res == 0)
1373
return true;
1374
1375
Error::SetErrno(error, errno);
1376
return false;
1377
}
1378
1379
s64 FileSystem::FTell64(std::FILE* fp)
1380
{
1381
#ifdef _WIN32
1382
return static_cast<s64>(_ftelli64(fp));
1383
#else
1384
return static_cast<s64>(ftello(fp));
1385
#endif
1386
}
1387
1388
s64 FileSystem::FSize64(std::FILE* fp, Error* error)
1389
{
1390
const s64 pos = FTell64(fp);
1391
if (pos < 0) [[unlikely]]
1392
{
1393
Error::SetErrno(error, "FTell64() failed: ", errno);
1394
return -1;
1395
}
1396
1397
if (FSeek64(fp, 0, SEEK_END) != 0) [[unlikely]]
1398
{
1399
Error::SetErrno(error, "FSeek64() to end failed: ", errno);
1400
return -1;
1401
}
1402
1403
const s64 size = FTell64(fp);
1404
if (size < 0) [[unlikely]]
1405
{
1406
Error::SetErrno(error, "FTell64() failed: ", errno);
1407
return -1;
1408
}
1409
1410
if (FSeek64(fp, pos, SEEK_SET) != 0)
1411
{
1412
Error::SetErrno(error, "FSeek64() to original position failed: ", errno);
1413
return -1;
1414
}
1415
1416
return size;
1417
}
1418
1419
bool FileSystem::FTruncate64(std::FILE* fp, s64 size, Error* error)
1420
{
1421
const int fd = fileno(fp);
1422
if (fd < 0)
1423
{
1424
Error::SetErrno(error, "fileno() failed: ", errno);
1425
return false;
1426
}
1427
1428
#ifdef _WIN32
1429
const errno_t err = _chsize_s(fd, size);
1430
if (err != 0)
1431
{
1432
Error::SetErrno(error, "_chsize_s() failed: ", err);
1433
return false;
1434
}
1435
1436
return true;
1437
#else
1438
// Prevent truncation on platforms which don't have a 64-bit off_t.
1439
if constexpr (sizeof(off_t) != sizeof(s64))
1440
{
1441
if (size < std::numeric_limits<off_t>::min() || size > std::numeric_limits<off_t>::max())
1442
{
1443
Error::SetStringView(error, "File size is too large.");
1444
return false;
1445
}
1446
}
1447
1448
if (ftruncate(fd, static_cast<off_t>(size)) < 0)
1449
{
1450
Error::SetErrno(error, "ftruncate() failed: ", errno);
1451
return false;
1452
}
1453
1454
return true;
1455
#endif
1456
}
1457
1458
s64 FileSystem::GetPathFileSize(const char* path)
1459
{
1460
FILESYSTEM_STAT_DATA sd;
1461
if (!StatFile(path, &sd))
1462
return -1;
1463
1464
return sd.Size;
1465
}
1466
1467
std::optional<DynamicHeapArray<u8>> FileSystem::ReadBinaryFile(const char* path, Error* error)
1468
{
1469
std::optional<DynamicHeapArray<u8>> ret;
1470
1471
ManagedCFilePtr fp = OpenManagedCFile(path, "rb", error);
1472
if (!fp)
1473
return ret;
1474
1475
ret = ReadBinaryFile(fp.get(), error);
1476
return ret;
1477
}
1478
1479
std::optional<DynamicHeapArray<u8>> FileSystem::ReadBinaryFile(std::FILE* fp, Error* error)
1480
{
1481
std::optional<DynamicHeapArray<u8>> ret;
1482
1483
if (FSeek64(fp, 0, SEEK_END) != 0) [[unlikely]]
1484
{
1485
Error::SetErrno(error, "FSeek64() to end failed: ", errno);
1486
return ret;
1487
}
1488
1489
const s64 size = FTell64(fp);
1490
if (size < 0) [[unlikely]]
1491
{
1492
Error::SetErrno(error, "FTell64() for length failed: ", errno);
1493
return ret;
1494
}
1495
1496
if constexpr (sizeof(s64) != sizeof(size_t))
1497
{
1498
if (size > static_cast<s64>(std::numeric_limits<long>::max())) [[unlikely]]
1499
{
1500
Error::SetStringFmt(error, "File size of {} is too large to read on this platform.", size);
1501
return ret;
1502
}
1503
}
1504
1505
if (FSeek64(fp, 0, SEEK_SET) != 0) [[unlikely]]
1506
{
1507
Error::SetErrno(error, "FSeek64() to start failed: ", errno);
1508
return ret;
1509
}
1510
1511
ret = DynamicHeapArray<u8>(static_cast<size_t>(size));
1512
if (size > 0 && std::fread(ret->data(), 1u, static_cast<size_t>(size), fp) != static_cast<size_t>(size)) [[unlikely]]
1513
{
1514
Error::SetErrno(error, "fread() failed: ", errno);
1515
ret.reset();
1516
}
1517
1518
return ret;
1519
}
1520
1521
std::optional<std::string> FileSystem::ReadFileToString(const char* path, Error* error)
1522
{
1523
std::optional<std::string> ret;
1524
1525
ManagedCFilePtr fp = OpenManagedCFile(path, "rb", error);
1526
if (!fp)
1527
return ret;
1528
1529
ret = ReadFileToString(fp.get());
1530
return ret;
1531
}
1532
1533
std::optional<std::string> FileSystem::ReadFileToString(std::FILE* fp, Error* error)
1534
{
1535
std::optional<std::string> ret;
1536
1537
if (FSeek64(fp, 0, SEEK_END) != 0) [[unlikely]]
1538
{
1539
Error::SetErrno(error, "FSeek64() to end failed: ", errno);
1540
return ret;
1541
}
1542
1543
const s64 size = FTell64(fp);
1544
if (size < 0) [[unlikely]]
1545
{
1546
Error::SetErrno(error, "FTell64() for length failed: ", errno);
1547
return ret;
1548
}
1549
1550
if constexpr (sizeof(s64) != sizeof(size_t))
1551
{
1552
if (size > static_cast<s64>(std::numeric_limits<long>::max())) [[unlikely]]
1553
{
1554
Error::SetStringFmt(error, "File size of {} is too large to read on this platform.", size);
1555
return ret;
1556
}
1557
}
1558
1559
if (FSeek64(fp, 0, SEEK_SET) != 0) [[unlikely]]
1560
{
1561
Error::SetErrno(error, "FSeek64() to start failed: ", errno);
1562
return ret;
1563
}
1564
1565
ret = std::string();
1566
ret->resize(static_cast<size_t>(size));
1567
// NOTE - assumes mode 'rb', for example, this will fail over missing Windows carriage return bytes
1568
if (size > 0)
1569
{
1570
if (std::fread(ret->data(), 1u, static_cast<size_t>(size), fp) != static_cast<size_t>(size))
1571
{
1572
Error::SetErrno(error, "fread() failed: ", errno);
1573
ret.reset();
1574
}
1575
else
1576
{
1577
static constexpr const u8 UTF16_BE_BOM[] = {0xFE, 0xFF};
1578
static constexpr const u8 UTF16_LE_BOM[] = {0xFF, 0xFE};
1579
static constexpr const u8 UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
1580
1581
if (ret->size() >= sizeof(UTF8_BOM) && std::memcmp(ret->data(), UTF8_BOM, sizeof(UTF8_BOM)) == 0)
1582
{
1583
// Remove UTF-8 BOM.
1584
ret->erase(0, sizeof(UTF8_BOM));
1585
}
1586
else if (ret->size() >= sizeof(UTF16_LE_BOM) && (ret->size() % 2) == 0)
1587
{
1588
const bool le = (std::memcmp(ret->data(), UTF16_LE_BOM, sizeof(UTF16_LE_BOM)) == 0);
1589
const bool be = (std::memcmp(ret->data(), UTF16_BE_BOM, sizeof(UTF16_BE_BOM)) == 0);
1590
if (le || be)
1591
{
1592
const std::string utf16 = std::move(ret.value());
1593
const std::string_view no_bom = std::string_view(utf16).substr(sizeof(UTF16_LE_BOM));
1594
ret = no_bom.empty() ? std::string() :
1595
(be ? StringUtil::DecodeUTF16BEString(no_bom.data(), no_bom.size()) :
1596
StringUtil::DecodeUTF16String(no_bom.data(), no_bom.size()));
1597
}
1598
}
1599
}
1600
}
1601
1602
return ret;
1603
}
1604
1605
bool FileSystem::WriteBinaryFile(const char* path, const void* data, size_t data_length, Error* error)
1606
{
1607
ManagedCFilePtr fp = OpenManagedCFile(path, "wb", error);
1608
if (!fp)
1609
return false;
1610
1611
if (data_length > 0 && std::fwrite(data, 1u, data_length, fp.get()) != data_length)
1612
{
1613
Error::SetErrno(error, "fwrite() failed: ", errno);
1614
return false;
1615
}
1616
1617
return true;
1618
}
1619
1620
bool FileSystem::WriteBinaryFile(const char* path, const std::span<const u8> data, Error* error /*= nullptr*/)
1621
{
1622
return WriteBinaryFile(path, data.empty() ? nullptr : data.data(), data.size(), error);
1623
}
1624
1625
bool FileSystem::WriteStringToFile(const char* path, std::string_view sv, Error* error)
1626
{
1627
ManagedCFilePtr fp = OpenManagedCFile(path, "wb", error);
1628
if (!fp)
1629
return false;
1630
1631
if (sv.length() > 0 && std::fwrite(sv.data(), 1u, sv.length(), fp.get()) != sv.length())
1632
{
1633
Error::SetErrno(error, "fwrite() failed: ", errno);
1634
return false;
1635
}
1636
1637
return true;
1638
}
1639
1640
bool FileSystem::EnsureDirectoryExists(const char* path, bool recursive, Error* error)
1641
{
1642
if (FileSystem::DirectoryExists(path))
1643
return true;
1644
1645
// if it fails to create, we're not going to be able to use it anyway
1646
return FileSystem::CreateDirectory(path, recursive, error);
1647
}
1648
1649
bool FileSystem::RecursiveDeleteDirectory(const char* path, Error* error)
1650
{
1651
FindResultsArray results;
1652
if (FindFiles(path, "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &results))
1653
{
1654
for (const FILESYSTEM_FIND_DATA& fd : results)
1655
{
1656
// don't recurse into symlinked directories, just remove the link itself
1657
if ((fd.Attributes & (FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY | FILESYSTEM_FILE_ATTRIBUTE_LINK)) ==
1658
FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)
1659
{
1660
if (!RecursiveDeleteDirectory(fd.FileName.c_str(), error))
1661
return false;
1662
}
1663
else
1664
{
1665
if (!DeleteFile(fd.FileName.c_str(), error))
1666
{
1667
Error::AddPrefixFmt(error, "Failed to delete {}: ", fd.FileName);
1668
return false;
1669
}
1670
}
1671
}
1672
}
1673
1674
return DeleteDirectory(path, error);
1675
}
1676
1677
bool FileSystem::CopyFilePath(const char* source, const char* destination, bool replace, Error* error)
1678
{
1679
#ifndef _WIN32
1680
// TODO: There's technically a race here between checking and opening the file..
1681
// But fopen doesn't specify any way to say "don't create if it exists"...
1682
if (!replace && FileExists(destination))
1683
{
1684
Error::SetStringView(error, "File already exists.");
1685
return false;
1686
}
1687
1688
auto in_fp = OpenManagedCFile(source, "rb", error);
1689
if (!in_fp)
1690
return false;
1691
1692
auto out_fp = OpenManagedCFile(destination, "wb", error);
1693
if (!out_fp)
1694
return false;
1695
1696
u8 buf[4096];
1697
while (!std::feof(in_fp.get()))
1698
{
1699
size_t bytes_in = std::fread(buf, 1, sizeof(buf), in_fp.get());
1700
if ((bytes_in == 0 && !std::feof(in_fp.get())) ||
1701
(bytes_in > 0 && std::fwrite(buf, 1, bytes_in, out_fp.get()) != bytes_in))
1702
{
1703
Error::SetErrno(error, "fread() or fwrite() failed: ", errno);
1704
out_fp.reset();
1705
DeleteFile(destination);
1706
return false;
1707
}
1708
}
1709
1710
if (std::fflush(out_fp.get()) != 0)
1711
{
1712
Error::SetErrno(error, "fflush() failed: ", errno);
1713
out_fp.reset();
1714
DeleteFile(destination);
1715
return false;
1716
}
1717
1718
return true;
1719
#else
1720
if (CopyFileW(GetWin32Path(source).c_str(), GetWin32Path(destination).c_str(), !replace))
1721
return true;
1722
1723
Error::SetWin32(error, "CopyFileW() failed(): ", GetLastError());
1724
return false;
1725
#endif
1726
}
1727
1728
#ifdef _WIN32
1729
1730
static u32 TranslateWin32Attributes(u32 w32attrs)
1731
{
1732
return ((w32attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY : 0) |
1733
((w32attrs & FILE_ATTRIBUTE_READONLY) ? FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY : 0) |
1734
((w32attrs & FILE_ATTRIBUTE_COMPRESSED) ? FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED : 0) |
1735
((w32attrs & FILE_ATTRIBUTE_REPARSE_POINT) ? FILESYSTEM_FILE_ATTRIBUTE_LINK : 0);
1736
}
1737
1738
static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path, const char* path, const char* pattern,
1739
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited)
1740
{
1741
std::string search_dir;
1742
if (path)
1743
{
1744
if (parent_path)
1745
search_dir = fmt::format("{}\\{}\\{}\\*", origin_path, parent_path, path);
1746
else
1747
search_dir = fmt::format("{}\\{}\\*", origin_path, path);
1748
}
1749
else
1750
{
1751
search_dir = fmt::format("{}\\*", origin_path);
1752
}
1753
1754
// holder for utf-8 conversion
1755
WIN32_FIND_DATAW wfd;
1756
std::string utf8_filename;
1757
utf8_filename.reserve((sizeof(wfd.cFileName) / sizeof(wfd.cFileName[0])) * 2);
1758
1759
const HANDLE hFind = FindFirstFileW(FileSystem::GetWin32Path(search_dir).c_str(), &wfd);
1760
if (hFind == INVALID_HANDLE_VALUE)
1761
return 0;
1762
1763
// small speed optimization for '*' case
1764
bool hasWildCards = false;
1765
bool wildCardMatchAll = false;
1766
u32 nFiles = 0;
1767
if (std::strpbrk(pattern, "*?"))
1768
{
1769
hasWildCards = true;
1770
wildCardMatchAll = !(std::strcmp(pattern, "*"));
1771
}
1772
1773
// iterate results
1774
do
1775
{
1776
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & FILESYSTEM_FIND_HIDDEN_FILES))
1777
continue;
1778
1779
if (wfd.cFileName[0] == L'.')
1780
{
1781
if (wfd.cFileName[1] == L'\0' || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == L'\0'))
1782
continue;
1783
}
1784
1785
if (!StringUtil::WideStringToUTF8String(utf8_filename, wfd.cFileName))
1786
continue;
1787
1788
FILESYSTEM_FIND_DATA outData;
1789
outData.Attributes = TranslateWin32Attributes(wfd.dwFileAttributes);
1790
1791
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1792
{
1793
if (flags & FILESYSTEM_FIND_RECURSIVE)
1794
{
1795
// check that we're not following an infinite symbolic link loop
1796
std::string real_recurse_dir;
1797
if (parent_path)
1798
real_recurse_dir =
1799
Path::RealPath(fmt::format("{}\\{}\\{}\\{}", origin_path, parent_path, path, utf8_filename));
1800
else if (path)
1801
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}\\{}", origin_path, path, utf8_filename));
1802
else
1803
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}", origin_path, utf8_filename));
1804
if (real_recurse_dir.empty() || std::find(visited.begin(), visited.end(), real_recurse_dir) == visited.end())
1805
{
1806
if (!real_recurse_dir.empty())
1807
visited.push_back(std::move(real_recurse_dir));
1808
1809
// recurse into this directory
1810
if (parent_path)
1811
{
1812
const std::string recurse_dir = fmt::format("{}\\{}", parent_path, path);
1813
nFiles += RecursiveFindFiles(origin_path, recurse_dir.c_str(), utf8_filename.c_str(), pattern, flags,
1814
results, visited);
1815
}
1816
else
1817
{
1818
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited);
1819
}
1820
}
1821
}
1822
1823
if (!(flags & FILESYSTEM_FIND_FOLDERS))
1824
continue;
1825
}
1826
else
1827
{
1828
if (!(flags & FILESYSTEM_FIND_FILES))
1829
continue;
1830
}
1831
1832
// match the filename
1833
if (hasWildCards)
1834
{
1835
if (!wildCardMatchAll && !StringUtil::WildcardMatch(utf8_filename.c_str(), pattern))
1836
continue;
1837
}
1838
else
1839
{
1840
if (std::strcmp(utf8_filename.c_str(), pattern) != 0)
1841
continue;
1842
}
1843
1844
// add file to list
1845
if (!(flags & FILESYSTEM_FIND_RELATIVE_PATHS))
1846
{
1847
if (parent_path)
1848
outData.FileName = fmt::format("{}\\{}\\{}\\{}", origin_path, parent_path, path, utf8_filename);
1849
else if (path)
1850
outData.FileName = fmt::format("{}\\{}\\{}", origin_path, path, utf8_filename);
1851
else
1852
outData.FileName = fmt::format("{}\\{}", origin_path, utf8_filename);
1853
}
1854
else
1855
{
1856
if (parent_path)
1857
outData.FileName = fmt::format("{}\\{}\\{}", parent_path, path, utf8_filename);
1858
else if (path)
1859
outData.FileName = fmt::format("{}\\{}", path, utf8_filename);
1860
else
1861
outData.FileName = utf8_filename;
1862
}
1863
1864
outData.CreationTime = ConvertFileTimeToUnixTime(wfd.ftCreationTime);
1865
outData.ModificationTime = ConvertFileTimeToUnixTime(wfd.ftLastWriteTime);
1866
outData.Size = (static_cast<u64>(wfd.nFileSizeHigh) << 32) | static_cast<u64>(wfd.nFileSizeLow);
1867
1868
nFiles++;
1869
results->push_back(std::move(outData));
1870
} while (FindNextFileW(hFind, &wfd) == TRUE);
1871
FindClose(hFind);
1872
1873
return nFiles;
1874
}
1875
1876
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
1877
{
1878
// clear result array
1879
if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY))
1880
results->clear();
1881
1882
// add self if recursive, we don't want to visit it twice
1883
std::vector<std::string> visited;
1884
if (flags & FILESYSTEM_FIND_RECURSIVE)
1885
{
1886
std::string real_path = Path::RealPath(path);
1887
if (!real_path.empty())
1888
visited.push_back(std::move(real_path));
1889
}
1890
1891
// enter the recursive function
1892
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) == 0)
1893
return false;
1894
1895
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
1896
{
1897
std::sort(results->begin(), results->end(), [](const FILESYSTEM_FIND_DATA& lhs, const FILESYSTEM_FIND_DATA& rhs) {
1898
// directories first
1899
if ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) !=
1900
(rhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY))
1901
{
1902
return ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != 0);
1903
}
1904
1905
return (StringUtil::Strcasecmp(lhs.FileName.c_str(), rhs.FileName.c_str()) < 0);
1906
});
1907
}
1908
1909
return true;
1910
}
1911
1912
static void TranslateStat64(struct stat* st, const struct _stat64& st64)
1913
{
1914
static constexpr __int64 MAX_SIZE = static_cast<__int64>(std::numeric_limits<decltype(st->st_size)>::max());
1915
st->st_dev = st64.st_dev;
1916
st->st_ino = st64.st_ino;
1917
st->st_mode = st64.st_mode;
1918
st->st_nlink = st64.st_nlink;
1919
st->st_uid = st64.st_uid;
1920
st->st_rdev = st64.st_rdev;
1921
st->st_size = static_cast<decltype(st->st_size)>((st64.st_size > MAX_SIZE) ? MAX_SIZE : st64.st_size);
1922
st->st_atime = static_cast<time_t>(st64.st_atime);
1923
st->st_mtime = static_cast<time_t>(st64.st_mtime);
1924
st->st_ctime = static_cast<time_t>(st64.st_ctime);
1925
}
1926
1927
bool FileSystem::StatFile(const char* path, struct stat* st, Error* error)
1928
{
1929
// convert to wide string
1930
const std::wstring wpath = GetWin32Path(path);
1931
if (wpath.empty()) [[unlikely]]
1932
{
1933
Error::SetStringView(error, "Path is empty.");
1934
return false;
1935
}
1936
1937
struct _stat64 st64;
1938
if (_wstati64(wpath.c_str(), &st64) != 0)
1939
return false;
1940
1941
TranslateStat64(st, st64);
1942
return true;
1943
}
1944
1945
bool FileSystem::StatFile(std::FILE* fp, struct stat* st, Error* error)
1946
{
1947
const int fd = _fileno(fp);
1948
if (fd < 0)
1949
{
1950
Error::SetErrno(error, "_fileno() failed: ", errno);
1951
return false;
1952
}
1953
1954
struct _stat64 st64;
1955
if (_fstati64(fd, &st64) != 0)
1956
{
1957
Error::SetErrno(error, "_fstati64() failed: ", errno);
1958
return false;
1959
}
1960
1961
TranslateStat64(st, st64);
1962
return true;
1963
}
1964
1965
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error)
1966
{
1967
// convert to wide string
1968
const std::wstring wpath = GetWin32Path(path);
1969
if (wpath.empty()) [[unlikely]]
1970
{
1971
Error::SetStringView(error, "Path is empty.");
1972
return false;
1973
}
1974
1975
// determine attributes for the path. if it's a directory, things have to be handled differently..
1976
DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
1977
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
1978
{
1979
Error::SetWin32(error, "GetFileAttributesW() failed: ", GetLastError());
1980
return false;
1981
}
1982
1983
// test if it is a directory
1984
HANDLE hFile;
1985
if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1986
{
1987
hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
1988
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1989
}
1990
else
1991
{
1992
hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
1993
OPEN_EXISTING, 0, nullptr);
1994
}
1995
1996
// createfile succeded?
1997
if (hFile == INVALID_HANDLE_VALUE)
1998
{
1999
Error::SetWin32(error, "CreateFileW() failed: ", GetLastError());
2000
return false;
2001
}
2002
2003
// use GetFileInformationByHandle
2004
BY_HANDLE_FILE_INFORMATION bhfi;
2005
if (GetFileInformationByHandle(hFile, &bhfi) == FALSE)
2006
{
2007
Error::SetWin32(error, "GetFileInformationByHandle() failed: ", GetLastError());
2008
CloseHandle(hFile);
2009
return false;
2010
}
2011
2012
// close handle
2013
CloseHandle(hFile);
2014
2015
// fill in the stat data
2016
sd->Attributes = TranslateWin32Attributes(bhfi.dwFileAttributes);
2017
sd->CreationTime = ConvertFileTimeToUnixTime(bhfi.ftCreationTime);
2018
sd->ModificationTime = ConvertFileTimeToUnixTime(bhfi.ftLastWriteTime);
2019
sd->Size = static_cast<s64>(((u64)bhfi.nFileSizeHigh) << 32 | (u64)bhfi.nFileSizeLow);
2020
return true;
2021
}
2022
2023
bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error)
2024
{
2025
const int fd = _fileno(fp);
2026
if (fd < 0)
2027
{
2028
Error::SetErrno(error, "_fileno() failed: ", errno);
2029
return false;
2030
}
2031
2032
struct _stat64 st;
2033
if (_fstati64(fd, &st) != 0)
2034
{
2035
Error::SetErrno(error, "_fstati64() failed: ", errno);
2036
return false;
2037
}
2038
2039
// parse attributes
2040
sd->CreationTime = st.st_ctime;
2041
sd->ModificationTime = st.st_mtime;
2042
sd->Attributes = 0;
2043
if ((st.st_mode & _S_IFMT) == _S_IFDIR)
2044
sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
2045
2046
// parse size
2047
if ((st.st_mode & _S_IFMT) == _S_IFREG)
2048
sd->Size = st.st_size;
2049
else
2050
sd->Size = 0;
2051
2052
return true;
2053
}
2054
2055
bool FileSystem::FileExists(const char* path)
2056
{
2057
// convert to wide string
2058
const std::wstring wpath = GetWin32Path(path);
2059
if (wpath.empty())
2060
return false;
2061
2062
// determine attributes for the path. if it's a directory, things have to be handled differently..
2063
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
2064
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
2065
return false;
2066
2067
return ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
2068
}
2069
2070
bool FileSystem::DirectoryExists(const char* path)
2071
{
2072
// convert to wide string
2073
const std::wstring wpath = GetWin32Path(path);
2074
if (wpath.empty())
2075
return false;
2076
2077
// determine attributes for the path. if it's a directory, things have to be handled differently..
2078
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
2079
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
2080
return false;
2081
2082
return ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
2083
}
2084
2085
bool FileSystem::IsRealDirectory(const char* path)
2086
{
2087
// convert to wide string
2088
const std::wstring wpath = GetWin32Path(path);
2089
if (wpath.empty())
2090
return false;
2091
2092
// determine attributes for the path. if it's a directory, things have to be handled differently..
2093
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
2094
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
2095
return false;
2096
2097
return ((fileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) != FILE_ATTRIBUTE_DIRECTORY);
2098
}
2099
2100
bool FileSystem::IsDirectoryEmpty(const char* path)
2101
{
2102
std::wstring wpath = GetWin32Path(path);
2103
wpath += L"\\*";
2104
2105
WIN32_FIND_DATAW wfd;
2106
HANDLE hFind = FindFirstFileW(wpath.c_str(), &wfd);
2107
2108
if (hFind == INVALID_HANDLE_VALUE)
2109
return true;
2110
2111
do
2112
{
2113
if (wfd.cFileName[0] == L'.')
2114
{
2115
if (wfd.cFileName[1] == L'\0' || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == L'\0'))
2116
continue;
2117
}
2118
2119
FindClose(hFind);
2120
return false;
2121
} while (FindNextFileW(hFind, &wfd));
2122
2123
FindClose(hFind);
2124
return true;
2125
}
2126
2127
bool FileSystem::CreateDirectory(const char* Path, bool Recursive, Error* error)
2128
{
2129
const std::wstring win32_path = GetWin32Path(Path);
2130
if (win32_path.empty()) [[unlikely]]
2131
{
2132
Error::SetStringView(error, "Path is empty.");
2133
return false;
2134
}
2135
2136
// try just flat-out, might work if there's no other segments that have to be made
2137
if (CreateDirectoryW(win32_path.c_str(), nullptr))
2138
return true;
2139
2140
DWORD lastError = GetLastError();
2141
if (lastError == ERROR_ALREADY_EXISTS)
2142
{
2143
// check the attributes
2144
const u32 Attributes = GetFileAttributesW(win32_path.c_str());
2145
if (Attributes != INVALID_FILE_ATTRIBUTES && Attributes & FILE_ATTRIBUTE_DIRECTORY)
2146
return true;
2147
}
2148
2149
if (!Recursive)
2150
{
2151
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
2152
return false;
2153
}
2154
2155
// check error
2156
if (lastError == ERROR_PATH_NOT_FOUND)
2157
{
2158
// part of the path does not exist, so we'll create the parent folders, then
2159
// the full path again.
2160
const size_t pathLength = std::strlen(Path);
2161
for (size_t i = 0; i < pathLength; i++)
2162
{
2163
if (Path[i] == '\\' || Path[i] == '/')
2164
{
2165
const std::string_view ppath(Path, i);
2166
const BOOL result = CreateDirectoryW(GetWin32Path(ppath).c_str(), nullptr);
2167
if (!result)
2168
{
2169
lastError = GetLastError();
2170
if (lastError != ERROR_ALREADY_EXISTS) // fine, continue to next path segment
2171
{
2172
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
2173
return false;
2174
}
2175
}
2176
}
2177
}
2178
2179
// re-create the end if it's not a separator, check / as well because windows can interpret them
2180
if (Path[pathLength - 1] != '\\' && Path[pathLength - 1] != '/')
2181
{
2182
const BOOL result = CreateDirectoryW(win32_path.c_str(), nullptr);
2183
if (!result)
2184
{
2185
lastError = GetLastError();
2186
if (lastError != ERROR_ALREADY_EXISTS)
2187
{
2188
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
2189
return false;
2190
}
2191
}
2192
}
2193
2194
// ok
2195
return true;
2196
}
2197
else
2198
{
2199
// unhandled error
2200
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
2201
return false;
2202
}
2203
}
2204
2205
bool FileSystem::DeleteFile(const char* path, Error* error)
2206
{
2207
const std::wstring wpath = GetWin32Path(path);
2208
2209
// Need to handle both links/junctions and files as per unix.
2210
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
2211
if (fileAttributes == INVALID_FILE_ATTRIBUTES ||
2212
((fileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) == FILE_ATTRIBUTE_DIRECTORY))
2213
{
2214
Error::SetStringView(error, "File does not exist.");
2215
return false;
2216
}
2217
2218
// if it's a junction/symlink, we need to use RemoveDirectory() instead
2219
if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2220
{
2221
if (!RemoveDirectoryW(wpath.c_str()))
2222
{
2223
Error::SetWin32(error, "RemoveDirectoryW() failed: ", GetLastError());
2224
return false;
2225
}
2226
}
2227
else
2228
{
2229
if (!DeleteFileW(wpath.c_str()))
2230
{
2231
Error::SetWin32(error, "DeleteFileW() failed: ", GetLastError());
2232
return false;
2233
}
2234
}
2235
2236
return true;
2237
}
2238
2239
bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* error)
2240
{
2241
const std::wstring old_wpath = GetWin32Path(old_path);
2242
const std::wstring new_wpath = GetWin32Path(new_path);
2243
2244
if (!MoveFileExW(old_wpath.c_str(), new_wpath.c_str(), MOVEFILE_REPLACE_EXISTING)) [[unlikely]]
2245
{
2246
Error::SetWin32(error, "MoveFileExW() failed: ", GetLastError());
2247
return false;
2248
}
2249
2250
return true;
2251
}
2252
2253
bool FileSystem::DeleteDirectory(const char* path, Error* error)
2254
{
2255
const std::wstring wpath = GetWin32Path(path);
2256
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
2257
if (fileAttributes == INVALID_FILE_ATTRIBUTES || !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY))
2258
{
2259
Error::SetStringView(error, "File does not exist.");
2260
return false;
2261
}
2262
2263
if (!RemoveDirectoryW(wpath.c_str()))
2264
{
2265
Error::SetWin32(error, "RemoveDirectoryW() failed: ", GetLastError());
2266
return false;
2267
}
2268
2269
return true;
2270
}
2271
2272
std::string FileSystem::GetProgramPath()
2273
{
2274
std::wstring buffer;
2275
buffer.resize(MAX_PATH);
2276
2277
// Fall back to the main module if this fails.
2278
HMODULE module = nullptr;
2279
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
2280
reinterpret_cast<LPCWSTR>(&GetProgramPath), &module);
2281
2282
for (;;)
2283
{
2284
DWORD nChars = GetModuleFileNameW(module, buffer.data(), static_cast<DWORD>(buffer.size()));
2285
if (nChars == static_cast<DWORD>(buffer.size()) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
2286
{
2287
buffer.resize(buffer.size() * 2);
2288
continue;
2289
}
2290
2291
buffer.resize(nChars);
2292
break;
2293
}
2294
2295
// Windows symlinks don't behave silly like Linux, so no need to RealPath() it.
2296
return StringUtil::WideStringToUTF8String(buffer);
2297
}
2298
2299
std::string FileSystem::GetWorkingDirectory()
2300
{
2301
DWORD required_size = GetCurrentDirectoryW(0, nullptr);
2302
if (!required_size)
2303
return {};
2304
2305
std::wstring buffer;
2306
buffer.resize(required_size - 1);
2307
2308
if (!GetCurrentDirectoryW(static_cast<DWORD>(buffer.size() + 1), buffer.data()))
2309
return {};
2310
2311
return StringUtil::WideStringToUTF8String(buffer);
2312
}
2313
2314
bool FileSystem::SetWorkingDirectory(const char* path)
2315
{
2316
const std::wstring wpath = GetWin32Path(path);
2317
return (SetCurrentDirectoryW(wpath.c_str()) == TRUE);
2318
}
2319
2320
bool FileSystem::SetPathCompression(const char* path, bool enable)
2321
{
2322
const std::wstring wpath = GetWin32Path(path);
2323
const DWORD attrs = GetFileAttributesW(wpath.c_str());
2324
if (attrs == INVALID_FILE_ATTRIBUTES)
2325
return false;
2326
2327
const bool isCompressed = (attrs & FILE_ATTRIBUTE_COMPRESSED) != 0;
2328
if (enable == isCompressed)
2329
{
2330
// already compressed/not compressed
2331
return true;
2332
}
2333
2334
const bool isFile = !(attrs & FILE_ATTRIBUTE_DIRECTORY);
2335
const DWORD flags = isFile ? FILE_ATTRIBUTE_NORMAL : (FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_DIRECTORY);
2336
2337
const HANDLE handle = CreateFileW(wpath.c_str(), FILE_GENERIC_WRITE | FILE_GENERIC_READ,
2338
FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, flags, nullptr);
2339
if (handle == INVALID_HANDLE_VALUE)
2340
return false;
2341
2342
DWORD bytesReturned = 0;
2343
DWORD compressMode = enable ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE;
2344
2345
bool result = DeviceIoControl(handle, FSCTL_SET_COMPRESSION, &compressMode, 2, nullptr, 0, &bytesReturned, nullptr);
2346
2347
CloseHandle(handle);
2348
return result;
2349
}
2350
2351
#elif !defined(__ANDROID__)
2352
2353
static u32 TranslateStatAttributes(struct stat& st, struct stat& st_link)
2354
{
2355
return (S_ISDIR(st.st_mode) ? FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY : 0) |
2356
(S_ISLNK(st_link.st_mode) ? FILESYSTEM_FILE_ATTRIBUTE_LINK : 0);
2357
}
2358
2359
static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern,
2360
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited)
2361
{
2362
std::string tempStr;
2363
if (Path)
2364
{
2365
if (ParentPath)
2366
tempStr = fmt::format("{}/{}/{}", OriginPath, ParentPath, Path);
2367
else
2368
tempStr = fmt::format("{}/{}", OriginPath, Path);
2369
}
2370
else
2371
{
2372
tempStr = fmt::format("{}", OriginPath);
2373
}
2374
2375
DIR* pDir = opendir(tempStr.c_str());
2376
if (!pDir)
2377
return 0;
2378
2379
// small speed optimization for '*' case
2380
bool hasWildCards = false;
2381
bool wildCardMatchAll = false;
2382
u32 nFiles = 0;
2383
if (std::strpbrk(Pattern, "*?"))
2384
{
2385
hasWildCards = true;
2386
wildCardMatchAll = (std::strcmp(Pattern, "*") == 0);
2387
}
2388
2389
// iterate results
2390
struct dirent* pDirEnt;
2391
while ((pDirEnt = readdir(pDir)) != nullptr)
2392
{
2393
if (pDirEnt->d_name[0] == '.')
2394
{
2395
if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0'))
2396
continue;
2397
2398
if (!(Flags & FILESYSTEM_FIND_HIDDEN_FILES))
2399
continue;
2400
}
2401
2402
std::string full_path;
2403
if (ParentPath)
2404
full_path = fmt::format("{}/{}/{}/{}", OriginPath, ParentPath, Path, pDirEnt->d_name);
2405
else if (Path)
2406
full_path = fmt::format("{}/{}/{}", OriginPath, Path, pDirEnt->d_name);
2407
else
2408
full_path = fmt::format("{}/{}", OriginPath, pDirEnt->d_name);
2409
2410
struct stat sDir, sDirLink;
2411
if (stat(full_path.c_str(), &sDir) < 0 || lstat(full_path.c_str(), &sDirLink) < 0)
2412
continue;
2413
2414
FILESYSTEM_FIND_DATA outData;
2415
outData.Attributes = TranslateStatAttributes(sDir, sDirLink);
2416
2417
if (S_ISDIR(sDir.st_mode))
2418
{
2419
if (Flags & FILESYSTEM_FIND_RECURSIVE)
2420
{
2421
// check that we're not following an infinite symbolic link loop
2422
if (std::string real_recurse_dir = Path::RealPath(full_path);
2423
real_recurse_dir.empty() || std::find(visited.begin(), visited.end(), real_recurse_dir) == visited.end())
2424
{
2425
if (!real_recurse_dir.empty())
2426
visited.push_back(std::move(real_recurse_dir));
2427
2428
// recurse into this directory
2429
if (ParentPath)
2430
{
2431
const std::string recursive_dir = fmt::format("{}/{}", ParentPath, Path);
2432
nFiles +=
2433
RecursiveFindFiles(OriginPath, recursive_dir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults, visited);
2434
}
2435
else
2436
{
2437
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited);
2438
}
2439
}
2440
}
2441
2442
if (!(Flags & FILESYSTEM_FIND_FOLDERS))
2443
continue;
2444
}
2445
else
2446
{
2447
if (!(Flags & FILESYSTEM_FIND_FILES))
2448
continue;
2449
}
2450
2451
outData.Size = static_cast<u64>(sDir.st_size);
2452
outData.CreationTime = sDir.st_ctime;
2453
outData.ModificationTime = sDir.st_mtime;
2454
2455
// match the filename
2456
if (hasWildCards)
2457
{
2458
if (!wildCardMatchAll && !StringUtil::WildcardMatch(pDirEnt->d_name, Pattern))
2459
continue;
2460
}
2461
else
2462
{
2463
if (std::strcmp(pDirEnt->d_name, Pattern) != 0)
2464
continue;
2465
}
2466
2467
// add file to list
2468
if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS))
2469
{
2470
outData.FileName = std::move(full_path);
2471
}
2472
else
2473
{
2474
if (ParentPath)
2475
outData.FileName = fmt::format("{}/{}/{}", ParentPath, Path, pDirEnt->d_name);
2476
else if (Path)
2477
outData.FileName = fmt::format("{}/{}", Path, pDirEnt->d_name);
2478
else
2479
outData.FileName = pDirEnt->d_name;
2480
}
2481
2482
nFiles++;
2483
pResults->push_back(std::move(outData));
2484
}
2485
2486
closedir(pDir);
2487
return nFiles;
2488
}
2489
2490
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
2491
{
2492
// clear result array
2493
if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY))
2494
results->clear();
2495
2496
// add self if recursive, we don't want to visit it twice
2497
std::vector<std::string> visited;
2498
if (flags & FILESYSTEM_FIND_RECURSIVE)
2499
{
2500
std::string real_path = Path::RealPath(path);
2501
if (!real_path.empty())
2502
visited.push_back(std::move(real_path));
2503
}
2504
2505
// enter the recursive function
2506
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) == 0)
2507
return false;
2508
2509
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
2510
{
2511
std::sort(results->begin(), results->end(), [](const FILESYSTEM_FIND_DATA& lhs, const FILESYSTEM_FIND_DATA& rhs) {
2512
// directories first
2513
if ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) !=
2514
(rhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY))
2515
{
2516
return ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != 0);
2517
}
2518
2519
return (StringUtil::Strcasecmp(lhs.FileName.c_str(), rhs.FileName.c_str()) < 0);
2520
});
2521
}
2522
2523
return true;
2524
}
2525
2526
bool FileSystem::StatFile(const char* path, struct stat* st, Error* error)
2527
{
2528
if (stat(path, st) != 0)
2529
{
2530
Error::SetErrno(error, "stat() failed: ", errno);
2531
return false;
2532
}
2533
2534
return true;
2535
}
2536
2537
bool FileSystem::StatFile(std::FILE* fp, struct stat* st, Error* error)
2538
{
2539
const int fd = fileno(fp);
2540
if (fd < 0)
2541
{
2542
Error::SetErrno(error, "fileno() failed: ", errno);
2543
return false;
2544
}
2545
2546
if (fstat(fd, st) != 0)
2547
{
2548
Error::SetErrno(error, "fstat() failed: ", errno);
2549
return false;
2550
}
2551
2552
return true;
2553
}
2554
2555
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error)
2556
{
2557
// stat file
2558
struct stat ssd, ssd_link;
2559
if (stat(path, &ssd) < 0 || lstat(path, &ssd_link) < 0)
2560
{
2561
Error::SetErrno(error, "stat() failed: ", errno);
2562
return false;
2563
}
2564
2565
// parse attributes
2566
sd->CreationTime = ssd.st_ctime;
2567
sd->ModificationTime = ssd.st_mtime;
2568
sd->Attributes = TranslateStatAttributes(ssd, ssd_link);
2569
sd->Size = S_ISREG(ssd.st_mode) ? ssd.st_size : 0;
2570
2571
// ok
2572
return true;
2573
}
2574
2575
bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error)
2576
{
2577
const int fd = fileno(fp);
2578
if (fd < 0)
2579
{
2580
Error::SetErrno(error, "fileno() failed: ", errno);
2581
return false;
2582
}
2583
2584
// stat file
2585
struct stat ssd;
2586
if (fstat(fd, &ssd) != 0)
2587
{
2588
Error::SetErrno(error, "stat() failed: ", errno);
2589
return false;
2590
}
2591
2592
// parse attributes
2593
sd->CreationTime = ssd.st_ctime;
2594
sd->ModificationTime = ssd.st_mtime;
2595
sd->Attributes = TranslateStatAttributes(ssd, ssd);
2596
sd->Size = S_ISREG(ssd.st_mode) ? ssd.st_size : 0;
2597
2598
return true;
2599
}
2600
2601
bool FileSystem::FileExists(const char* path)
2602
{
2603
struct stat sysStatData;
2604
if (stat(path, &sysStatData) < 0)
2605
return false;
2606
2607
if (S_ISDIR(sysStatData.st_mode))
2608
return false;
2609
else
2610
return true;
2611
}
2612
2613
bool FileSystem::DirectoryExists(const char* path)
2614
{
2615
struct stat sysStatData;
2616
if (stat(path, &sysStatData) < 0)
2617
return false;
2618
2619
return S_ISDIR(sysStatData.st_mode);
2620
}
2621
2622
bool FileSystem::IsRealDirectory(const char* path)
2623
{
2624
struct stat sysStatData;
2625
if (lstat(path, &sysStatData) < 0)
2626
return false;
2627
2628
return (S_ISDIR(sysStatData.st_mode) && !S_ISLNK(sysStatData.st_mode));
2629
}
2630
2631
bool FileSystem::IsDirectoryEmpty(const char* path)
2632
{
2633
DIR* pDir = opendir(path);
2634
if (pDir == nullptr)
2635
return true;
2636
2637
// iterate results
2638
struct dirent* pDirEnt;
2639
while ((pDirEnt = readdir(pDir)) != nullptr)
2640
{
2641
if (pDirEnt->d_name[0] == '.')
2642
{
2643
if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0'))
2644
continue;
2645
}
2646
2647
closedir(pDir);
2648
return false;
2649
}
2650
2651
closedir(pDir);
2652
return true;
2653
}
2654
2655
bool FileSystem::CreateDirectory(const char* path, bool recursive, Error* error)
2656
{
2657
// has a path
2658
const size_t pathLength = std::strlen(path);
2659
if (pathLength == 0)
2660
return false;
2661
2662
// try just flat-out, might work if there's no other segments that have to be made
2663
if (mkdir(path, 0777) == 0)
2664
return true;
2665
2666
// check error
2667
int lastError = errno;
2668
if (lastError == EEXIST)
2669
{
2670
// check the attributes
2671
struct stat sysStatData;
2672
if (stat(path, &sysStatData) == 0 && S_ISDIR(sysStatData.st_mode))
2673
return true;
2674
}
2675
2676
if (!recursive)
2677
{
2678
Error::SetErrno(error, "mkdir() failed: ", lastError);
2679
return false;
2680
}
2681
2682
else if (lastError == ENOENT)
2683
{
2684
// part of the path does not exist, so we'll create the parent folders, then
2685
// the full path again.
2686
std::string tempPath;
2687
tempPath.reserve(pathLength);
2688
2689
// create directories along the path
2690
for (size_t i = 0; i < pathLength; i++)
2691
{
2692
if (i > 0 && path[i] == '/')
2693
{
2694
if (mkdir(tempPath.c_str(), 0777) < 0)
2695
{
2696
lastError = errno;
2697
if (lastError != EEXIST) // fine, continue to next path segment
2698
{
2699
Error::SetErrno(error, "mkdir() failed: ", lastError);
2700
return false;
2701
}
2702
}
2703
}
2704
2705
tempPath.push_back(path[i]);
2706
}
2707
2708
// re-create the end if it's not a separator, check / as well because windows can interpret them
2709
if (path[pathLength - 1] != '/')
2710
{
2711
if (mkdir(path, 0777) < 0)
2712
{
2713
lastError = errno;
2714
if (lastError != EEXIST)
2715
{
2716
Error::SetErrno(error, "mkdir() failed: ", lastError);
2717
return false;
2718
}
2719
}
2720
}
2721
2722
// ok
2723
return true;
2724
}
2725
else
2726
{
2727
// unhandled error
2728
Error::SetErrno(error, "mkdir() failed: ", lastError);
2729
return false;
2730
}
2731
}
2732
2733
bool FileSystem::DeleteFile(const char* path, Error* error)
2734
{
2735
struct stat sd;
2736
if (lstat(path, &sd) != 0 || (S_ISDIR(sd.st_mode) && !S_ISLNK(sd.st_mode)))
2737
{
2738
Error::SetStringView(error, "File does not exist.");
2739
return false;
2740
}
2741
2742
if (unlink(path) != 0)
2743
{
2744
Error::SetErrno(error, "unlink() failed: ", errno);
2745
return false;
2746
}
2747
2748
return true;
2749
}
2750
2751
bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* error)
2752
{
2753
if (rename(old_path, new_path) != 0)
2754
{
2755
Error::SetErrno(error, "rename() failed: ", errno);
2756
return false;
2757
}
2758
2759
return true;
2760
}
2761
2762
bool FileSystem::DeleteDirectory(const char* path, Error* error)
2763
{
2764
struct stat sd;
2765
if (stat(path, &sd) != 0 || !S_ISDIR(sd.st_mode))
2766
return false;
2767
2768
// if it's a symlink, use unlink() instead
2769
if (S_ISLNK(sd.st_mode))
2770
{
2771
if (unlink(path) != 0)
2772
{
2773
Error::SetErrno(error, "unlink() failed: ", errno);
2774
return false;
2775
}
2776
}
2777
else
2778
{
2779
if (rmdir(path) != 0)
2780
{
2781
Error::SetErrno(error, "rmdir() failed: ", errno);
2782
return false;
2783
}
2784
}
2785
2786
return true;
2787
}
2788
2789
std::string FileSystem::GetProgramPath()
2790
{
2791
#if defined(__linux__)
2792
static const char* exe_path = "/proc/self/exe";
2793
2794
int curSize = PATH_MAX;
2795
char* buffer = static_cast<char*>(std::realloc(nullptr, curSize));
2796
for (;;)
2797
{
2798
int len = readlink(exe_path, buffer, curSize);
2799
if (len < 0)
2800
{
2801
std::free(buffer);
2802
return {};
2803
}
2804
else if (len < curSize)
2805
{
2806
buffer[len] = '\0';
2807
std::string ret(buffer, len);
2808
std::free(buffer);
2809
return ret;
2810
}
2811
2812
curSize *= 2;
2813
buffer = static_cast<char*>(std::realloc(buffer, curSize));
2814
}
2815
2816
#elif defined(__APPLE__)
2817
2818
int curSize = PATH_MAX;
2819
char* buffer = static_cast<char*>(std::realloc(nullptr, curSize));
2820
for (;;)
2821
{
2822
u32 nChars = curSize - 1;
2823
int res = _NSGetExecutablePath(buffer, &nChars);
2824
if (res == 0)
2825
{
2826
buffer[nChars] = 0;
2827
2828
char* resolvedBuffer = realpath(buffer, nullptr);
2829
if (resolvedBuffer == nullptr)
2830
{
2831
std::free(buffer);
2832
return {};
2833
}
2834
2835
std::string ret(buffer);
2836
std::free(buffer);
2837
return ret;
2838
}
2839
2840
curSize *= 2;
2841
buffer = static_cast<char*>(std::realloc(buffer, curSize + 1));
2842
}
2843
2844
#elif defined(__FreeBSD__)
2845
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
2846
char buffer[PATH_MAX];
2847
size_t cb = sizeof(buffer) - 1;
2848
int res = sysctl(mib, std::size(mib), buffer, &cb, nullptr, 0);
2849
if (res != 0)
2850
return {};
2851
2852
buffer[cb] = '\0';
2853
return buffer;
2854
#else
2855
return {};
2856
#endif
2857
}
2858
2859
std::string FileSystem::GetWorkingDirectory()
2860
{
2861
std::string buffer;
2862
buffer.resize(PATH_MAX);
2863
while (!getcwd(buffer.data(), buffer.size()))
2864
{
2865
if (errno != ERANGE)
2866
return {};
2867
2868
buffer.resize(buffer.size() * 2);
2869
}
2870
2871
buffer.resize(std::strlen(buffer.c_str())); // Remove excess nulls
2872
return buffer;
2873
}
2874
2875
bool FileSystem::SetWorkingDirectory(const char* path)
2876
{
2877
return (chdir(path) == 0);
2878
}
2879
2880
bool FileSystem::SetPathCompression(const char* path, bool enable)
2881
{
2882
return false;
2883
}
2884
2885
#endif
2886
2887
#ifdef HAS_POSIX_FILE_LOCK
2888
2889
static bool SetLock(int fd, bool lock, bool block, Error* error)
2890
{
2891
// We want to lock the whole file.
2892
const off_t offs = lseek(fd, 0, SEEK_CUR);
2893
if (offs < 0)
2894
{
2895
if (error)
2896
error->SetErrno("lseek() failed: ", errno);
2897
else
2898
ERROR_LOG("lseek({}) failed: {}", fd, errno);
2899
return false;
2900
}
2901
2902
if (offs != 0 && lseek(fd, 0, SEEK_SET) < 0)
2903
{
2904
if (error)
2905
error->SetErrno("lseek(0) failed: ", errno);
2906
else
2907
ERROR_LOG("lseek({}, 0) failed: {}", fd, errno);
2908
return false;
2909
}
2910
2911
// bloody signals...
2912
bool res;
2913
for (;;)
2914
{
2915
res = (lockf(fd, lock ? (block ? F_LOCK : F_TLOCK) : F_ULOCK, 0) == 0);
2916
if (!res && errno == EINTR)
2917
continue;
2918
else
2919
break;
2920
}
2921
2922
if (!res)
2923
{
2924
if (error)
2925
error->SetErrno("lockf() failed: ", errno);
2926
else
2927
ERROR_LOG("lockf() for {} failed: {}", lock ? "lock" : "unlock", errno);
2928
}
2929
2930
if (lseek(fd, offs, SEEK_SET) < 0)
2931
Panic("Repositioning file descriptor after lock failed.");
2932
2933
return res;
2934
}
2935
2936
FileSystem::POSIXLock::POSIXLock() : m_fd(-1)
2937
{
2938
}
2939
2940
FileSystem::POSIXLock::POSIXLock(int fd, bool block, Error* error) : m_fd(fd)
2941
{
2942
if (!SetLock(m_fd, true, block, error))
2943
m_fd = -1;
2944
}
2945
2946
FileSystem::POSIXLock::POSIXLock(std::FILE* fp, bool block, Error* error) : m_fd(fileno(fp))
2947
{
2948
if (!SetLock(m_fd, true, block, error))
2949
m_fd = -1;
2950
}
2951
2952
FileSystem::POSIXLock::POSIXLock(POSIXLock&& move)
2953
{
2954
m_fd = std::exchange(move.m_fd, -1);
2955
}
2956
2957
FileSystem::POSIXLock::~POSIXLock()
2958
{
2959
Unlock();
2960
}
2961
2962
void FileSystem::POSIXLock::Unlock()
2963
{
2964
if (m_fd >= 0)
2965
{
2966
SetLock(m_fd, false, true, nullptr);
2967
m_fd = -1;
2968
}
2969
}
2970
2971
FileSystem::POSIXLock& FileSystem::POSIXLock::operator=(POSIXLock&& move)
2972
{
2973
m_fd = std::exchange(move.m_fd, -1);
2974
return *this;
2975
}
2976
2977
#endif
2978
2979