CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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