CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Common/File/FileUtil.cpp
Views: 1401
// Copyright (C) 2003 Dolphin Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official SVN repository and contact information can be found at15// http://code.google.com/p/dolphin-emu/1617#if defined(_MSC_VER)18#pragma warning(disable:4091) // workaround bug in VS2015 headers19#ifndef UNICODE20#error Win32 build requires a unicode build21#endif22#else23#define _POSIX_SOURCE24#define _LARGE_TIME_API25#endif2627#include "ppsspp_config.h"2829#include "android/jni/app-android.h"3031#ifdef __MINGW32__32#include <unistd.h>33#ifndef _POSIX_THREAD_SAFE_FUNCTIONS34#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L35#endif36#endif3738#include <cstring>39#include <ctime>40#include <memory>4142#include <sys/types.h>4344#include "Common/Log.h"45#include "Common/LogReporting.h"46#include "Common/File/AndroidContentURI.h"47#include "Common/File/FileUtil.h"48#include "Common/StringUtils.h"49#include "Common/SysError.h"5051#ifdef _WIN3252#include "Common/CommonWindows.h"53#include <sys/utime.h>54#include <Windows.h>55#include <shlobj.h> // for SHGetFolderPath56#include <shellapi.h>57#include <commdlg.h> // for GetSaveFileName58#include <io.h>59#include <direct.h> // getcwd60#if PPSSPP_PLATFORM(UWP)61#include <fileapifromapp.h>62#include "UWP/UWPHelpers/StorageManager.h"63#endif64#else65#include <sys/param.h>66#include <sys/types.h>67#include <dirent.h>68#include <errno.h>69#include <stdlib.h>70#include <unistd.h>71#include <utime.h>72#endif7374#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__)75#include <sys/sysctl.h> // KERN_PROC_PATHNAME76#endif7778#if defined(__APPLE__)79#include <CoreFoundation/CFString.h>80#include <CoreFoundation/CFURL.h>81#include <CoreFoundation/CFBundle.h>82#if !PPSSPP_PLATFORM(IOS)83#include <mach-o/dyld.h>84#endif // !PPSSPP_PLATFORM(IOS)85#endif // __APPLE__8687#include "Common/Data/Encoding/Utf8.h"8889#include <sys/stat.h>9091#ifndef S_ISDIR92#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)93#endif9495#if !defined(__linux__) && !defined(_WIN32) && !defined(__QNX__)96#define stat64 stat97#define fstat64 fstat98#endif99100#define DIR_SEP "/"101#ifdef _WIN32102#define DIR_SEP_CHRS "/\\"103#else104#define DIR_SEP_CHRS "/"105#endif106107// This namespace has various generic functions related to files and paths.108// The code still needs a ton of cleanup.109// REMEMBER: strdup considered harmful!110namespace File {111112FILE *OpenCFile(const Path &path, const char *mode) {113switch (path.Type()) {114case PathType::NATIVE:115break;116case PathType::CONTENT_URI:117// We're gonna need some error codes..118if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) {119INFO_LOG(Log::Common, "Opening content file for read: '%s'", path.c_str());120// Read, let's support this - easy one.121int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ);122if (descriptor < 0) {123return nullptr;124}125return fdopen(descriptor, "rb");126} else if (!strcmp(mode, "w") || !strcmp(mode, "wb") || !strcmp(mode, "wt") || !strcmp(mode, "at") || !strcmp(mode, "a")) {127// Need to be able to create the file here if it doesn't exist.128// Not exactly sure which abstractions are best, let's start simple.129if (!File::Exists(path)) {130INFO_LOG(Log::Common, "OpenCFile(%s): Opening content file for write. Doesn't exist, creating empty and reopening.", path.c_str());131std::string name = path.GetFilename();132if (path.CanNavigateUp()) {133Path parent = path.NavigateUp();134if (Android_CreateFile(parent.ToString(), name) != StorageError::SUCCESS) {135WARN_LOG(Log::Common, "Failed to create file '%s' in '%s'", name.c_str(), parent.c_str());136return nullptr;137}138} else {139INFO_LOG_REPORT_ONCE(openCFileFailedNavigateUp, Log::Common, "Failed to navigate up to create file: %s", path.c_str());140return nullptr;141}142} else {143INFO_LOG(Log::Common, "OpenCFile(%s): Opening existing content file for write (truncating). Requested mode: '%s'", path.c_str(), mode);144}145146// TODO: Support append modes and stuff... For now let's go with the most common one.147Android_OpenContentUriMode openMode = Android_OpenContentUriMode::READ_WRITE_TRUNCATE;148const char *fmode = "wb";149if (!strcmp(mode, "at") || !strcmp(mode, "a")) {150openMode = Android_OpenContentUriMode::READ_WRITE;151fmode = "ab";152}153int descriptor = Android_OpenContentUriFd(path.ToString(), openMode);154if (descriptor < 0) {155INFO_LOG(Log::Common, "Opening '%s' for write failed", path.ToString().c_str());156return nullptr;157}158FILE *f = fdopen(descriptor, fmode);159if (f && (!strcmp(mode, "at") || !strcmp(mode, "a"))) {160// Append mode - not sure we got a "true" append mode, so seek to the end.161fseek(f, 0, SEEK_END);162}163return f;164} else {165ERROR_LOG(Log::Common, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode);166return nullptr;167}168break;169default:170ERROR_LOG(Log::Common, "OpenCFile(%s): PathType not yet supported", path.c_str());171return nullptr;172}173174#if defined(_WIN32) && defined(UNICODE)175#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)176// We shouldn't use _wfopen here,177// this function is not allowed to read outside Local and Installation folders178// FileSystem (broadFileSystemAccess) doesn't apply on _wfopen179// if we have custom memory stick location _wfopen will return null180// 'GetFileStreamFromApp' will convert 'mode' to [access, share, creationDisposition]181// then it will call 'CreateFile2FromAppW' -> convert HANDLE to FILE*182FILE* file = GetFileStreamFromApp(path.ToString(), mode);183return file;184#else185return _wfopen(path.ToWString().c_str(), ConvertUTF8ToWString(mode).c_str());186#endif187#else188return fopen(path.c_str(), mode);189#endif190}191192static std::string OpenFlagToString(OpenFlag flags) {193std::string s;194if (flags & OPEN_READ)195s += "READ|";196if (flags & OPEN_WRITE)197s += "WRITE|";198if (flags & OPEN_APPEND)199s += "APPEND|";200if (flags & OPEN_CREATE)201s += "CREATE|";202if (flags & OPEN_TRUNCATE)203s += "TRUNCATE|";204if (!s.empty()) {205s.pop_back(); // Remove trailing separator.206}207return s;208}209210int OpenFD(const Path &path, OpenFlag flags) {211switch (path.Type()) {212case PathType::CONTENT_URI:213break;214default:215ERROR_LOG(Log::Common, "OpenFD: Only supports Content URI paths. Not '%s' (%s)!", path.c_str(), OpenFlagToString(flags).c_str());216// Not yet supported - use other paths.217return -1;218}219220if (flags & OPEN_CREATE) {221if (!File::Exists(path)) {222INFO_LOG(Log::Common, "OpenFD(%s): Creating file.", path.c_str());223std::string name = path.GetFilename();224if (path.CanNavigateUp()) {225Path parent = path.NavigateUp();226if (Android_CreateFile(parent.ToString(), name) != StorageError::SUCCESS) {227WARN_LOG(Log::Common, "OpenFD: Failed to create file '%s' in '%s'", name.c_str(), parent.c_str());228return -1;229}230} else {231INFO_LOG(Log::Common, "Failed to navigate up to create file: %s", path.c_str());232return -1;233}234} else {235INFO_LOG(Log::Common, "OpenCFile(%s): Opening existing content file ('%s')", path.c_str(), OpenFlagToString(flags).c_str());236}237}238239Android_OpenContentUriMode mode;240if (flags == OPEN_READ) {241mode = Android_OpenContentUriMode::READ;242} else if (flags & OPEN_WRITE) {243if (flags & OPEN_TRUNCATE) {244mode = Android_OpenContentUriMode::READ_WRITE_TRUNCATE;245} else {246mode = Android_OpenContentUriMode::READ_WRITE;247}248// TODO: Maybe better checking of additional flags here.249} else {250// TODO: Add support for more modes if possible.251ERROR_LOG_REPORT_ONCE(openFlagNotSupported, Log::Common, "OpenFlag %s not yet supported", OpenFlagToString(flags).c_str());252return -1;253}254255INFO_LOG(Log::Common, "Android_OpenContentUriFd: %s (%s)", path.c_str(), OpenFlagToString(flags).c_str());256int descriptor = Android_OpenContentUriFd(path.ToString(), mode);257if (descriptor < 0) {258ERROR_LOG(Log::Common, "Android_OpenContentUriFd failed: '%s'", path.c_str());259}260261if (flags & OPEN_APPEND) {262// Simply seek to the end of the file to simulate append mode.263lseek(descriptor, 0, SEEK_END);264}265266return descriptor;267}268269#ifdef _WIN32270static bool ResolvePathVista(const std::wstring &path, wchar_t *buf, DWORD bufSize) {271typedef DWORD(WINAPI *getFinalPathNameByHandleW_f)(HANDLE hFile, LPWSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags);272static getFinalPathNameByHandleW_f getFinalPathNameByHandleW = nullptr;273274#if PPSSPP_PLATFORM(UWP)275getFinalPathNameByHandleW = &GetFinalPathNameByHandleW;276#else277if (!getFinalPathNameByHandleW) {278HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");279if (kernel32)280getFinalPathNameByHandleW = (getFinalPathNameByHandleW_f)GetProcAddress(kernel32, "GetFinalPathNameByHandleW");281}282#endif283284if (getFinalPathNameByHandleW) {285#if PPSSPP_PLATFORM(UWP)286HANDLE hFile = CreateFile2FromAppW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr);287#else288HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);289#endif290if (hFile == INVALID_HANDLE_VALUE)291return false;292293DWORD result = getFinalPathNameByHandleW(hFile, buf, bufSize - 1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);294CloseHandle(hFile);295296return result < bufSize && result != 0;297}298299return false;300}301#endif302303std::string ResolvePath(const std::string &path) {304if (startsWith(path, "http://") || startsWith(path, "https://")) {305return path;306}307308if (Android_IsContentUri(path)) {309// Nothing to do?310return path;311}312313#ifdef _WIN32314static const int BUF_SIZE = 32768;315wchar_t *buf = new wchar_t[BUF_SIZE] {};316317std::wstring input = ConvertUTF8ToWString(path);318// Try to resolve symlinks (such as Documents aliases, etc.) if possible on Vista and higher.319// For some paths and remote shares, this may fail, so fall back.320if (!ResolvePathVista(input, buf, BUF_SIZE)) {321wchar_t *longBuf = new wchar_t[BUF_SIZE] {};322323int result = GetLongPathNameW(input.c_str(), longBuf, BUF_SIZE - 1);324if (result >= BUF_SIZE || result == 0)325wcscpy_s(longBuf, BUF_SIZE - 1, input.c_str());326327result = GetFullPathNameW(longBuf, BUF_SIZE - 1, buf, nullptr);328if (result >= BUF_SIZE || result == 0)329wcscpy_s(buf, BUF_SIZE - 1, input.c_str());330331delete [] longBuf;332}333334// Normalize slashes just in case.335for (int i = 0; i < BUF_SIZE; ++i) {336if (buf[i] == '\\')337buf[i] = '/';338else if (buf[i] == '\0')339break;340}341342// Undo the \\?\C:\ syntax that's normally returned (after normalization of slashes.)343std::string output = ConvertWStringToUTF8(buf);344if (buf[0] == '/' && buf[1] == '/' && buf[2] == '?' && buf[3] == '/' && isalpha(buf[4]) && buf[5] == ':')345output = output.substr(4);346delete [] buf;347return output;348349#elif PPSSPP_PLATFORM(IOS)350// Resolving has wacky effects on documents paths.351return path;352#else353std::unique_ptr<char[]> buf(new char[PATH_MAX + 32768]);354if (realpath(path.c_str(), buf.get()) == nullptr)355return path;356return buf.get();357#endif358}359360static int64_t RecursiveSize(const Path &path) {361// TODO: Some file systems can optimize this.362std::vector<FileInfo> fileInfo;363if (!GetFilesInDir(path, &fileInfo, nullptr, GETFILES_GETHIDDEN)) {364return -1;365}366int64_t sizeSum = 0;367for (const auto &file : fileInfo) {368if (file.isDirectory) {369sizeSum += RecursiveSize(file.fullName);370} else {371sizeSum += file.size;372}373}374return sizeSum;375}376377uint64_t ComputeRecursiveDirectorySize(const Path &path) {378if (path.Type() == PathType::CONTENT_URI) {379return Android_ComputeRecursiveDirectorySize(path.ToString());380}381382// Generic solution.383return RecursiveSize(path);384}385386// Returns true if file filename exists. Will return true on directories.387bool ExistsInDir(const Path &path, const std::string &filename) {388return Exists(path / filename);389}390391bool Exists(const Path &path) {392if (path.Type() == PathType::CONTENT_URI) {393return Android_FileExists(path.c_str());394}395396#if defined(_WIN32)397398// Make sure Windows will no longer handle critical errors, which means no annoying "No disk" dialog399#if !PPSSPP_PLATFORM(UWP)400int OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);401#endif402WIN32_FILE_ATTRIBUTE_DATA data{};403#if PPSSPP_PLATFORM(UWP)404if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {405return false;406}407#else408if (!GetFileAttributesEx(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {409return false;410}411#endif412#if !PPSSPP_PLATFORM(UWP)413SetErrorMode(OldMode);414#endif415return true;416#else // !WIN32417struct stat file_info{};418return stat(path.c_str(), &file_info) == 0;419#endif420}421422// Returns true if filename exists and is a directory423bool IsDirectory(const Path &filename) {424switch (filename.Type()) {425case PathType::NATIVE:426break; // OK427case PathType::CONTENT_URI:428{429FileInfo info;430if (!Android_GetFileInfo(filename.ToString(), &info)) {431return false;432}433return info.exists && info.isDirectory;434}435default:436return false;437}438439#if defined(_WIN32)440WIN32_FILE_ATTRIBUTE_DATA data{};441#if PPSSPP_PLATFORM(UWP)442if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {443#else444if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {445#endif446auto err = GetLastError();447if (err != ERROR_FILE_NOT_FOUND) {448WARN_LOG(Log::Common, "GetFileAttributes failed on %s: %08x %s", filename.ToVisualString().c_str(), (uint32_t)err, GetStringErrorMsg(err).c_str());449}450return false;451}452DWORD result = data.dwFileAttributes;453return (result & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;454#else455std::string copy = filename.ToString();456struct stat file_info;457int result = stat(copy.c_str(), &file_info);458if (result < 0) {459WARN_LOG(Log::Common, "IsDirectory: stat failed on %s: %s", copy.c_str(), GetLastErrorMsg().c_str());460return false;461}462return S_ISDIR(file_info.st_mode);463#endif464}465466// Deletes a given filename, return true on success467// Doesn't supports deleting a directory468bool Delete(const Path &filename) {469switch (filename.Type()) {470case PathType::NATIVE:471break; // OK472case PathType::CONTENT_URI:473return Android_RemoveFile(filename.ToString()) == StorageError::SUCCESS;474default:475return false;476}477478INFO_LOG(Log::Common, "Delete: file %s", filename.c_str());479480// Return true because we care about the file no481// being there, not the actual delete.482if (!Exists(filename)) {483WARN_LOG(Log::Common, "Delete: '%s' already does not exist", filename.c_str());484return true;485}486487// We can't delete a directory488if (IsDirectory(filename)) {489WARN_LOG(Log::Common, "Delete failed: '%s' is a directory", filename.c_str());490return false;491}492493#ifdef _WIN32494#if PPSSPP_PLATFORM(UWP)495if (!DeleteFileFromAppW(filename.ToWString().c_str())) {496WARN_LOG(Log::Common, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str());497return false;498}499#else500if (!DeleteFile(filename.ToWString().c_str())) {501WARN_LOG(Log::Common, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str());502return false;503}504#endif505#else506if (unlink(filename.c_str()) == -1) {507WARN_LOG(Log::Common, "Delete: unlink failed on %s: %s",508filename.c_str(), GetLastErrorMsg().c_str());509return false;510}511#endif512513return true;514}515516// Returns true if successful, or path already exists.517bool CreateDir(const Path &path) {518switch (path.Type()) {519case PathType::NATIVE:520break; // OK521case PathType::CONTENT_URI:522{523// NOTE: The Android storage API will simply create a renamed directory (append a number) if it already exists.524// We want to avoid that, so let's just return true if the directory already is there.525if (File::Exists(path)) {526return true;527}528529// Convert it to a "CreateDirIn" call, if possible, since that's530// what we can do with the storage API.531AndroidContentURI uri(path.ToString());532std::string newDirName = uri.GetLastPart();533if (uri.NavigateUp()) {534INFO_LOG(Log::Common, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str());535return Android_CreateDirectory(uri.ToString(), newDirName) == StorageError::SUCCESS;536} else {537// Bad path - can't create this directory.538WARN_LOG(Log::Common, "CreateDir failed: '%s'", path.c_str());539return false;540}541break;542}543default:544return false;545}546547DEBUG_LOG(Log::Common, "CreateDir('%s')", path.c_str());548#ifdef _WIN32549#if PPSSPP_PLATFORM(UWP)550if (CreateDirectoryFromAppW(path.ToWString().c_str(), NULL))551return true;552#else553if (::CreateDirectory(path.ToWString().c_str(), NULL))554return true;555#endif556557DWORD error = GetLastError();558if (error == ERROR_ALREADY_EXISTS) {559WARN_LOG(Log::Common, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str());560return true;561}562ERROR_LOG(Log::Common, "CreateDir: CreateDirectory failed on %s: %08x %s", path.c_str(), (uint32_t)error, GetStringErrorMsg(error).c_str());563return false;564#else565if (mkdir(path.ToString().c_str(), 0755) == 0) {566return true;567}568569int err = errno;570if (err == EEXIST) {571WARN_LOG(Log::Common, "CreateDir: mkdir failed on %s: already exists", path.c_str());572return true;573}574575ERROR_LOG(Log::Common, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err));576return false;577#endif578}579580// Creates the full path of fullPath returns true on success581bool CreateFullPath(const Path &path) {582if (File::Exists(path)) {583DEBUG_LOG(Log::Common, "CreateFullPath: path exists %s", path.c_str());584return true;585}586587switch (path.Type()) {588case PathType::NATIVE:589case PathType::CONTENT_URI:590break; // OK591default:592ERROR_LOG(Log::Common, "CreateFullPath(%s): Not yet supported", path.c_str());593return false;594}595596// The below code is entirely agnostic of path format.597598Path root = path.GetRootVolume();599600std::string diff;601if (!root.ComputePathTo(path, diff)) {602return false;603}604605std::vector<std::string_view> parts;606SplitString(diff, '/', parts);607608// Probably not necessary sanity check, ported from the old code.609if (parts.size() > 100) {610ERROR_LOG(Log::Common, "CreateFullPath: directory structure too deep");611return false;612}613614Path curPath = root;615for (auto part : parts) {616curPath /= part;617if (!File::Exists(curPath)) {618File::CreateDir(curPath);619}620}621622return true;623}624625// renames file srcFilename to destFilename, returns true on success626bool Rename(const Path &srcFilename, const Path &destFilename) {627if (srcFilename.Type() != destFilename.Type()) {628// Impossible. You're gonna need to make a copy, and delete the original. Not the responsibility629// of Rename.630return false;631}632633// We've already asserted that they're the same Type, so only need to check either src or dest.634switch (srcFilename.Type()) {635case PathType::NATIVE:636// OK, proceed with the regular code.637break;638case PathType::CONTENT_URI:639// Content URI: Can only rename if in the same folder.640// TODO: Fallback to move + rename? Or do we even care about that use case? We have MoveIfFast for such tricks.641if (srcFilename.GetDirectory() != destFilename.GetDirectory()) {642INFO_LOG(Log::Common, "Content URI rename: Directories not matching, failing. %s --> %s", srcFilename.c_str(), destFilename.c_str());643return false;644}645INFO_LOG(Log::Common, "Content URI rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());646return Android_RenameFileTo(srcFilename.ToString(), destFilename.GetFilename()) == StorageError::SUCCESS;647default:648return false;649}650651INFO_LOG(Log::Common, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());652653#if defined(_WIN32) && defined(UNICODE)654#if PPSSPP_PLATFORM(UWP)655if (MoveFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str()))656return true;657#else658std::wstring srcw = srcFilename.ToWString();659std::wstring destw = destFilename.ToWString();660if (_wrename(srcw.c_str(), destw.c_str()) == 0)661return true;662#endif663#else664if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)665return true;666#endif667668ERROR_LOG(Log::Common, "Rename: failed %s --> %s: %s",669srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());670return false;671}672673// copies file srcFilename to destFilename, returns true on success674bool Copy(const Path &srcFilename, const Path &destFilename) {675switch (srcFilename.Type()) {676case PathType::NATIVE:677break; // OK678case PathType::CONTENT_URI:679if (destFilename.Type() == PathType::CONTENT_URI && destFilename.CanNavigateUp()) {680Path destParent = destFilename.NavigateUp();681// Use native file copy.682if (Android_CopyFile(srcFilename.ToString(), destParent.ToString()) == StorageError::SUCCESS) {683return true;684}685INFO_LOG(Log::Common, "Android_CopyFile failed, falling back.");686// Else fall through, and try using file I/O.687}688break;689default:690return false;691}692693INFO_LOG(Log::Common, "Copy by OpenCFile: %s --> %s", srcFilename.c_str(), destFilename.c_str());694#ifdef _WIN32695#if PPSSPP_PLATFORM(UWP)696if (CopyFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE))697return true;698#else699if (CopyFile(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE))700return true;701#endif702ERROR_LOG(Log::Common, "Copy: failed %s --> %s: %s",703srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());704return false;705#else // Non-Win32706707// buffer size708#define BSIZE 16384709710char buffer[BSIZE];711712// Open input file713FILE *input = OpenCFile(srcFilename, "rb");714if (!input) {715ERROR_LOG(Log::Common, "Copy: input failed %s --> %s: %s",716srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());717return false;718}719720// open output file721FILE *output = OpenCFile(destFilename, "wb");722if (!output) {723fclose(input);724ERROR_LOG(Log::Common, "Copy: output failed %s --> %s: %s",725srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());726return false;727}728729int bytesWritten = 0;730731// copy loop732while (!feof(input)) {733// read input734int rnum = fread(buffer, sizeof(char), BSIZE, input);735if (rnum != BSIZE) {736if (ferror(input) != 0) {737ERROR_LOG(Log::Common,738"Copy: failed reading from source, %s --> %s: %s",739srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());740fclose(input);741fclose(output);742return false;743}744}745746// write output747int wnum = fwrite(buffer, sizeof(char), rnum, output);748if (wnum != rnum) {749ERROR_LOG(Log::Common,750"Copy: failed writing to output, %s --> %s: %s",751srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());752fclose(input);753fclose(output);754return false;755}756757bytesWritten += wnum;758}759760if (bytesWritten == 0) {761WARN_LOG(Log::Common, "Copy: No bytes written (must mean that input was empty)");762}763764// close flushes765fclose(input);766fclose(output);767return true;768#endif769}770771// Will overwrite the target.772bool Move(const Path &srcFilename, const Path &destFilename) {773bool fast = MoveIfFast(srcFilename, destFilename);774if (fast) {775return true;776}777// OK, that failed, so fall back on a copy.778if (Copy(srcFilename, destFilename)) {779return Delete(srcFilename);780} else {781return false;782}783}784785bool MoveIfFast(const Path &srcFilename, const Path &destFilename) {786if (srcFilename.Type() != destFilename.Type()) {787// No way it's gonna work.788return false;789}790791// Only need to check one type here, due to the above check.792if (srcFilename.Type() == PathType::CONTENT_URI && srcFilename.CanNavigateUp() && destFilename.CanNavigateUp()) {793if (srcFilename.GetFilename() == destFilename.GetFilename()) {794Path srcParent = srcFilename.NavigateUp();795Path dstParent = destFilename.NavigateUp();796return Android_MoveFile(srcFilename.ToString(), srcParent.ToString(), dstParent.ToString()) == StorageError::SUCCESS;797// If failed, fall through and try other ways.798} else {799// We do not handle simultaneous renames here.800return false;801}802}803804// Try a traditional rename operation.805return Rename(srcFilename, destFilename);806}807808// Returns the size of file (64bit)809// TODO: Add a way to return an error.810uint64_t GetFileSize(const Path &filename) {811switch (filename.Type()) {812case PathType::NATIVE:813break; // OK814case PathType::CONTENT_URI:815{816FileInfo info;817if (Android_GetFileInfo(filename.ToString(), &info)) {818return info.size;819} else {820return 0;821}822}823break;824default:825return false;826}827828#if defined(_WIN32) && defined(UNICODE)829WIN32_FILE_ATTRIBUTE_DATA attr;830#if PPSSPP_PLATFORM(UWP)831if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))832#else833if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))834#endif835return 0;836if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)837return 0;838return ((uint64_t)attr.nFileSizeHigh << 32) | (uint64_t)attr.nFileSizeLow;839#else840#if __ANDROID__ && __ANDROID_API__ < 21841struct stat file_info;842int result = stat(filename.c_str(), &file_info);843#else844struct stat64 file_info;845int result = stat64(filename.c_str(), &file_info);846#endif847if (result != 0) {848WARN_LOG(Log::Common, "GetSize: failed %s: No such file", filename.ToVisualString().c_str());849return 0;850}851if (S_ISDIR(file_info.st_mode)) {852WARN_LOG(Log::Common, "GetSize: failed %s: is a directory", filename.ToVisualString().c_str());853return 0;854}855DEBUG_LOG(Log::Common, "GetSize: %s: %lld", filename.ToVisualString().c_str(), (long long)file_info.st_size);856return file_info.st_size;857#endif858}859860uint64_t GetFileSize(FILE *f) {861// This will only support 64-bit when large file support is available.862// That won't be the case on some versions of Android, at least.863#if defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)864int fd = fileno(f);865866off64_t pos = lseek64(fd, 0, SEEK_CUR);867off64_t size = lseek64(fd, 0, SEEK_END);868if (size != pos && lseek64(fd, pos, SEEK_SET) != pos) {869// Should error here.870return 0;871}872if (size == -1)873return 0;874return size;875#else876#ifdef _WIN32877uint64_t pos = _ftelli64(f);878#else879uint64_t pos = ftello(f);880#endif881if (fseek(f, 0, SEEK_END) != 0) {882return 0;883}884#ifdef _WIN32885uint64_t size = _ftelli64(f);886// Reset the seek position to where it was when we started.887if (size != pos && _fseeki64(f, pos, SEEK_SET) != 0) {888#else889uint64_t size = ftello(f);890// Reset the seek position to where it was when we started.891if (size != pos && fseeko(f, pos, SEEK_SET) != 0) {892#endif893// Should error here.894return 0;895}896if (size == -1)897return 0;898return size;899#endif900}901902// creates an empty file filename, returns true on success903bool CreateEmptyFile(const Path &filename) {904INFO_LOG(Log::Common, "CreateEmptyFile: %s", filename.c_str());905FILE *pFile = OpenCFile(filename, "wb");906if (!pFile) {907ERROR_LOG(Log::Common, "CreateEmptyFile: failed to create '%s': %s", filename.c_str(), GetLastErrorMsg().c_str());908return false;909}910fclose(pFile);911return true;912}913914// Deletes an empty directory, returns true on success915// WARNING: On Android with content URIs, it will delete recursively!916bool DeleteDir(const Path &path) {917switch (path.Type()) {918case PathType::NATIVE:919break; // OK920case PathType::CONTENT_URI:921return Android_RemoveFile(path.ToString()) == StorageError::SUCCESS;922default:923return false;924}925INFO_LOG(Log::Common, "DeleteDir: directory %s", path.c_str());926927// check if a directory928if (!File::IsDirectory(path)) {929ERROR_LOG(Log::Common, "DeleteDir: Not a directory %s", path.c_str());930return false;931}932933#ifdef _WIN32934#if PPSSPP_PLATFORM(UWP)935if (RemoveDirectoryFromAppW(path.ToWString().c_str()))936return true;937#else938if (::RemoveDirectory(path.ToWString().c_str()))939return true;940#endif941#else942if (rmdir(path.c_str()) == 0)943return true;944#endif945ERROR_LOG(Log::Common, "DeleteDir: %s: %s", path.c_str(), GetLastErrorMsg().c_str());946947return false;948}949950// Deletes the given directory and anything under it. Returns true on success.951bool DeleteDirRecursively(const Path &path) {952switch (path.Type()) {953case PathType::NATIVE:954break;955case PathType::CONTENT_URI:956// We make use of the dangerous auto-recursive property of Android_RemoveFile.957return Android_RemoveFile(path.ToString()) == StorageError::SUCCESS;958default:959ERROR_LOG(Log::Common, "DeleteDirRecursively: Path type not supported");960return false;961}962963std::vector<FileInfo> files;964GetFilesInDir(path, &files, nullptr, GETFILES_GETHIDDEN);965for (const auto &file : files) {966if (file.isDirectory) {967DeleteDirRecursively(file.fullName);968} else {969Delete(file.fullName);970}971}972return DeleteDir(path);973}974975bool OpenFileInEditor(const Path &fileName) {976switch (fileName.Type()) {977case PathType::NATIVE:978break; // OK979default:980ERROR_LOG(Log::Common, "OpenFileInEditor(%s): Path type not supported", fileName.c_str());981return false;982}983984#if PPSSPP_PLATFORM(WINDOWS)985#if PPSSPP_PLATFORM(UWP)986OpenFile(fileName.ToString());987#else988ShellExecuteW(nullptr, L"open", fileName.ToWString().c_str(), nullptr, nullptr, SW_SHOW);989#endif990#elif !defined(MOBILE_DEVICE)991std::string iniFile;992#if defined(__APPLE__)993iniFile = "open ";994#else995iniFile = "xdg-open ";996#endif997iniFile.append(fileName.ToString());998NOTICE_LOG(Log::Boot, "Launching %s", iniFile.c_str());999int retval = system(iniFile.c_str());1000if (retval != 0) {1001ERROR_LOG(Log::Common, "Failed to launch ini file");1002}1003#endif1004return true;1005}10061007const Path GetCurDirectory() {1008#ifdef _WIN321009wchar_t buffer[4096];1010size_t len = GetCurrentDirectory(sizeof(buffer) / sizeof(wchar_t), buffer);1011std::string curDir = ConvertWStringToUTF8(buffer);1012return Path(curDir);1013#else1014char temp[4096]{};1015getcwd(temp, 4096);1016return Path(temp);1017#endif1018}10191020const Path &GetExeDirectory() {1021static Path ExePath;10221023if (ExePath.empty()) {1024#ifdef _WIN321025std::wstring program_path;1026size_t sz;1027do {1028program_path.resize(program_path.size() + MAX_PATH);1029// On failure, this will return the same value as passed in, but success will always be one lower.1030sz = GetModuleFileNameW(nullptr, &program_path[0], (DWORD)program_path.size());1031} while (sz >= program_path.size());10321033const wchar_t *last_slash = wcsrchr(&program_path[0], '\\');1034if (last_slash != nullptr)1035program_path.resize(last_slash - &program_path[0] + 1);1036else1037program_path.resize(sz);1038ExePath = Path(program_path);10391040#elif (defined(__APPLE__) && !PPSSPP_PLATFORM(IOS)) || defined(__linux__) || defined(KERN_PROC_PATHNAME)1041char program_path[4096]{};1042uint32_t program_path_size = sizeof(program_path) - 1;10431044#if defined(__linux__)1045if (readlink("/proc/self/exe", program_path, program_path_size) > 0)1046#elif defined(__APPLE__) && !PPSSPP_PLATFORM(IOS)1047if (_NSGetExecutablePath(program_path, &program_path_size) == 0)1048#elif defined(KERN_PROC_PATHNAME)1049int mib[4] = {1050CTL_KERN,1051#if defined(__NetBSD__)1052KERN_PROC_ARGS,1053-1,1054KERN_PROC_PATHNAME,1055#else1056KERN_PROC,1057KERN_PROC_PATHNAME,1058-1,1059#endif1060};1061size_t sz = program_path_size;10621063if (sysctl(mib, 4, program_path, &sz, NULL, 0) == 0)1064#else1065#error Unmatched ifdef.1066#endif1067{1068program_path[sizeof(program_path) - 1] = '\0';1069char *last_slash = strrchr(program_path, '/');1070if (last_slash != nullptr)1071*last_slash = '\0';1072ExePath = Path(program_path);1073}1074#endif1075}10761077return ExePath;1078}107910801081IOFile::IOFile(const Path &filename, const char openmode[]) {1082Open(filename, openmode);1083}10841085IOFile::~IOFile() {1086Close();1087}10881089bool IOFile::Open(const Path& filename, const char openmode[])1090{1091Close();1092m_file = File::OpenCFile(filename, openmode);1093m_good = IsOpen();1094return m_good;1095}10961097bool IOFile::Close()1098{1099if (!IsOpen() || 0 != std::fclose(m_file))1100m_good = false;11011102m_file = NULL;1103return m_good;1104}11051106std::FILE* IOFile::ReleaseHandle()1107{1108std::FILE* const ret = m_file;1109m_file = NULL;1110return ret;1111}11121113void IOFile::SetHandle(std::FILE* file)1114{1115Close();1116Clear();1117m_file = file;1118}11191120uint64_t IOFile::GetSize()1121{1122if (IsOpen())1123return File::GetFileSize(m_file);1124else1125return 0;1126}11271128bool IOFile::Seek(int64_t off, int origin)1129{1130if (!IsOpen() || 0 != fseeko(m_file, off, origin))1131m_good = false;11321133return m_good;1134}11351136uint64_t IOFile::Tell()1137{1138if (IsOpen())1139return ftello(m_file);1140else1141return -1;1142}11431144bool IOFile::Flush()1145{1146if (!IsOpen() || 0 != std::fflush(m_file))1147m_good = false;11481149return m_good;1150}11511152bool IOFile::Resize(uint64_t size)1153{1154if (!IsOpen() || 0 !=1155#ifdef _WIN321156// ector: _chsize sucks, not 64-bit safe1157// F|RES: changed to _chsize_s. i think it is 64-bit safe1158_chsize_s(_fileno(m_file), size)1159#else1160// TODO: handle 64bit and growing1161ftruncate(fileno(m_file), size)1162#endif1163)1164m_good = false;11651166return m_good;1167}11681169bool ReadFileToStringOptions(bool textFile, bool allowShort, const Path &filename, std::string *str) {1170FILE *f = File::OpenCFile(filename, textFile ? "r" : "rb");1171if (!f)1172return false;1173// Warning: some files, like in /sys/, may return a fixed size like 4096.1174size_t len = (size_t)File::GetFileSize(f);1175bool success;1176if (len == 0) {1177// Just read until we can't read anymore.1178size_t totalSize = 1024;1179size_t totalRead = 0;1180do {1181totalSize *= 2;1182str->resize(totalSize);1183totalRead += fread(&(*str)[totalRead], 1, totalSize - totalRead, f);1184} while (totalRead == totalSize);1185str->resize(totalRead);1186success = true;1187} else {1188str->resize(len);1189size_t totalRead = fread(&(*str)[0], 1, len, f);1190str->resize(totalRead);1191// Allow less, because some system files will report incorrect lengths.1192// Also, when reading text with CRLF, the read length may be shorter.1193if (textFile) {1194// totalRead doesn't take \r into account since they might be skipped in this mode.1195// So let's just ask how far the cursor got.1196totalRead = ftell(f);1197}1198success = allowShort ? (totalRead <= len) : (totalRead == len);1199}1200fclose(f);1201return success;1202}12031204uint8_t *ReadLocalFile(const Path &filename, size_t *size) {1205FILE *file = File::OpenCFile(filename, "rb");1206if (!file) {1207*size = 0;1208return nullptr;1209}1210fseek(file, 0, SEEK_END);1211size_t f_size = ftell(file);1212if ((long)f_size < 0) {1213*size = 0;1214fclose(file);1215return nullptr;1216}1217fseek(file, 0, SEEK_SET);1218// NOTE: If you find ~10 memory leaks from here, with very varying sizes, it might be the VFPU LUTs.1219uint8_t *contents = new uint8_t[f_size + 1];1220if (fread(contents, 1, f_size, file) != f_size) {1221delete[] contents;1222contents = nullptr;1223*size = 0;1224} else {1225contents[f_size] = 0;1226*size = f_size;1227}1228fclose(file);1229return contents;1230}12311232bool WriteStringToFile(bool text_file, const std::string &str, const Path &filename) {1233FILE *f = File::OpenCFile(filename, text_file ? "w" : "wb");1234if (!f)1235return false;1236size_t len = str.size();1237if (len != fwrite(str.data(), 1, str.size(), f))1238{1239fclose(f);1240return false;1241}1242fclose(f);1243return true;1244}12451246bool WriteDataToFile(bool text_file, const void* data, size_t size, const Path &filename) {1247FILE *f = File::OpenCFile(filename, text_file ? "w" : "wb");1248if (!f)1249return false;1250if (size != fwrite(data, 1, size, f))1251{1252fclose(f);1253return false;1254}1255fclose(f);1256return true;1257}12581259void ChangeMTime(const Path &path, time_t mtime) {1260if (path.Type() == PathType::CONTENT_URI) {1261// No clue what to do here.1262return;1263}12641265#ifdef _WIN321266_utimbuf buf{};1267buf.actime = mtime;1268buf.modtime = mtime;1269_utime(path.c_str(), &buf);1270#else1271utimbuf buf{};1272buf.actime = mtime;1273buf.modtime = mtime;1274utime(path.c_str(), &buf);1275#endif1276}12771278bool IsProbablyInDownloadsFolder(const Path &filename) {1279INFO_LOG(Log::Common, "IsProbablyInDownloadsFolder: Looking at %s (%s)...", filename.c_str(), filename.ToVisualString().c_str());1280switch (filename.Type()) {1281case PathType::CONTENT_URI:1282{1283AndroidContentURI uri(filename.ToString());1284INFO_LOG(Log::Common, "Content URI provider: %s", uri.Provider().c_str());1285if (containsNoCase(uri.Provider(), "download")) {1286// like com.android.providers.downloads.documents1287return true;1288}1289break;1290}1291default:1292break;1293}1294return filename.FilePathContainsNoCase("download");1295}12961297} // namespace File129812991300