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