Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/File/FileUtil.cpp
5657 views
1
// Copyright (C) 2003 Dolphin Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official SVN repository and contact information can be found at
16
// http://code.google.com/p/dolphin-emu/
17
18
#if defined(_MSC_VER)
19
#pragma warning(disable:4091) // workaround bug in VS2015 headers
20
#ifndef UNICODE
21
#error Win32 build requires a unicode build
22
#endif
23
#else
24
#define _POSIX_SOURCE
25
#define _LARGE_TIME_API
26
#endif
27
28
#include "ppsspp_config.h"
29
30
#include "android/jni/app-android.h"
31
32
#include <cstring>
33
#include <ctime>
34
#include <memory>
35
36
#include <sys/types.h>
37
38
#include "Common/Log.h"
39
#include "Common/LogReporting.h"
40
#include "Common/File/AndroidContentURI.h"
41
#include "Common/File/FileUtil.h"
42
#include "Common/StringUtils.h"
43
#include "Common/TimeUtil.h"
44
#include "Common/SysError.h"
45
#include "Common/System/Request.h"
46
47
#ifdef _WIN32
48
#include "Common/CommonWindows.h"
49
#include <sys/utime.h>
50
#include <shellapi.h>
51
#include <io.h>
52
#include <direct.h> // getcwd
53
#if PPSSPP_PLATFORM(UWP)
54
#include <fileapifromapp.h>
55
#include "UWP/UWPHelpers/StorageManager.h"
56
#endif
57
#else
58
#include <sys/param.h>
59
#include <sys/types.h>
60
#include <dirent.h>
61
#include <errno.h>
62
#include <stdlib.h>
63
#include <unistd.h>
64
#include <utime.h>
65
#endif
66
67
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__)
68
#include <sys/sysctl.h> // KERN_PROC_PATHNAME
69
#endif
70
71
#if defined(__APPLE__)
72
#include <CoreFoundation/CFString.h>
73
#include <CoreFoundation/CFURL.h>
74
#include <CoreFoundation/CFBundle.h>
75
#if !PPSSPP_PLATFORM(IOS)
76
#include <mach-o/dyld.h>
77
#endif // !PPSSPP_PLATFORM(IOS)
78
#endif // __APPLE__
79
80
#ifdef HAVE_LIBRETRO_VFS
81
#include <file/file_path.h>
82
#endif
83
84
#include "Common/Data/Encoding/Utf8.h"
85
86
#include <sys/stat.h>
87
88
// NOTE: There's another one in DirListing.cpp.
89
#ifdef _WIN32
90
constexpr bool SIMULATE_SLOW_IO = false;
91
#else
92
constexpr bool SIMULATE_SLOW_IO = false;
93
#endif
94
constexpr bool LOG_IO = false;
95
96
#ifndef S_ISDIR
97
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
98
#endif
99
100
#if !defined(__linux__) && !defined(_WIN32) && !defined(__QNX__)
101
#define stat64 stat
102
#define fstat64 fstat
103
#endif
104
105
#define DIR_SEP "/"
106
#ifdef _WIN32
107
#define DIR_SEP_CHRS "/\\"
108
#else
109
#define DIR_SEP_CHRS "/"
110
#endif
111
112
#ifdef HAVE_LIBRETRO_VFS
113
static retro_vfs_mkdir_t LibretroMkdirCallback = nullptr;
114
115
// Creates a directory at the given path. Parent directories are not created if
116
// they are missing. If the libretro VFS is supported by the libretro frontend,
117
// it will be used; if the libretro VFS is not supported by the frontend, the
118
// mkdir function will be used instead. Returns 0 if the directory did not exist
119
// and was successfully created, -2 if the directory already exists or -1 if
120
// some other error occurred.
121
static int LibretroMkdir(const char *path) noexcept {
122
return LibretroMkdirCallback != nullptr ? LibretroMkdirCallback(path) : retro_vfs_mkdir_impl(path);
123
}
124
#endif
125
126
// This namespace has various generic functions related to files and paths.
127
// The code still needs a ton of cleanup.
128
// REMEMBER: strdup considered harmful!
129
namespace File {
130
131
#ifdef HAVE_LIBRETRO_VFS
132
void InitLibretroVFS(const struct retro_vfs_interface_info *vfs) noexcept {
133
filestream_vfs_init(vfs);
134
path_vfs_init(vfs);
135
LibretroMkdirCallback = vfs->required_interface_version >= 3 ? vfs->iface->mkdir : nullptr;
136
}
137
#endif
138
139
FILE *OpenCFile(const Path &path, const char *mode) {
140
if (LOG_IO) {
141
INFO_LOG(Log::IO, "OpenCFile %s, %s", path.c_str(), mode);
142
}
143
if (SIMULATE_SLOW_IO) {
144
sleep_ms(300, "slow-io-sim");
145
}
146
switch (path.Type()) {
147
case PathType::NATIVE:
148
break;
149
#ifndef HAVE_LIBRETRO_VFS
150
case PathType::CONTENT_URI:
151
// We're gonna need some error codes..
152
if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) {
153
DEBUG_LOG(Log::IO, "Opening content file for read: '%s'", path.c_str());
154
// Read, let's support this - easy one.
155
int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ);
156
if (descriptor < 0) {
157
return nullptr;
158
}
159
return fdopen(descriptor, "rb");
160
} else if (!strcmp(mode, "w") || !strcmp(mode, "wb") || !strcmp(mode, "wt") || !strcmp(mode, "at") || !strcmp(mode, "a")) {
161
// Need to be able to create the file here if it doesn't exist.
162
// NOTE: The existance check is important, otherwise Android will create a numbered file by the side!
163
// This is also a terrible possible data race, ugh. Anyway...
164
// Not exactly sure which abstractions are best, let's start simple.
165
if (!File::Exists(path)) {
166
DEBUG_LOG(Log::IO, "OpenCFile(%s): Opening content file for write. Doesn't exist, creating empty and reopening.", path.c_str());
167
std::string name = path.GetFilename();
168
if (path.CanNavigateUp()) {
169
Path parent = path.NavigateUp();
170
if (Android_CreateFile(parent.ToString(), name) != StorageError::SUCCESS) {
171
WARN_LOG(Log::IO, "Failed to create file '%s' in '%s'", name.c_str(), parent.c_str());
172
return nullptr;
173
}
174
} else {
175
INFO_LOG(Log::IO, "Failed to navigate up to create file: %s", path.c_str());
176
return nullptr;
177
}
178
} else {
179
DEBUG_LOG(Log::IO, "OpenCFile(%s): Opening existing content file for write (truncating). Requested mode: '%s'", path.c_str(), mode);
180
}
181
182
// TODO: Support append modes and stuff... For now let's go with the most common one.
183
Android_OpenContentUriMode openMode = Android_OpenContentUriMode::READ_WRITE_TRUNCATE;
184
const char *fmode = "wb";
185
if (!strcmp(mode, "at") || !strcmp(mode, "a")) {
186
openMode = Android_OpenContentUriMode::READ_WRITE;
187
fmode = "ab";
188
}
189
int descriptor = Android_OpenContentUriFd(path.ToString(), openMode);
190
if (descriptor < 0) {
191
INFO_LOG(Log::IO, "Opening '%s' for write failed", path.ToString().c_str());
192
return nullptr;
193
}
194
FILE *f = fdopen(descriptor, fmode);
195
if (f && (!strcmp(mode, "at") || !strcmp(mode, "a"))) {
196
// Append mode - not sure we got a "true" append mode, so seek to the end.
197
Fseek(f, 0, SEEK_END);
198
}
199
return f;
200
} else {
201
ERROR_LOG(Log::IO, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode);
202
return nullptr;
203
}
204
break;
205
#endif
206
default:
207
ERROR_LOG(Log::IO, "OpenCFile(%s): PathType not yet supported", path.c_str());
208
return nullptr;
209
}
210
211
#ifdef HAVE_LIBRETRO_VFS
212
if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) {
213
FILE *f = filestream_open(path.c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
214
INFO_LOG(Log::IO, "OpenCFile(%s): Opening content file for read (libretro vfs): %s", path.c_str(), f ? "ok" : "null");
215
return f;
216
} else if (!strcmp(mode, "w") || !strcmp(mode, "wb") || !strcmp(mode, "wt") || !strcmp(mode, "at") || !strcmp(mode, "a")) {
217
bool append = !strcmp(mode, "at") || !strcmp(mode, "a");
218
FILE *f = filestream_open(path.c_str(), append && Exists(path) ? RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
219
INFO_LOG(Log::IO, "OpenCFile(%s): Opening content file for write (libretro vfs): %s", path.c_str(), f ? "ok" : "null");
220
if (f != nullptr && append) {
221
Fseek(f, 0, SEEK_END);
222
}
223
return f;
224
} else {
225
ERROR_LOG(Log::IO, "OpenCFile(%s): Mode not yet supported (libretro vfs): %s", path.c_str(), mode);
226
return nullptr;
227
}
228
#elif defined(_WIN32) && defined(UNICODE)
229
#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
230
// We shouldn't use _wfopen here,
231
// this function is not allowed to read outside Local and Installation folders
232
// FileSystem (broadFileSystemAccess) doesn't apply on _wfopen
233
// if we have custom memory stick location _wfopen will return null
234
// 'GetFileStreamFromApp' will convert 'mode' to [access, share, creationDisposition]
235
// then it will call 'CreateFile2FromAppW' -> convert HANDLE to FILE*
236
FILE* file = GetFileStreamFromApp(path.ToString(), mode);
237
return file;
238
#else
239
return _wfopen(path.ToWString().c_str(), ConvertUTF8ToWString(mode).c_str());
240
#endif
241
#else
242
return fopen(path.c_str(), mode);
243
#endif
244
}
245
246
static std::string OpenFlagToString(OpenFlag flags) {
247
std::string s;
248
if (flags & OPEN_READ)
249
s += "READ|";
250
if (flags & OPEN_WRITE)
251
s += "WRITE|";
252
if (flags & OPEN_APPEND)
253
s += "APPEND|";
254
if (flags & OPEN_CREATE)
255
s += "CREATE|";
256
if (flags & OPEN_TRUNCATE)
257
s += "TRUNCATE|";
258
if (!s.empty()) {
259
s.pop_back(); // Remove trailing separator.
260
}
261
return s;
262
}
263
264
int OpenFD(const Path &path, OpenFlag flags) {
265
if (LOG_IO) {
266
INFO_LOG(Log::IO, "OpenFD %s, %d", path.c_str(), flags);
267
}
268
if (SIMULATE_SLOW_IO) {
269
sleep_ms(300, "slow-io-sim");
270
}
271
272
switch (path.Type()) {
273
case PathType::CONTENT_URI:
274
break;
275
default:
276
ERROR_LOG(Log::IO, "OpenFD: Only supports Content URI paths. Not '%s' (%s)!", path.c_str(), OpenFlagToString(flags).c_str());
277
// Not yet supported - use other paths.
278
return -1;
279
}
280
281
bool knownExists = false;
282
283
if (flags & OPEN_CREATE) {
284
if (!File::Exists(path)) {
285
INFO_LOG(Log::IO, "OpenFD(%s): Creating file.", path.c_str());
286
std::string name = path.GetFilename();
287
if (path.CanNavigateUp()) {
288
Path parent = path.NavigateUp();
289
if (Android_CreateFile(parent.ToString(), name) != StorageError::SUCCESS) {
290
WARN_LOG(Log::IO, "OpenFD: Failed to create file '%s' in '%s'", name.c_str(), parent.c_str());
291
return -1;
292
}
293
knownExists = true;
294
} else {
295
INFO_LOG(Log::IO, "Failed to navigate up to create file: %s", path.c_str());
296
return -1;
297
}
298
} else {
299
INFO_LOG(Log::IO, "OpenCFile(%s): Opening existing content file ('%s')", path.c_str(), OpenFlagToString(flags).c_str());
300
knownExists = true;
301
}
302
}
303
304
Android_OpenContentUriMode mode;
305
if (flags == OPEN_READ) { // Intentionally not a bitfield check.
306
mode = Android_OpenContentUriMode::READ;
307
} else if (flags & OPEN_WRITE) {
308
if (flags & OPEN_TRUNCATE) {
309
mode = Android_OpenContentUriMode::READ_WRITE_TRUNCATE;
310
} else {
311
mode = Android_OpenContentUriMode::READ_WRITE;
312
}
313
// TODO: Maybe better checking of additional flags here.
314
} else {
315
// TODO: Add support for more modes if possible.
316
ERROR_LOG_REPORT_ONCE(openFlagNotSupported, Log::IO, "OpenFlag %s not yet supported", OpenFlagToString(flags).c_str());
317
return -1;
318
}
319
320
INFO_LOG(Log::IO, "Android_OpenContentUriFd: %s (%s)", path.c_str(), OpenFlagToString(flags).c_str());
321
int descriptor = Android_OpenContentUriFd(path.ToString(), mode);
322
if (descriptor < 0) {
323
// File probably just doesn't exist. No biggie.
324
if (knownExists) {
325
ERROR_LOG(Log::IO, "Android_OpenContentUriFd failed for existing file: '%s'", path.c_str());
326
} else {
327
INFO_LOG(Log::IO, "Android_OpenContentUriFd failed, probably doesn't exist: '%s'", path.c_str());
328
}
329
} else if (flags & OPEN_APPEND) {
330
// Simply seek to the end of the file to simulate append mode.
331
lseek(descriptor, 0, SEEK_END);
332
}
333
return descriptor;
334
}
335
336
void CloseFD(int fd) {
337
#if PPSSPP_PLATFORM(ANDROID)
338
close(fd);
339
#endif
340
}
341
342
343
#ifdef _WIN32
344
static bool ResolvePathVista(const std::wstring &path, wchar_t *buf, DWORD bufSize) {
345
typedef DWORD(WINAPI *getFinalPathNameByHandleW_f)(HANDLE hFile, LPWSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags);
346
static getFinalPathNameByHandleW_f getFinalPathNameByHandleW = nullptr;
347
348
#if PPSSPP_PLATFORM(UWP)
349
getFinalPathNameByHandleW = &GetFinalPathNameByHandleW;
350
#else
351
if (!getFinalPathNameByHandleW) {
352
HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
353
if (kernel32)
354
getFinalPathNameByHandleW = (getFinalPathNameByHandleW_f)GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
355
}
356
#endif
357
358
if (getFinalPathNameByHandleW) {
359
#if PPSSPP_PLATFORM(UWP)
360
HANDLE hFile = CreateFile2FromAppW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr);
361
#else
362
HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
363
#endif
364
if (hFile == INVALID_HANDLE_VALUE)
365
return false;
366
367
DWORD result = getFinalPathNameByHandleW(hFile, buf, bufSize - 1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
368
CloseHandle(hFile);
369
370
return result < bufSize && result != 0;
371
}
372
373
return false;
374
}
375
#endif
376
377
// Canonicalize the given path, resolving symlinks, relative paths, etc.
378
std::string ResolvePath(std::string_view path) {
379
if (LOG_IO) {
380
INFO_LOG(Log::IO, "ResolvePath %.*s", (int)path.size(), path.data());
381
}
382
if (SIMULATE_SLOW_IO) {
383
sleep_ms(100, "slow-io-sim");
384
}
385
386
if (startsWith(path, "http://") || startsWith(path, "https://")) {
387
return std::string(path);
388
}
389
390
if (Android_IsContentUri(path)) {
391
// Nothing to do? We consider these to only have one canonical form.
392
return std::string(path);
393
}
394
395
#ifdef _WIN32
396
static const int BUF_SIZE = 32768;
397
wchar_t *buf = new wchar_t[BUF_SIZE] {};
398
399
std::wstring input = ConvertUTF8ToWString(path);
400
// Try to resolve symlinks (such as Documents aliases, etc.) if possible on Vista and higher.
401
// For some paths and remote shares, this may fail, so fall back.
402
if (!ResolvePathVista(input, buf, BUF_SIZE)) {
403
wchar_t *longBuf = new wchar_t[BUF_SIZE] {};
404
405
int result = GetLongPathNameW(input.c_str(), longBuf, BUF_SIZE - 1);
406
if (result >= BUF_SIZE || result == 0)
407
wcscpy_s(longBuf, BUF_SIZE - 1, input.c_str());
408
409
result = GetFullPathNameW(longBuf, BUF_SIZE - 1, buf, nullptr);
410
if (result >= BUF_SIZE || result == 0)
411
wcscpy_s(buf, BUF_SIZE - 1, input.c_str());
412
413
delete [] longBuf;
414
}
415
416
// Normalize slashes just in case.
417
for (int i = 0; i < BUF_SIZE; ++i) {
418
if (buf[i] == '\\')
419
buf[i] = '/';
420
else if (buf[i] == '\0')
421
break;
422
}
423
424
// Undo the \\?\C:\ syntax that's normally returned (after normalization of slashes.)
425
std::string output = ConvertWStringToUTF8(buf);
426
if (buf[0] == '/' && buf[1] == '/' && buf[2] == '?' && buf[3] == '/' && isalpha(buf[4]) && buf[5] == ':')
427
output = output.substr(4);
428
delete [] buf;
429
return output;
430
431
#elif PPSSPP_PLATFORM(IOS)
432
// Resolving has wacky effects on documents paths.
433
return std::string(path);
434
#else
435
std::unique_ptr<char[]> buf(new char[PATH_MAX + 32768]);
436
std::string spath(path);
437
if (realpath(spath.c_str(), buf.get()) == nullptr)
438
return spath;
439
return std::string(buf.get());
440
#endif
441
}
442
443
static int64_t RecursiveSize(const Path &path) {
444
// TODO: Some file systems can optimize this.
445
std::vector<FileInfo> fileInfo;
446
if (!GetFilesInDir(path, &fileInfo, nullptr, GETFILES_GETHIDDEN)) {
447
return -1;
448
}
449
int64_t sizeSum = 0;
450
for (const auto &file : fileInfo) {
451
if (file.isDirectory) {
452
sizeSum += RecursiveSize(file.fullName);
453
} else {
454
sizeSum += file.size;
455
}
456
}
457
return sizeSum;
458
}
459
460
uint64_t ComputeRecursiveDirectorySize(const Path &path) {
461
if (path.Type() == PathType::CONTENT_URI) {
462
return Android_ComputeRecursiveDirectorySize(path.ToString());
463
}
464
465
// Generic solution.
466
return RecursiveSize(path);
467
}
468
469
// Returns true if file filename exists. Will return true on directories.
470
bool ExistsInDir(const Path &path, std::string_view filename) {
471
return Exists(path / filename);
472
}
473
474
bool Exists(const Path &path) {
475
if (LOG_IO) {
476
INFO_LOG(Log::IO, "Exists %s", path.ToVisualString().c_str());
477
}
478
if (SIMULATE_SLOW_IO) {
479
sleep_ms(200, "slow-io-sim");
480
}
481
482
if (path.Type() == PathType::CONTENT_URI) {
483
return Android_FileExists(path.c_str());
484
}
485
486
#ifdef HAVE_LIBRETRO_VFS
487
return path_is_valid(path.c_str());
488
#elif defined(_WIN32)
489
490
// Make sure Windows will no longer handle critical errors, which means no annoying "No disk" dialog
491
#if !PPSSPP_PLATFORM(UWP)
492
int OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
493
#endif
494
WIN32_FILE_ATTRIBUTE_DATA data{};
495
#if PPSSPP_PLATFORM(UWP)
496
if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
497
return false;
498
}
499
#else
500
if (!GetFileAttributesEx(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
501
return false;
502
}
503
#endif
504
#if !PPSSPP_PLATFORM(UWP)
505
SetErrorMode(OldMode);
506
#endif
507
return true;
508
#else // !WIN32
509
struct stat file_info{};
510
return stat(path.c_str(), &file_info) == 0;
511
#endif
512
}
513
514
// Returns true if filename exists and is a directory
515
bool IsDirectory(const Path &path) {
516
if (LOG_IO) {
517
INFO_LOG(Log::IO, "IsDirectory %s", path.c_str());
518
}
519
if (SIMULATE_SLOW_IO) {
520
sleep_ms(100, "slow-io-sim");
521
}
522
523
switch (path.Type()) {
524
case PathType::NATIVE:
525
break; // OK
526
case PathType::CONTENT_URI:
527
{
528
FileInfo info;
529
if (!Android_GetFileInfo(path.ToString(), &info)) {
530
return false;
531
}
532
return info.exists && info.isDirectory;
533
}
534
default:
535
return false;
536
}
537
538
#ifdef HAVE_LIBRETRO_VFS
539
return path_is_directory(path.c_str());
540
#elif defined(_WIN32)
541
WIN32_FILE_ATTRIBUTE_DATA data{};
542
#if PPSSPP_PLATFORM(UWP)
543
if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
544
#else
545
if (!GetFileAttributesEx(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
546
#endif
547
auto err = GetLastError();
548
if (err != ERROR_FILE_NOT_FOUND) {
549
WARN_LOG(Log::IO, "GetFileAttributes failed on %s: %08x %s", path.ToVisualString().c_str(), (uint32_t)err, GetStringErrorMsg(err).c_str());
550
}
551
return false;
552
}
553
DWORD result = data.dwFileAttributes;
554
return (result & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
555
#else
556
std::string copy = path.ToString();
557
struct stat file_info{};
558
int result = stat(copy.c_str(), &file_info);
559
if (result < 0) {
560
WARN_LOG(Log::IO, "IsDirectory: stat failed on %s: %s", copy.c_str(), GetLastErrorMsg().c_str());
561
return false;
562
}
563
return S_ISDIR(file_info.st_mode);
564
#endif
565
}
566
567
// Deletes a given filename, return true on success
568
// Doesn't supports deleting a directory
569
bool Delete(const Path &filename, bool quiet) {
570
if (SIMULATE_SLOW_IO) {
571
sleep_ms(200, "slow-io-sim");
572
}
573
switch (filename.Type()) {
574
case PathType::NATIVE:
575
break; // OK
576
case PathType::CONTENT_URI:
577
return Android_RemoveFile(filename.ToString()) == StorageError::SUCCESS;
578
default:
579
return false;
580
}
581
582
// Return true because we care about the file no
583
// being there, not the actual delete.
584
if (!Exists(filename)) {
585
if (!quiet) {
586
WARN_LOG(Log::IO, "Delete: '%s' already does not exist", filename.c_str());
587
}
588
return true;
589
}
590
591
// We can't delete a directory
592
if (IsDirectory(filename)) {
593
WARN_LOG(Log::IO, "Delete failed: '%s' is a directory", filename.c_str());
594
return false;
595
}
596
597
#ifdef HAVE_LIBRETRO_VFS
598
if (filestream_delete(filename.c_str()) != 0) {
599
WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s", filename.c_str());
600
return false;
601
}
602
#elif defined(_WIN32)
603
#if PPSSPP_PLATFORM(UWP)
604
if (!DeleteFileFromAppW(filename.ToWString().c_str())) {
605
WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str());
606
return false;
607
}
608
#else
609
if (!DeleteFile(filename.ToWString().c_str())) {
610
WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str());
611
return false;
612
}
613
#endif
614
#else
615
if (unlink(filename.c_str()) == -1) {
616
WARN_LOG(Log::IO, "Delete: unlink failed on %s: %s",
617
filename.c_str(), GetLastErrorMsg().c_str());
618
return false;
619
}
620
#endif
621
622
INFO_LOG(Log::IO, "Delete: file %s was deleted.", filename.c_str());
623
return true;
624
}
625
626
// Returns true if successful, or path already exists.
627
bool CreateDir(const Path &path) {
628
if (SIMULATE_SLOW_IO) {
629
sleep_ms(100, "slow-io-sim");
630
INFO_LOG(Log::IO, "CreateDir %s", path.c_str());
631
}
632
switch (path.Type()) {
633
case PathType::NATIVE:
634
break; // OK
635
case PathType::CONTENT_URI:
636
{
637
// NOTE: The Android storage API will simply create a renamed directory (append a number) if it already exists.
638
// We want to avoid that, so let's just return true if the directory already is there.
639
if (File::Exists(path)) {
640
return true;
641
}
642
643
// Convert it to a "CreateDirIn" call, if possible, since that's
644
// what we can do with the storage API.
645
AndroidContentURI uri(path.ToString());
646
std::string newDirName = uri.GetLastPart();
647
if (uri.NavigateUp()) {
648
INFO_LOG(Log::IO, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str());
649
return Android_CreateDirectory(uri.ToString(), newDirName) == StorageError::SUCCESS;
650
} else {
651
// Bad path - can't create this directory.
652
WARN_LOG(Log::IO, "CreateDir failed: '%s'", path.c_str());
653
return false;
654
}
655
break;
656
}
657
default:
658
return false;
659
}
660
661
DEBUG_LOG(Log::IO, "CreateDir('%s')", path.c_str());
662
#ifdef HAVE_LIBRETRO_VFS
663
switch (LibretroMkdir(path.ToString().c_str())) {
664
case -2:
665
DEBUG_LOG(Log::IO, "CreateDir: mkdir failed on %s: already exists", path.c_str());
666
case 0:
667
return true;
668
default:
669
ERROR_LOG(Log::IO, "CreateDir: mkdir failed on %s", path.c_str());
670
return false;
671
}
672
#elif defined(_WIN32)
673
#if PPSSPP_PLATFORM(UWP)
674
if (CreateDirectoryFromAppW(path.ToWString().c_str(), NULL))
675
return true;
676
#else
677
if (::CreateDirectory(path.ToWString().c_str(), NULL))
678
return true;
679
#endif
680
681
DWORD error = GetLastError();
682
if (error == ERROR_ALREADY_EXISTS) {
683
DEBUG_LOG(Log::IO, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str());
684
return true;
685
}
686
ERROR_LOG(Log::IO, "CreateDir: CreateDirectory failed on %s: %08x %s", path.c_str(), (uint32_t)error, GetStringErrorMsg(error).c_str());
687
return false;
688
#else
689
if (mkdir(path.ToString().c_str(), 0755) == 0) {
690
return true;
691
}
692
693
int err = errno;
694
if (err == EEXIST) {
695
DEBUG_LOG(Log::IO, "CreateDir: mkdir failed on %s: already exists", path.c_str());
696
return true;
697
}
698
699
ERROR_LOG(Log::IO, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err));
700
return false;
701
#endif
702
}
703
704
// Creates the full path of fullPath returns true on success
705
bool CreateFullPath(const Path &path) {
706
if (File::Exists(path)) {
707
DEBUG_LOG(Log::IO, "CreateFullPath: path exists %s", path.ToVisualString().c_str());
708
return true;
709
}
710
711
switch (path.Type()) {
712
case PathType::NATIVE:
713
case PathType::CONTENT_URI:
714
break; // OK
715
default:
716
ERROR_LOG(Log::IO, "CreateFullPath(%s): Not yet supported", path.ToVisualString().c_str());
717
return false;
718
}
719
720
// The below code is entirely agnostic of path format.
721
722
Path root = path.GetRootVolume();
723
724
std::string diff;
725
if (!root.ComputePathTo(path, diff)) {
726
return false;
727
}
728
729
std::vector<std::string_view> parts;
730
if (!diff.empty()) {
731
SplitString(diff, '/', parts);
732
}
733
734
// Probably not necessary sanity check, ported from the old code.
735
if (parts.size() > 100) {
736
ERROR_LOG(Log::IO, "CreateFullPath: directory structure too deep");
737
return false;
738
}
739
740
Path curPath = root;
741
for (auto part : parts) {
742
curPath /= part;
743
File::CreateDir(curPath);
744
}
745
746
return true;
747
}
748
749
// renames file srcFilename to destFilename, returns true on success
750
bool Rename(const Path &srcFilename, const Path &destFilename) {
751
if (LOG_IO) {
752
INFO_LOG(Log::IO, "Rename %s -> %s", srcFilename.c_str(), destFilename.c_str());
753
}
754
if (SIMULATE_SLOW_IO) {
755
sleep_ms(100, "slow-io-sim");
756
}
757
758
if (srcFilename.Type() != destFilename.Type()) {
759
// Impossible. You're gonna need to make a copy, and delete the original. Not the responsibility
760
// of Rename.
761
return false;
762
}
763
764
// We've already asserted that they're the same Type, so only need to check either src or dest.
765
switch (srcFilename.Type()) {
766
case PathType::NATIVE:
767
// OK, proceed with the regular code.
768
break;
769
case PathType::CONTENT_URI:
770
// Content URI: Can only rename if in the same folder.
771
// TODO: Fallback to move + rename? Or do we even care about that use case? We have MoveIfFast for such tricks.
772
if (srcFilename.GetDirectory() != destFilename.GetDirectory()) {
773
INFO_LOG(Log::IO, "Content URI rename: Directories not matching, failing. %s --> %s", srcFilename.c_str(), destFilename.c_str());
774
return false;
775
}
776
INFO_LOG(Log::IO, "Content URI rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());
777
return Android_RenameFileTo(srcFilename.ToString(), destFilename.GetFilename()) == StorageError::SUCCESS;
778
default:
779
return false;
780
}
781
782
INFO_LOG(Log::IO, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());
783
784
#ifdef HAVE_LIBRETRO_VFS
785
if (filestream_rename(srcFilename.c_str(), destFilename.c_str()) == 0)
786
return true;
787
ERROR_LOG(Log::IO, "Rename: failed %s --> %s",
788
srcFilename.c_str(), destFilename.c_str());
789
return false;
790
#else
791
#if defined(_WIN32) && defined(UNICODE)
792
#if PPSSPP_PLATFORM(UWP)
793
if (MoveFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str()))
794
return true;
795
#else
796
std::wstring srcw = srcFilename.ToWString();
797
std::wstring destw = destFilename.ToWString();
798
if (_wrename(srcw.c_str(), destw.c_str()) == 0)
799
return true;
800
#endif
801
#else
802
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
803
return true;
804
#endif
805
806
ERROR_LOG(Log::IO, "Rename: failed %s --> %s: %s",
807
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
808
return false;
809
#endif
810
}
811
812
// copies file srcFilename to destFilename, returns true on success
813
bool Copy(const Path &srcFilename, const Path &destFilename) {
814
if (LOG_IO) {
815
INFO_LOG(Log::IO, "Copy %s -> %s", srcFilename.c_str(), destFilename.c_str());
816
}
817
if (SIMULATE_SLOW_IO) {
818
sleep_ms(100, "slow-io-sim");
819
}
820
switch (srcFilename.Type()) {
821
case PathType::NATIVE:
822
break; // OK
823
case PathType::CONTENT_URI:
824
if (destFilename.Type() == PathType::CONTENT_URI && destFilename.CanNavigateUp()) {
825
Path destParent = destFilename.NavigateUp();
826
// Use native file copy.
827
if (Android_CopyFile(srcFilename.ToString(), destParent.ToString()) == StorageError::SUCCESS) {
828
return true;
829
}
830
INFO_LOG(Log::IO, "Android_CopyFile failed, falling back.");
831
// Else fall through, and try using file I/O.
832
}
833
break;
834
default:
835
return false;
836
}
837
838
INFO_LOG(Log::IO, "Copy by OpenCFile: %s --> %s", srcFilename.c_str(), destFilename.c_str());
839
#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS)
840
#if PPSSPP_PLATFORM(UWP)
841
if (CopyFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE))
842
return true;
843
#else
844
if (CopyFile(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE))
845
return true;
846
#endif
847
ERROR_LOG(Log::IO, "Copy: failed %s --> %s: %s",
848
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
849
return false;
850
#else // Non-Win32
851
852
// buffer size
853
#define BSIZE 16384
854
855
char buffer[BSIZE];
856
857
// Open input file
858
FILE *input = OpenCFile(srcFilename, "rb");
859
if (!input) {
860
ERROR_LOG(Log::IO, "Copy: input failed %s --> %s: %s",
861
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
862
return false;
863
}
864
865
// open output file
866
FILE *output = OpenCFile(destFilename, "wb");
867
if (!output) {
868
fclose(input);
869
ERROR_LOG(Log::IO, "Copy: output failed %s --> %s: %s",
870
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
871
return false;
872
}
873
874
int bytesWritten = 0;
875
876
// copy loop
877
while (!feof(input)) {
878
// read input
879
int rnum = fread(buffer, sizeof(char), BSIZE, input);
880
if (rnum != BSIZE) {
881
if (ferror(input) != 0) {
882
ERROR_LOG(Log::IO,
883
"Copy: failed reading from source, %s --> %s: %s",
884
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
885
fclose(input);
886
fclose(output);
887
return false;
888
}
889
}
890
891
// write output
892
int wnum = fwrite(buffer, sizeof(char), rnum, output);
893
if (wnum != rnum) {
894
ERROR_LOG(Log::IO,
895
"Copy: failed writing to output, %s --> %s: %s",
896
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
897
fclose(input);
898
fclose(output);
899
return false;
900
}
901
902
bytesWritten += wnum;
903
}
904
905
if (bytesWritten == 0) {
906
WARN_LOG(Log::IO, "Copy: No bytes written (must mean that input was empty)");
907
}
908
909
// close flushes
910
fclose(input);
911
fclose(output);
912
return true;
913
#endif
914
}
915
916
// Will overwrite the target.
917
bool Move(const Path &srcFilename, const Path &destFilename) {
918
if (SIMULATE_SLOW_IO) {
919
sleep_ms(100, "slow-io-sim");
920
INFO_LOG(Log::IO, "Move %s -> %s", srcFilename.c_str(), destFilename.c_str());
921
}
922
bool fast = MoveIfFast(srcFilename, destFilename);
923
if (fast) {
924
return true;
925
}
926
// OK, that failed, so fall back on a copy.
927
if (Copy(srcFilename, destFilename)) {
928
return Delete(srcFilename);
929
} else {
930
return false;
931
}
932
}
933
934
bool MoveIfFast(const Path &srcFilename, const Path &destFilename) {
935
if (srcFilename.Type() != destFilename.Type()) {
936
// No way it's gonna work.
937
return false;
938
}
939
940
// Only need to check one type here, due to the above check.
941
if (srcFilename.Type() == PathType::CONTENT_URI && srcFilename.CanNavigateUp() && destFilename.CanNavigateUp()) {
942
if (srcFilename.GetFilename() == destFilename.GetFilename()) {
943
Path srcParent = srcFilename.NavigateUp();
944
Path dstParent = destFilename.NavigateUp();
945
return Android_MoveFile(srcFilename.ToString(), srcParent.ToString(), dstParent.ToString()) == StorageError::SUCCESS;
946
// If failed, fall through and try other ways.
947
} else {
948
// We do not handle simultaneous renames here.
949
return false;
950
}
951
}
952
953
// Try a traditional rename operation.
954
return Rename(srcFilename, destFilename);
955
}
956
957
// Returns the size of file (64bit)
958
// TODO: Add a way to return an error.
959
uint64_t GetFileSize(const Path &filename) {
960
if (LOG_IO) {
961
INFO_LOG(Log::IO, "GetFileSize %s", filename.c_str());
962
}
963
if (SIMULATE_SLOW_IO) {
964
sleep_ms(100, "slow-io-sim");
965
}
966
switch (filename.Type()) {
967
case PathType::NATIVE:
968
break; // OK
969
case PathType::CONTENT_URI:
970
{
971
FileInfo info;
972
if (Android_GetFileInfo(filename.ToString(), &info)) {
973
return info.size;
974
} else {
975
return 0;
976
}
977
}
978
break;
979
default:
980
return false;
981
}
982
983
#ifdef HAVE_LIBRETRO_VFS
984
return path_get_size(filename.c_str());
985
#elif defined(_WIN32) && defined(UNICODE)
986
WIN32_FILE_ATTRIBUTE_DATA attr;
987
#if PPSSPP_PLATFORM(UWP)
988
if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))
989
#else
990
if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))
991
#endif
992
return 0;
993
if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
994
return 0;
995
return ((uint64_t)attr.nFileSizeHigh << 32) | (uint64_t)attr.nFileSizeLow;
996
#else
997
#if __ANDROID__ && __ANDROID_API__ < 21
998
struct stat file_info;
999
int result = stat(filename.c_str(), &file_info);
1000
#else
1001
struct stat64 file_info;
1002
int result = stat64(filename.c_str(), &file_info);
1003
#endif
1004
if (result != 0) {
1005
WARN_LOG(Log::IO, "GetSize: failed %s: No such file", filename.ToVisualString().c_str());
1006
return 0;
1007
}
1008
if (S_ISDIR(file_info.st_mode)) {
1009
WARN_LOG(Log::IO, "GetSize: failed %s: is a directory", filename.ToVisualString().c_str());
1010
return 0;
1011
}
1012
DEBUG_LOG(Log::IO, "GetSize: %s: %lld", filename.ToVisualString().c_str(), (long long)file_info.st_size);
1013
return file_info.st_size;
1014
#endif
1015
}
1016
1017
uint64_t GetFileSize(FILE *f) {
1018
uint64_t pos = Ftell(f);
1019
if (Fseek(f, 0, SEEK_END) != 0) {
1020
return 0;
1021
}
1022
uint64_t size = Ftell(f);
1023
// Reset the seek position to where it was when we started.
1024
if (size != pos && Fseek(f, pos, SEEK_SET) != 0) {
1025
// Should error here.
1026
return 0;
1027
}
1028
if (size == -1)
1029
return 0;
1030
return size;
1031
}
1032
1033
// creates an empty file filename, returns true on success
1034
bool CreateEmptyFile(const Path &filename) {
1035
INFO_LOG(Log::IO, "CreateEmptyFile: %s", filename.c_str());
1036
FILE *pFile = OpenCFile(filename, "wb");
1037
if (!pFile) {
1038
ERROR_LOG(Log::IO, "CreateEmptyFile: failed to create '%s': %s", filename.c_str(), GetLastErrorMsg().c_str());
1039
return false;
1040
}
1041
fclose(pFile);
1042
return true;
1043
}
1044
1045
// Deletes an empty directory, returns true on success
1046
// WARNING: On Android with content URIs, it will delete recursively!
1047
bool DeleteDir(const Path &path) {
1048
if (LOG_IO) {
1049
INFO_LOG(Log::IO, "DeleteDir %s", path.c_str());
1050
}
1051
if (SIMULATE_SLOW_IO) {
1052
sleep_ms(100, "slow-io-sim");
1053
}
1054
switch (path.Type()) {
1055
case PathType::NATIVE:
1056
break; // OK
1057
case PathType::CONTENT_URI:
1058
return Android_RemoveFile(path.ToString()) == StorageError::SUCCESS;
1059
default:
1060
return false;
1061
}
1062
INFO_LOG(Log::IO, "DeleteDir: directory %s", path.c_str());
1063
1064
// check if a directory
1065
if (!File::IsDirectory(path)) {
1066
ERROR_LOG(Log::IO, "DeleteDir: Not a directory %s", path.c_str());
1067
return false;
1068
}
1069
1070
#ifdef HAVE_LIBRETRO_VFS
1071
if (filestream_delete(path.c_str()) == 0)
1072
return true;
1073
ERROR_LOG(Log::IO, "DeleteDir (libretro vfs): %s", path.c_str());
1074
return false;
1075
#else
1076
#ifdef _WIN32
1077
#if PPSSPP_PLATFORM(UWP)
1078
if (RemoveDirectoryFromAppW(path.ToWString().c_str()))
1079
return true;
1080
#else
1081
if (::RemoveDirectory(path.ToWString().c_str()))
1082
return true;
1083
#endif
1084
#else
1085
if (rmdir(path.c_str()) == 0)
1086
return true;
1087
#endif
1088
ERROR_LOG(Log::IO, "DeleteDir: %s: %s", path.c_str(), GetLastErrorMsg().c_str());
1089
1090
return false;
1091
#endif
1092
}
1093
1094
// Deletes the given directory and anything under it. Returns true on success.
1095
bool DeleteDirRecursively(const Path &path) {
1096
switch (path.Type()) {
1097
case PathType::NATIVE:
1098
break;
1099
case PathType::CONTENT_URI:
1100
// We make use of the dangerous auto-recursive property of Android_RemoveFile.
1101
return Android_RemoveFile(path.ToString()) == StorageError::SUCCESS;
1102
default:
1103
ERROR_LOG(Log::IO, "DeleteDirRecursively: Path type not supported");
1104
return false;
1105
}
1106
1107
std::vector<FileInfo> files;
1108
GetFilesInDir(path, &files, nullptr, GETFILES_GETHIDDEN);
1109
for (const auto &file : files) {
1110
if (file.isDirectory) {
1111
DeleteDirRecursively(file.fullName);
1112
} else {
1113
Delete(file.fullName);
1114
}
1115
}
1116
return DeleteDir(path);
1117
}
1118
1119
bool OpenFileInEditor(const Path &fileName) {
1120
switch (fileName.Type()) {
1121
case PathType::NATIVE:
1122
break; // OK
1123
default:
1124
ERROR_LOG(Log::IO, "OpenFileInEditor(%s): Path type not supported", fileName.c_str());
1125
return false;
1126
}
1127
1128
#if PPSSPP_PLATFORM(WINDOWS)
1129
#if PPSSPP_PLATFORM(UWP)
1130
OpenFile(fileName.ToString());
1131
#else
1132
ShellExecuteW(nullptr, L"open", fileName.ToWString().c_str(), nullptr, nullptr, SW_SHOW);
1133
#endif
1134
#elif !defined(MOBILE_DEVICE)
1135
std::string iniFile;
1136
#if defined(__APPLE__)
1137
iniFile = "open ";
1138
#else
1139
iniFile = "xdg-open ";
1140
#endif
1141
iniFile.append(fileName.ToString());
1142
NOTICE_LOG(Log::Boot, "Launching %s", iniFile.c_str());
1143
int retval = system(iniFile.c_str());
1144
if (retval != 0) {
1145
ERROR_LOG(Log::IO, "Failed to launch ini file");
1146
}
1147
#endif
1148
return true;
1149
}
1150
1151
const Path GetCurDirectory() {
1152
#ifdef _WIN32
1153
wchar_t buffer[4096];
1154
size_t len = GetCurrentDirectory(sizeof(buffer) / sizeof(wchar_t), buffer);
1155
std::string curDir = ConvertWStringToUTF8(buffer);
1156
return Path(curDir);
1157
#else
1158
char temp[4096]{};
1159
getcwd(temp, 4096);
1160
return Path(temp);
1161
#endif
1162
}
1163
1164
const Path &GetExeDirectory() {
1165
static Path ExePath;
1166
1167
if (ExePath.empty()) {
1168
#ifdef _WIN32
1169
std::wstring program_path;
1170
size_t sz;
1171
do {
1172
program_path.resize(program_path.size() + MAX_PATH);
1173
// On failure, this will return the same value as passed in, but success will always be one lower.
1174
sz = GetModuleFileNameW(nullptr, &program_path[0], (DWORD)program_path.size());
1175
} while (sz >= program_path.size());
1176
1177
const wchar_t *last_slash = wcsrchr(&program_path[0], '\\');
1178
if (last_slash != nullptr)
1179
program_path.resize(last_slash - &program_path[0] + 1);
1180
else
1181
program_path.resize(sz);
1182
ExePath = Path(program_path);
1183
1184
#elif (defined(__APPLE__) && !PPSSPP_PLATFORM(IOS)) || defined(__linux__) || defined(KERN_PROC_PATHNAME)
1185
char program_path[4096]{};
1186
uint32_t program_path_size = sizeof(program_path) - 1;
1187
1188
#if defined(__linux__)
1189
if (readlink("/proc/self/exe", program_path, program_path_size) > 0)
1190
#elif defined(__APPLE__) && !PPSSPP_PLATFORM(IOS)
1191
if (_NSGetExecutablePath(program_path, &program_path_size) == 0)
1192
#elif defined(KERN_PROC_PATHNAME)
1193
int mib[4] = {
1194
CTL_KERN,
1195
#if defined(__NetBSD__)
1196
KERN_PROC_ARGS,
1197
-1,
1198
KERN_PROC_PATHNAME,
1199
#else
1200
KERN_PROC,
1201
KERN_PROC_PATHNAME,
1202
-1,
1203
#endif
1204
};
1205
size_t sz = program_path_size;
1206
1207
if (sysctl(mib, 4, program_path, &sz, NULL, 0) == 0)
1208
#else
1209
#error Unmatched ifdef.
1210
#endif
1211
{
1212
program_path[sizeof(program_path) - 1] = '\0';
1213
char *last_slash = strrchr(program_path, '/');
1214
if (last_slash != nullptr)
1215
*last_slash = '\0';
1216
ExePath = Path(program_path);
1217
}
1218
#endif
1219
}
1220
1221
return ExePath;
1222
}
1223
1224
int Fseek(FILE *file, int64_t offset, int whence) {
1225
#ifdef HAVE_LIBRETRO_VFS
1226
switch (whence) {
1227
default:
1228
whence = RETRO_VFS_SEEK_POSITION_START;
1229
break;
1230
case SEEK_CUR:
1231
whence = RETRO_VFS_SEEK_POSITION_CURRENT;
1232
break;
1233
case SEEK_END:
1234
whence = RETRO_VFS_SEEK_POSITION_END;
1235
break;
1236
}
1237
return filestream_seek(file, offset, whence) != 0 ? -1 : 0;
1238
#elif defined(_WIN32)
1239
return _fseeki64(file, offset, whence);
1240
#elif (defined(__ANDROID__) && __ANDROID_API__ < 24) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
1241
return fseek(file, offset, whence);
1242
#else
1243
return fseeko(file, offset, whence);
1244
#endif
1245
}
1246
1247
int64_t Fseektell(FILE *file, int64_t offset, int whence) {
1248
#ifdef HAVE_LIBRETRO_VFS
1249
switch (whence) {
1250
default:
1251
whence = RETRO_VFS_SEEK_POSITION_START;
1252
break;
1253
case SEEK_CUR:
1254
whence = RETRO_VFS_SEEK_POSITION_CURRENT;
1255
break;
1256
case SEEK_END:
1257
whence = RETRO_VFS_SEEK_POSITION_END;
1258
break;
1259
}
1260
return filestream_seek(file, offset, whence) != 0 ? -1 : filestream_tell(file);
1261
#elif defined(_WIN32)
1262
return _fseeki64(file, offset, whence) != 0 ? -1 : _ftelli64(file);
1263
#elif (defined(__ANDROID__) && __ANDROID_API__ < 24) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
1264
return fseek(file, offset, whence) != 0 ? -1 : ftell(file);
1265
#else
1266
return fseeko(file, offset, whence) != 0 ? -1 : ftello(file);
1267
#endif
1268
}
1269
1270
int64_t Ftell(FILE *file) {
1271
#ifdef HAVE_LIBRETRO_VFS
1272
return filestream_tell(file);
1273
#elif defined(_WIN32)
1274
return _ftelli64(file);
1275
#elif defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
1276
return ftell(file);
1277
#else
1278
return ftello(file);
1279
#endif
1280
}
1281
1282
1283
IOFile::IOFile(const Path &filename, const char openmode[]) {
1284
Open(filename, openmode);
1285
}
1286
1287
IOFile::~IOFile() {
1288
Close();
1289
}
1290
1291
bool IOFile::Open(const Path& filename, const char openmode[])
1292
{
1293
Close();
1294
m_file = File::OpenCFile(filename, openmode);
1295
m_good = IsOpen();
1296
return m_good;
1297
}
1298
1299
bool IOFile::Close()
1300
{
1301
if (!IsOpen() || 0 != fclose(m_file))
1302
m_good = false;
1303
1304
m_file = NULL;
1305
return m_good;
1306
}
1307
1308
FILE* IOFile::ReleaseHandle()
1309
{
1310
FILE* const ret = m_file;
1311
m_file = NULL;
1312
return ret;
1313
}
1314
1315
void IOFile::SetHandle(FILE* file)
1316
{
1317
Close();
1318
Clear();
1319
m_file = file;
1320
}
1321
1322
uint64_t IOFile::GetSize()
1323
{
1324
if (IsOpen())
1325
return File::GetFileSize(m_file);
1326
else
1327
return 0;
1328
}
1329
1330
bool IOFile::Seek(int64_t off, int origin)
1331
{
1332
if (!IsOpen() || 0 != Fseek(m_file, off, origin))
1333
m_good = false;
1334
1335
return m_good;
1336
}
1337
1338
uint64_t IOFile::Tell()
1339
{
1340
if (IsOpen())
1341
return Ftell(m_file);
1342
else
1343
return -1;
1344
}
1345
1346
bool IOFile::Flush()
1347
{
1348
if (!IsOpen() || 0 != fflush(m_file))
1349
m_good = false;
1350
1351
return m_good;
1352
}
1353
1354
bool IOFile::Resize(uint64_t size)
1355
{
1356
if (!IsOpen() || 0 !=
1357
#ifdef HAVE_LIBRETRO_VFS
1358
filestream_truncate(m_file, size)
1359
#elif defined(_WIN32)
1360
// ector: _chsize sucks, not 64-bit safe
1361
// F|RES: changed to _chsize_s. i think it is 64-bit safe
1362
_chsize_s(_fileno(m_file), size)
1363
#else
1364
// TODO: handle 64bit and growing
1365
ftruncate(fileno(m_file), size)
1366
#endif
1367
)
1368
m_good = false;
1369
1370
return m_good;
1371
}
1372
1373
bool ReadFileToStringOptions(bool textFile, bool allowShort, const Path &filename, std::string *str) {
1374
FILE *f = File::OpenCFile(filename, textFile ? "r" : "rb");
1375
if (!f)
1376
return false;
1377
// Warning: some files, like in /sys/, may return a fixed size like 4096.
1378
size_t len = (size_t)File::GetFileSize(f);
1379
bool success;
1380
if (len == 0) {
1381
// Just read until we can't read anymore.
1382
size_t totalSize = 1024;
1383
size_t totalRead = 0;
1384
do {
1385
totalSize *= 2;
1386
str->resize(totalSize);
1387
totalRead += fread(&(*str)[totalRead], 1, totalSize - totalRead, f);
1388
} while (totalRead == totalSize);
1389
str->resize(totalRead);
1390
success = true;
1391
} else {
1392
str->resize(len);
1393
size_t totalRead = fread(&(*str)[0], 1, len, f);
1394
str->resize(totalRead);
1395
// Allow less, because some system files will report incorrect lengths.
1396
// Also, when reading text with CRLF, the read length may be shorter.
1397
if (textFile) {
1398
// totalRead doesn't take \r into account since they might be skipped in this mode.
1399
// So let's just ask how far the cursor got.
1400
totalRead = Ftell(f);
1401
}
1402
success = allowShort ? (totalRead <= len) : (totalRead == len);
1403
}
1404
fclose(f);
1405
return success;
1406
}
1407
1408
uint8_t *ReadLocalFile(const Path &filename, size_t *size) {
1409
FILE *file = File::OpenCFile(filename, "rb");
1410
if (!file) {
1411
*size = 0;
1412
return nullptr;
1413
}
1414
int64_t f_size = Fseektell(file, 0, SEEK_END);
1415
if (f_size < 0) {
1416
*size = 0;
1417
fclose(file);
1418
return nullptr;
1419
}
1420
Fseek(file, 0, SEEK_SET);
1421
// NOTE: If you find ~10 memory leaks from here, with very varying sizes, it might be the VFPU LUTs.
1422
uint8_t *contents = new uint8_t[f_size + 1];
1423
if (fread(contents, 1, f_size, file) != f_size) {
1424
delete[] contents;
1425
contents = nullptr;
1426
*size = 0;
1427
} else {
1428
contents[f_size] = 0;
1429
*size = f_size;
1430
}
1431
fclose(file);
1432
return contents;
1433
}
1434
1435
bool WriteStringToFile(bool text_file, std::string_view str, const Path &filename) {
1436
FILE *f = File::OpenCFile(filename, text_file ? "w" : "wb");
1437
if (!f)
1438
return false;
1439
size_t len = str.size();
1440
if (len != fwrite(str.data(), 1, str.size(), f))
1441
{
1442
fclose(f);
1443
return false;
1444
}
1445
fclose(f);
1446
return true;
1447
}
1448
1449
bool WriteDataToFile(bool text_file, const void* data, size_t size, const Path &filename) {
1450
FILE *f = File::OpenCFile(filename, text_file ? "w" : "wb");
1451
if (!f)
1452
return false;
1453
if (size != fwrite(data, 1, size, f))
1454
{
1455
fclose(f);
1456
return false;
1457
}
1458
fclose(f);
1459
return true;
1460
}
1461
1462
void ChangeMTime(const Path &path, time_t mtime) {
1463
if (path.Type() == PathType::CONTENT_URI) {
1464
// No clue what to do here. There doesn't seem to be a way.
1465
return;
1466
}
1467
1468
#ifndef HAVE_LIBRETRO_VFS
1469
#ifdef _WIN32
1470
_utimbuf buf{};
1471
buf.actime = mtime;
1472
buf.modtime = mtime;
1473
_utime(path.c_str(), &buf);
1474
#else
1475
utimbuf buf{};
1476
buf.actime = mtime;
1477
buf.modtime = mtime;
1478
utime(path.c_str(), &buf);
1479
#endif
1480
#endif
1481
}
1482
1483
bool IsProbablyInDownloadsFolder(const Path &filename) {
1484
INFO_LOG(Log::IO, "IsProbablyInDownloadsFolder: Looking at %s (%s)...", filename.c_str(), filename.ToVisualString().c_str());
1485
switch (filename.Type()) {
1486
case PathType::CONTENT_URI:
1487
{
1488
AndroidContentURI uri(filename.ToString());
1489
INFO_LOG(Log::IO, "Content URI provider: %s", uri.Provider().c_str());
1490
if (containsNoCase(uri.Provider(), "download")) {
1491
// like com.android.providers.downloads.documents
1492
return true;
1493
}
1494
break;
1495
}
1496
default:
1497
break;
1498
}
1499
return filename.FilePathContainsNoCase("download");
1500
}
1501
1502
} // namespace File
1503
1504