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