载入中...
搜索中...
未找到
Filesystem.cpp
浏览该文件的文档.
1
4
5#include <sys/stat.h>
6#include <sys/types.h>
7
8#include <algorithm>
9#include <atomic>
10#include <cerrno>
11#include <cstring>
12#include <filesystem>
13#include <string_view>
14#include <simplesquirrel/simplesquirrel.hpp>
15
16#include "common/Assert.h"
18
19#if defined(EVENGINE_MACOSX)
20#include "macosx/macosx.h"
21#elif defined(EVENGINE_IOS)
22#include "ios/ios.h"
23#elif defined(EVENGINE_ANDROID)
24#include "android/android.h"
25#elif defined(EVENGINE_WEBGPU)
26#include "webgpu/webplatform.h"
27#elif defined(EVENGINE_WINDOWS)
28#include <windows.h>
29
30#include "common/utf8.h"
31#elif defined(EVENGINE_LINUX)
32#include <unistd.h>
33#endif
34
35#if !defined(EVENGINE_WINDOWS) && !defined(EVENGINE_WEBGPU)
36#include <fcntl.h>
37#include <unistd.h>
38#endif
39
40#if defined(EVENGINE_WEBGPU) && defined(EVENGINE_WINDOWS)
41#include <windows.h>
42
43#include "common/utf8.h"
44#endif
45
46namespace eve {
47namespace filesystem {
48
49namespace {
50
51std::filesystem::path pathFromUtf8(std::string_view text) {
52 const auto *data = reinterpret_cast<const char8_t *>(text.data());
53 return std::filesystem::path(std::u8string_view(data, text.size()));
54}
55
56bool validRelativeSavePath(const std::filesystem::path &path) {
57 if (path.empty() || path.is_absolute() || path.has_root_name() || path.has_root_directory()) return false;
58 for (const auto &part : path) {
59 if (part == ".." || part == ".") return false;
60 }
61 return true;
62}
63
64std::atomic<std::uint64_t> &atomicWriteSequence() {
65 static std::atomic<std::uint64_t> sequence{1};
66 return sequence;
67}
68
69} // namespace
70
72
73bool Filesystem::writeText(const std::string &filename, const std::string &text) const {
74 try {
75 write(filename, text.data(), static_cast<int64_t>(text.size()));
76 return true;
77 } catch (const eve::Exception &) {
78 return false;
79 }
80}
81
82eve::Result<void> Filesystem::writeTextAtomic(std::string_view filename, std::string_view text) {
83#if defined(EVENGINE_WEBGPU)
84 (void)filename;
85 (void)text;
87 eve::Diagnostic::error(eve::DiagnosticCode::Unsupported, "atomic save replacement is unavailable on the WebGPU filesystem", {}, {}, "filesystem.atomic-write"));
88#else
89 const std::string relativeText(filename);
90 const std::filesystem::path relative = pathFromUtf8(relativeText).lexically_normal();
91 if (!validRelativeSavePath(relative))
93 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "atomic save path must be relative and cannot traverse parents", relativeText, {}, "filesystem.atomic-write"));
94 const std::string saveDirectory = getSaveDirectory();
95 if (saveDirectory.empty())
97 eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "filesystem write directory is not configured", relativeText, {}, "filesystem.atomic-write"));
98 const std::filesystem::path base = pathFromUtf8(saveDirectory).lexically_normal();
99 const std::filesystem::path target = (base / relative).lexically_normal();
100 if (target.parent_path().empty() || !std::filesystem::exists(target.parent_path()))
102 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "atomic save parent directory does not exist", relativeText, {}, "filesystem.atomic-write"));
103
104 const auto sequence = atomicWriteSequence().fetch_add(1, std::memory_order_relaxed);
105#if defined(EVENGINE_WINDOWS)
106 const std::wstring targetPath = target.wstring();
107 const std::wstring temporaryPath = targetPath + L".tmp." + std::to_wstring(GetCurrentProcessId()) +
108 L"." + std::to_wstring(sequence);
109 HANDLE file = CreateFileW(temporaryPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW,
110 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, nullptr);
111 if (file == INVALID_HANDLE_VALUE)
113 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "could not create atomic save temporary file", relativeText, {}, "filesystem.atomic-write"));
114 bool written = true;
115 std::size_t offset = 0;
116 while (offset < text.size()) {
117 const DWORD chunk = static_cast<DWORD>(std::min<std::size_t>(text.size() - offset, 0x7fffffffu));
118 DWORD completed = 0;
119 if (!WriteFile(file, text.data() + offset, chunk, &completed, nullptr) || completed != chunk) {
120 written = false;
121 break;
122 }
123 offset += completed;
124 }
125 if (written && !FlushFileBuffers(file)) written = false;
126 if (!CloseHandle(file)) written = false;
127 if (!written) {
128 DeleteFileW(temporaryPath.c_str());
130 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "could not write or flush atomic save temporary file", relativeText, {}, "filesystem.atomic-write"));
131 }
132 if (!MoveFileExW(temporaryPath.c_str(), targetPath.c_str(),
133 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
134 DeleteFileW(temporaryPath.c_str());
136 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "could not atomically replace save file", relativeText, {}, "filesystem.atomic-write"));
137 }
138#else
139 const std::string targetPath = target.string();
140 const std::string temporaryPath = targetPath + ".tmp." + std::to_string(getpid()) + "." +
141 std::to_string(sequence);
142 const int file = ::open(temporaryPath.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600);
143 if (file < 0)
145 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "could not create atomic save temporary file", relativeText, {}, "filesystem.atomic-write"));
146 bool written = true;
147 std::size_t offset = 0;
148 while (offset < text.size()) {
149 const ssize_t completed = ::write(file, text.data() + offset, text.size() - offset);
150 if (completed < 0) {
151 if (errno == EINTR) continue;
152 written = false;
153 break;
154 }
155 offset += static_cast<std::size_t>(completed);
156 }
157 if (written && ::fsync(file) != 0) written = false;
158 if (::close(file) != 0) written = false;
159 if (!written) {
160 ::unlink(temporaryPath.c_str());
162 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "could not write or flush atomic save temporary file", relativeText, {}, "filesystem.atomic-write"));
163 }
164 if (::rename(temporaryPath.c_str(), targetPath.c_str()) != 0) {
165 ::unlink(temporaryPath.c_str());
167 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "could not atomically replace save file", relativeText, {}, "filesystem.atomic-write"));
168 }
169#ifdef O_DIRECTORY
170 const int directory = ::open(target.parent_path().string().c_str(), O_RDONLY | O_DIRECTORY);
171 if (directory >= 0) {
172 (void)::fsync(directory);
173 (void)::close(directory);
174 }
175#endif
176#endif
178#endif
179}
180
181std::string Filesystem::readText(const std::string &filename) const {
182 try {
183 FileData *data = read(filename);
184 if (!data) return {};
185 const auto *bytes = static_cast<const char *>(data->getData());
186 std::string text(bytes, static_cast<size_t>(data->getSize()));
187 delete data;
188 return text;
189 } catch (const eve::Exception &) {
190 return {};
191 }
192}
193
194void Filesystem::expose(ssq::Table &table) {
195 auto cls = table.addClass(name, Filesystem::create, false);
196 expose(cls);
197}
198
199void Filesystem::expose(ssq::Class &cls) {
201 cls.addFunc("getName", &Filesystem::getName);
202 cls.addFunc("setFused", &Filesystem::setFused);
203 cls.addFunc("isFused", &Filesystem::isFused);
204 cls.addFunc("setupWriteDirectory", &Filesystem::setupWriteDirectory);
205 cls.addFunc("setAndroidSaveExternal", &Filesystem::setAndroidSaveExternal);
206 cls.addFunc("isAndroidSaveExternal", &Filesystem::isAndroidSaveExternal);
207 cls.addFunc("setIdentity", &Filesystem::setIdentity);
208 cls.addFunc("getIdentity", &Filesystem::getIdentity);
209 cls.addFunc("setSource", &Filesystem::setSource);
210 cls.addFunc("getSource", &Filesystem::getSource);
211 cls.addFunc("allowMountingForPath", &Filesystem::allowMountingForPath);
212 cls.addFunc("mountExternalReadOnly", &Filesystem::mountExternalReadOnly);
213 cls.addFunc("mountPath", [](Filesystem *self, const std::string &archive,
214 const std::string &mountpoint, bool appendToPath) {
215 return self && self->mount(archive, mountpoint, appendToPath);
216 });
217 cls.addFunc("unmountPath", [](Filesystem *self, const std::string &archive) {
218 return self && self->unmount(archive);
219 });
220 cls.addFunc("newFile", &Filesystem::newFile);
221 cls.addFunc("newFileData", &Filesystem::newFileData);
222 cls.addFunc("getWorkingDirectory", &Filesystem::getWorkingDirectory);
223 cls.addFunc("getUserDirectory", &Filesystem::getUserDirectory);
224 cls.addFunc("getAppdataDirectory", &Filesystem::getAppdataDirectory);
225 cls.addFunc("getSaveDirectory", &Filesystem::getSaveDirectory);
226 cls.addFunc("getSourceBaseDirectory", &Filesystem::getSourceBaseDirectory);
227 cls.addFunc("getRealDirectory", &Filesystem::getRealDirectory);
228 cls.addFunc("getExecutablePath", &Filesystem::getExecutablePath);
229 cls.addFunc("createDirectory", &Filesystem::createDirectory);
230 cls.addFunc("remove", &Filesystem::remove);
231 cls.addFunc("read", &Filesystem::read);
232 cls.addFunc("write", &Filesystem::write);
233 cls.addFunc("append", &Filesystem::append);
234 cls.addFunc("writeText", &Filesystem::writeText);
235 cls.addFunc("writeTextAtomic", [](Filesystem *self, const std::string &filename,
236 const std::string &text) -> int {
237 return self && self->writeTextAtomic(filename, text).ok() ? 1 : 0;
238 });
239 cls.addFunc("readText", &Filesystem::readText);
240 cls.addFunc("getDirectoryItems", &Filesystem::getDirectoryItems);
241 cls.addFunc("setSymlinksEnabled", &Filesystem::setSymlinksEnabled);
242 cls.addFunc("areSymlinksEnabled", &Filesystem::areSymlinksEnabled);
243 cls.addFunc("getRequirePath", &Filesystem::getRequirePath);
244 cls.addFunc("getCRequirePath", &Filesystem::getCRequirePath);
245 cls.addFunc("getRequirePath", &Filesystem::getRequirePath);
246 cls.addFunc("isRealDirectory", &Filesystem::isRealDirectory);
247 cls.addFunc("watch", &Filesystem::watch);
248 cls.addFunc("unwatch", &Filesystem::unwatch);
249 cls.addFunc("unwatchAll", &Filesystem::unwatchAll);
250 cls.addFunc("getWatchCount", &Filesystem::getWatchCount);
251 cls.addFunc("pollWatch", &Filesystem::pollWatch);
252 cls.addFunc("getLastWatchPath", &Filesystem::getLastWatchPath);
253 cls.addFunc("getLastWatchRealPath", &Filesystem::getLastWatchRealPath);
254}
255
256bool Filesystem::mountExternalReadOnly(const std::string &path, const std::string &mountpoint) {
257 if (path.empty() || mountpoint.empty())
258 return false;
260 return mount(path, mountpoint, false);
261}
262
263FileData *Filesystem::newFileData(const void *data, std::string filename, size_t size) const {
264 const bool validData = size == 0 || data != nullptr;
265 EV_PARAM_CHECK(validData, "file data must not be null when size > 0");
266 FileData *fd = new FileData(std::string(filename), size);
267 memcpy(fd->getData(), data, size);
268 return fd;
269}
270
271bool Filesystem::isRealDirectory(const std::string &path) const {
272#ifdef EVENGINE_WINDOWS
273 // make sure non-ASCII paths work.
274 std::wstring wpath = to_widestr(path);
275
276 struct _stat buf;
277 if (_wstat(wpath.c_str(), &buf) != 0) return false;
278 return (buf.st_mode & _S_IFDIR) == _S_IFDIR;
279#else
280 // Assume POSIX support...
281 struct stat buf;
282 if (stat(path.c_str(), &buf) != 0) return false;
283 return S_ISDIR(buf.st_mode) != 0;
284#endif
285}
286
287std::string Filesystem::getExecutablePath() const {
288#if defined(EVENGINE_MACOSX)
289 return macosx::getExecutablePath();
290#elif defined(EVENGINE_IOS)
291 return ios::getExecutablePath();
292#elif defined(EVENGINE_ANDROID)
293 return android::getExecutablePath();
294#elif defined(EVENGINE_WEBGPU)
295 return eve::webgpu_platform::getExecutablePath();
296#elif defined(EVENGINE_WINDOWS)
297 wchar_t buffer[MAX_PATH + 1] = {0};
298 if (GetModuleFileNameW(nullptr, buffer, MAX_PATH) == 0) return "";
299 return to_utf8(buffer);
300#elif defined(EVENGINE_LINUX)
301 char buffer[2048] = {0};
302 ssize_t len = readlink("/proc/self/exe", buffer, 2048);
303 if (len <= 0) return "";
304 return std::string(buffer, len);
305#else
306#error Missing implementation for Filesystem::getExecutablePath!
307#endif
308}
309
310} // namespace filesystem
311} // namespace eve
LogicalId target
Vec3 relative
Definition AnimSmr.cpp:164
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
Definition Assert.h:30
HSQOBJECT cls
Definition ECS.cpp:21
filesystem::File * file
std::string text
size_t offset
std::uint64_t bytes
std::string name
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::string path
Definition PlayHost.cpp:110
float size
Definition TreeMesh.cpp:156
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
virtual std::string getName() const =0
Returns the name.
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Data buffer paired with a filename (used for type identification).
Definition FileData.h:14
void * getData() const
Pointer to the owned byte buffer.
Definition FileData.h:27
EVENGINE_API_FOUNDATION public API.
Definition Filesystem.h:43
virtual std::string getSourceBaseDirectory() const =0
Gets the full path to the directory containing the game source. For example if the game source is C:\...
virtual void allowMountingForPath(const std::string &path)=0
Allows a full (OS-dependent) path to be used with Filesystem::mount.
virtual bool isAndroidSaveExternal() const
Gets whether the Android save is external. Returns a bool.
Definition Filesystem.h:109
virtual std::string getUserDirectory()=0
Gets the user home directory.
bool writeText(const std::string &filename, const std::string &text) const
Write a UTF-8 string to a file in the configured save directory.
virtual bool setSource(std::string source)=0
Sets the path to the game source. This can only be set once.
virtual std::string getExecutablePath() const
Gets the full platform-dependent path to the executable.
virtual bool setIdentity(std::string ident, bool appendToPath=false)=0
Sets the name of the save folder.
virtual std::vector< std::string > & getRequirePath()=0
virtual std::string getLastWatchRealPath() const =0
std::string readText(const std::string &filename) const
Read a UTF-8 string from the virtual filesystem.
virtual FileData * newFileData(const void *data, std::string filename, size_t size) const
Creates a new FileData object. Data will be copied.
virtual int getWatchCount() const =0
virtual File * newFile(std::string filename) const =0
Creates a new file.
virtual std::string getWorkingDirectory()=0
Gets the current working directory.
virtual void setFused(bool fused)=0
virtual std::vector< std::string > getDirectoryItems(std::string dir)=0
This "native" method returns a table of all files in a given directory.
virtual std::string pollWatch()=0
Pop next watch event kind: "added"|"removed"|"modified"|"movedFrom"|"movedTo". Empty string if queue ...
virtual void unwatchAll()=0
virtual bool unwatch(std::string path)=0
Stop watching a path previously passed to watch().
virtual std::string getRealDirectory(std::string filename) const =0
Gets the real directory path containing the file.
virtual bool isFused() const =0
virtual std::string getAppdataDirectory()=0
Gets the APPDATA directory. On Windows, this is the folder in the APPDATA% enviroment variable....
virtual std::string getSaveDirectory()=0
Gets the full path of the save folder.
virtual bool isRealDirectory(const std::string &path) const
Gets whether the given full (OS-dependent) path is a directory.
virtual std::string getLastWatchPath() const =0
virtual bool remove(std::string file)=0
Removes a file (or directory).
virtual bool mount(std::string archive, std::string mountpoint, bool appendToPath=false)=0
Mounts an archive (path or in-memory Data) at a virtual mountpoint.
bool mountExternalReadOnly(const std::string &path, const std::string &mountpoint)
Mount an external OS directory for read-only asset access.
virtual bool createDirectory(std::string dir)=0
Creates a directory. Write dir must be set.
virtual std::string getIdentity() const =0
virtual bool areSymlinksEnabled() const =0
Gets whether symbolic link support is enabled.
virtual void write(std::string filename, const void *data, int64_t size) const =0
Write data to a file.
virtual bool setupWriteDirectory()=0
This sets up the save directory. If the it is already set up, nothing happens.
virtual FileData * read(std::string filename, int64_t size=File::ALL) const =0
Reads data from a file.
virtual void append(std::string filename, const void *data, int64_t size) const =0
Append data to a file, creating it if it doesn't exist.
virtual void setSymlinksEnabled(bool enable)=0
Enable or disable symbolic link support in love.filesystem.
virtual std::string getSource() const =0
Gets the path to the game source. Returns a 0-length string if the source has not been set.
virtual void setAndroidSaveExternal(bool external=false)
This sets the save location on Android. False for internal, true for external
Definition Filesystem.h:103
virtual std::vector< std::string > & getCRequirePath()=0
eve::Result< void > writeTextAtomic(std::string_view filename, std::string_view text)
Durably replace a UTF-8 text file in the configured save directory.
virtual bool watch(std::string path)=0
Watch a virtual or absolute OS path (file or directory). File watches monitor the parent directory an...
PhysFS 文件系统后端实现(挂载/解挂、读写、热重载)。
Definition Filesystem.h:21
void exposePreparedFileBindings(ssq::Class &cls)
Install game-thread script submission bindings; retains no class borrow.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16