/* * The ManaPlus Client * Copyright (C) 2013-2020 The ManaPlus Developers * Copyright (C) 2020-2023 The ManaVerse Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "fs/virtfs/fsdir.h" #include "fs/files.h" #include "fs/mkdir.h" #include "fs/paths.h" #include "fs/virtfs/direntry.h" #include "fs/virtfs/file.h" #include "fs/virtfs/fsdirrwops.h" #include "fs/virtfs/fsfuncs.h" #include "fs/virtfs/list.h" #include "utils/cast.h" #include "utils/checkutils.h" #include "utils/foreach.h" #include "utils/stdmove.h" #include "utils/stringutils.h" #include #include #include #include #include "debug.h" extern const char *dirSeparator; namespace VirtFs { namespace { std::string mWriteDir; std::string mBaseDir; std::string mUserDir; bool mPermitLinks = false; FsFuncs funcs; } // namespace namespace FsDir { File *openInternal(FsEntry *restrict const entry, const std::string &filename, const FILEMTYPE mode) { const std::string path = static_cast(entry)->rootSubDir + filename; if (Files::existsLocal(path) == false) return nullptr; FILEHTYPE fd = FILEOPEN(path.c_str(), mode); if (fd == FILEHDEFAULT) { reportAlways("VirtFs::open file open error: %s", filename.c_str()) return nullptr; } return new File(&funcs, fd); } File *openRead(FsEntry *restrict const entry, std::string filename) { return openInternal(entry, filename, FILEOPEN_FLAG_READ); } File *openWrite(FsEntry *restrict const entry, const std::string &filename) { return openInternal(entry, filename, FILEOPEN_FLAG_WRITE); } File *openAppend(FsEntry *restrict const entry, const std::string &filename) { return openInternal(entry, filename, FILEOPEN_FLAG_APPEND); } void deinit() { } #if defined(__native_client__) void init(const std::string &restrict name A_UNUSED) { mBaseDir = "/"; #elif defined(ANDROID) void init(const std::string &restrict name A_UNUSED) { mBaseDir = getRealPath("."); #else // defined(__native_client__) void init(const std::string &restrict name) { mBaseDir = getRealPath(getFileDir(name)); #endif // defined(__native_client__) prepareFsPath(mBaseDir); mUserDir = getHomePath(); prepareFsPath(mUserDir); initFuncs(&funcs); } void initFuncs(FsFuncs *restrict const ptr) { ptr->close = &FsDir::close; ptr->read = &FsDir::read; ptr->write = &FsDir::write; ptr->fileLength = &FsDir::fileLength; ptr->tell = &FsDir::tell; ptr->seek = &FsDir::seek; ptr->eof = &FsDir::eof; ptr->exists = &FsDir::exists; ptr->getRealDir = &FsDir::getRealDir; ptr->enumerate = &FsDir::enumerate; ptr->isDirectory = &FsDir::isDirectory; ptr->openRead = &FsDir::openRead; ptr->openWrite = &FsDir::openWrite; ptr->openAppend = &FsDir::openAppend; ptr->loadFile = &FsDir::loadFile; ptr->getFiles = &FsDir::getFiles; ptr->getFilesWithDir = &FsDir::getFilesWithDir; ptr->getDirs = &FsDir::getDirs; ptr->rwops_seek = &FsDir::rwops_seek; ptr->rwops_read = &FsDir::rwops_read; ptr->rwops_write = &FsDir::rwops_write; ptr->rwops_close = &FsDir::rwops_close; #ifdef USE_SDL2 ptr->rwops_size = &FsDir::rwops_size; #endif // USE_SDL2 } FsFuncs *getFuncs() { return &funcs; } const char *getBaseDir() { return mBaseDir.c_str(); } const char *getUserDir() { return mUserDir.c_str(); } bool getRealDir(FsEntry *restrict const entry, std::string filename, std::string dirName A_UNUSED, std::string &realDir) { const DirEntry *const dirEntry = static_cast(entry); if (Files::existsLocal(dirEntry->rootSubDir + filename)) { realDir = dirEntry->userDir; return true; } return false; } bool exists(FsEntry *restrict const entry, std::string fileName, std::string dirName A_UNUSED) { return Files::existsLocal(static_cast(entry)->rootSubDir + fileName); } void enumerate(FsEntry *restrict const entry, std::string dirName, StringVect &names) { const std::string path = static_cast(entry)->rootSubDir + dirName; const dirent *next_file = nullptr; DIR *const dir = opendir(path.c_str()); if (dir != nullptr) { while ((next_file = readdir(dir)) != nullptr) { const std::string file = next_file->d_name; if (file == "." || file == "..") continue; #ifndef WIN32 if (mPermitLinks == false) { struct stat statbuf; if (lstat(path.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode) != 0) { continue; } } #endif // WIN32 bool found(false); FOR_EACH (StringVectCIter, itn, names) { if (*itn == file) { found = true; break; } } if (found == false) names.push_back(file); } closedir(dir); } } bool isDirectory(FsEntry *restrict const entry, std::string dirName, bool &isDirFlag) { std::string path = static_cast(entry)->rootSubDir + dirName; struct stat statbuf; if (stat(path.c_str(), &statbuf) == 0) { isDirFlag = (S_ISDIR(statbuf.st_mode) != 0); return true; } return false; } bool isSymbolicLink(std::string name) { prepareFsPath(name); if (checkPath(name) == false) { reportAlways("FsDir::isSymbolicLink invalid path: %s", name.c_str()) return false; } #ifndef WIN32 if (mPermitLinks == false) return false; struct stat statbuf; return lstat(name.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode) != 0; #else return false; #endif // WIN32 } void freeList(List *restrict const handle) { delete handle; } bool setWriteDir(std::string newDir) { prepareFsPath(newDir); mWriteDir = STD_MOVE(newDir); if (findLast(mWriteDir, std::string(dirSeparator)) == false) mWriteDir += dirSeparator; return true; } bool mkdir(std::string dirname) { prepareFsPath(dirname); if (mWriteDir.empty()) { reportAlways("FsDir::mkdir write dir is empty") return false; } return mkdir_r((mWriteDir + dirname).c_str()) != -1; } bool remove(std::string filename) { prepareFsPath(filename); if (mWriteDir.empty()) { reportAlways("FsDir::remove write dir is empty") return false; } return ::remove((mWriteDir + filename).c_str()) != 0; } void permitLinks(const bool val) { mPermitLinks = val; } int close(File *restrict const file) { if (file == nullptr) return 0; delete file; return 1; } int64_t read(File *restrict const file, void *restrict const buffer, const uint32_t objSize, const uint32_t objCount) { if (file == nullptr) return 0; FILEHTYPE fd = file->mFd; if (fd == FILEHDEFAULT) { reportAlways("FsDir::read file not opened.") return 0; } #ifdef USE_FILE_FOPEN return fread(buffer, objSize, objCount, fd); #else // USE_FILE_FOPEN int max = objSize * objCount; int cnt = ::read(fd, buffer, max); if (cnt <= 0) return cnt; return cnt / objSize; #endif // USE_FILE_FOPEN } int64_t write(File *restrict const file, const void *restrict const buffer, const uint32_t objSize, const uint32_t objCount) { if (file == nullptr) return 0; FILEHTYPE fd = file->mFd; if (fd == FILEHDEFAULT) { reportAlways("FsDir::write file not opened.") return 0; } #ifdef USE_FILE_FOPEN return fwrite(buffer, objSize, objCount, fd); #else // USE_FILE_FOPEN int max = objSize * objCount; int cnt = ::write(fd, buffer, max); if (cnt <= 0) return cnt; return cnt / objSize; #endif // USE_FILE_FOPEN } int64_t fileLength(File *restrict const file) { if (file == nullptr) return -1; FILEHTYPE fd = file->mFd; if (fd == FILEHDEFAULT) { reportAlways("FsDir::fileLength file not opened.") return 0; } #ifdef USE_FILE_FOPEN const long pos = ftell(fd); if (pos < 0) { reportAlways("FsDir::fileLength ftell error.") return -1; } fseek(fd, 0, SEEK_END); const long sz = ftell(fd); fseek(fd, pos, SEEK_SET); return sz; #else // USE_FILE_FOPEN struct stat statbuf; if (fstat(fd, &statbuf) == -1) { reportAlways("FsDir::fileLength error.") return -1; } return static_cast(statbuf.st_size); #endif // USE_FILE_FOPEN } int64_t tell(File *restrict const file) { if (file == nullptr) return -1; FILEHTYPE fd = file->mFd; if (fd == FILEHDEFAULT) { reportAlways("FsDir::tell file not opened.") return 0; } #ifdef USE_FILE_FOPEN return ftell(fd); #else // USE_FILE_FOPEN return lseek(fd, 0, SEEK_CUR); #endif // USE_FILE_FOPEN } int seek(File *restrict const file, const uint64_t pos) { if (file == nullptr) return 0; FILEHTYPE fd = file->mFd; if (fd == FILEHDEFAULT) { reportAlways("FsDir::seek file not opened.") return 0; } const int64_t res = FILESEEK(fd, pos, SEEK_SET); if (res == -1) return 0; return 1; } int eof(File *restrict const file) { if (file == nullptr) return -1; FILEHTYPE fd = file->mFd; if (fd == FILEHDEFAULT) { reportAlways("FsDir::eof file not opened.") return 0; } #ifdef USE_FILE_FOPEN const int flag = feof(fd); if (flag != 0) return 1; const int64_t pos = ftell(fd); const int64_t len = fileLength(file); #else // USE_FILE_FOPEN const int64_t pos = lseek(fd, 0, SEEK_CUR); struct stat statbuf; if (fstat(fd, &statbuf) == -1) { reportAlways("FsDir::fileLength error.") return -1; } const int64_t len = static_cast(statbuf.st_size); #endif // USE_FILE_FOPEN return static_cast(pos < 0 || len < 0 || pos >= len); } const char *loadFile(FsEntry *restrict const entry, std::string filename, int &restrict fileSize) { const DirEntry *const dirEntry = static_cast(entry); const std::string path = dirEntry->rootSubDir + filename; if (Files::existsLocal(path) == false) return nullptr; FILEHTYPE fd = FILEOPEN(path.c_str(), FILEOPEN_FLAG_READ); if (fd == FILEHDEFAULT) { reportAlways("VirtFs::loadFile file open error: %s", filename.c_str()) return nullptr; } logger->log("Loaded %s/%s", dirEntry->userDir.c_str(), filename.c_str()); #ifdef USE_FILE_FOPEN fseek(fd, 0, SEEK_END); const long sz = ftell(fd); if (sz < 0) { reportAlways("FsDir::fileLength ftell error.") if (fd != FILEHDEFAULT) FILECLOSE(fd); return nullptr; } fseek(fd, 0, SEEK_SET); fileSize = static_cast(sz); #else // USE_FILE_FOPEN struct stat statbuf; if (fstat(fd, &statbuf) == -1) { reportAlways("FsDir::fileLength error.") if (fd != FILEHDEFAULT) FILECLOSE(fd); return nullptr; } fileSize = static_cast(statbuf.st_size); #endif // USE_FILE_FOPEN // Allocate memory and load the file char *restrict const buffer = new char[CAST_SIZE(fileSize)]; if (fileSize > 0) buffer[fileSize - 1] = 0; #ifdef USE_FILE_FOPEN const int cnt = CAST_S32(fread(buffer, 1, fileSize, fd)); #else // USE_FILE_FOPEN const int cnt = ::read(fd, buffer, fileSize); #endif // USE_FILE_FOPEN if (cnt <= 0) { delete [] buffer; if (fd != FILEHDEFAULT) FILECLOSE(fd); return nullptr; } if (fd != FILEHDEFAULT) FILECLOSE(fd); return buffer; } void getFiles(FsEntry *restrict const entry, std::string dirName, StringVect &names) { const std::string path = static_cast(entry)->rootSubDir + dirName; const dirent *next_file = nullptr; DIR *const dir = opendir(path.c_str()); if (dir != nullptr) { while ((next_file = readdir(dir)) != nullptr) { struct stat statbuf; const std::string file = next_file->d_name; if (file == "." || file == "..") continue; #ifndef WIN32 if (mPermitLinks == false) { if (lstat(path.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode) != 0) { continue; } } #endif // WIN32 const std::string filePath = pathJoin(path, file); if (stat(filePath.c_str(), &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode) != 0) continue; } bool found(false); FOR_EACH (StringVectCIter, itn, names) { if (*itn == file) { found = true; break; } } if (found == false) names.push_back(file); } closedir(dir); } } void getFilesWithDir(FsEntry *restrict const entry, const std::string &dirName, StringVect &names) { const std::string path = static_cast(entry)->rootSubDir + dirName; const dirent *next_file = nullptr; DIR *const dir = opendir(path.c_str()); if (dir != nullptr) { while ((next_file = readdir(dir)) != nullptr) { struct stat statbuf; const std::string file = next_file->d_name; if (file == "." || file == "..") continue; #ifndef WIN32 if (mPermitLinks == false) { if (lstat(path.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode) != 0) { continue; } } #endif // WIN32 const std::string filePath = pathJoin(path, file); if (stat(filePath.c_str(), &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode) != 0) continue; } bool found(false); FOR_EACH (StringVectCIter, itn, names) { if (*itn == file) { found = true; break; } } if (found == false) names.push_back(pathJoin(dirName, file)); } closedir(dir); } } void getDirs(FsEntry *restrict const entry, std::string dirName, StringVect &names) { const std::string path = static_cast(entry)->rootSubDir + dirName; const dirent *next_file = nullptr; DIR *const dir = opendir(path.c_str()); if (dir != nullptr) { while ((next_file = readdir(dir)) != nullptr) { struct stat statbuf; const std::string file = next_file->d_name; if (file == "." || file == "..") continue; #ifndef WIN32 if (mPermitLinks == false) { if (lstat(path.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode) != 0) { continue; } } #endif // WIN32 const std::string filePath = pathJoin(path, file); if (stat(filePath.c_str(), &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode) == 0) continue; } bool found(false); FOR_EACH (StringVectCIter, itn, names) { if (*itn == file) { found = true; break; } } if (found == false) names.push_back(file); } closedir(dir); } } } // namespace FsDir } // namespace VirtFs