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