SnowflakePowered / snowflake

:snowflake: :video_game: Emulator Frontend and SDK
http://snowflakepowe.red
Mozilla Public License 2.0
250 stars 15 forks source link

Virtualized Filesystem Projection and Disposable Directories API #818

Closed chyyran closed 2 years ago

chyyran commented 3 years ago

Rationale

Snowflake's Filesystem API allows for complicated game installation for modern, NAND based consoles. However, at runtime, this becomes complicated with the need to copy over installed DLC data, and merge it with save files, and other system files that must be present at runtime into the runtime root.

We need to have some way of linking directories together on the underlying filesystem without needing to copy entire directory trees. #815 proposed a virtualized filesystem, but this requires dual implementations on both Linux and Windows, as well as a third-party external dependency (and unmanaged code on Linux to implement a FUSE driver).

Instead, a restricted API to create symbolic links onto the filesystem is proposed here. This means that the install script for Snowflake will have to enable Developer Mode on the user's system. I see this as equivalently invasive as installing a signed kernel-mode driver as WinFsp does, and this approach allows us to write relatively less platform-specific code.

API

Terminology: A projecting directory allows symlinks to be created inside, while a projectable directory is a directory that can have symlinks pointing to it.

When a runtime directory is requested for a game, it shall be a IDisposableDirectory , which aRE are lifetime limited, and will be deleted on Dispose.

IDisposableDirectory can open directories as "projecting", i.e. symbolic links can be created if no such existing file exists. Note that the root IDisposableDirectory is not projecting.

There are a few restrictions on IDisposableDirectory and IProjectingDirectory. IDisposableDirectory does not create manifests (there would be no point, since it is a temporary directory) and neither does IProjectingDirectory. IFile and IDirectory instances can be opened as normal, but they too will leave no manifest files behind.

IProjectableDirectory OpenProjectableDirectory(string name);
IReadOnlyFile Project(IFile file);
IReadOnlyFile Project(IFile file, string name);
IReadOnlyDirectory Project(IDirectory directory);
IReadOnlyDirectory Project(IDirectory directory, string name);

Once a directory has been opened as a projection, it is *inaccessible through `Open**. Doing so will throwUnauthorizedAccessException. Another handle to theIReadOnly*object will not be retrievable.RootedPath,UnsafeGetFilePathwill respect the symbolic link, andIFiles that are links will be dereferenced (symbolic links to their real paths will be reified), and theIReadOnlyFile` shall have the GUID of the link.

IDirectory instances with links shall not be projectable.

Notably, the resultant object is non-projecting. This ensures that only one level of symbolic link can be created.

On dispose, IDisposableDirectory will be deleted, along with all the symbolic links created under that file tree.

Drawbacks

All symbolic link libraries available for C# today are terrible and untested. Filesystem code has to be thoroughly tested to ensure we have a good base to build upon: consider vcdiff for our save-diffing implementation. We will have to write our own symlink library.

Cleanup can be managed as long as IDisposableDirectory is disposed. If it becomes dangling due to a crash, we are left with dangling symlinks. This is not too bad of an issue, but is a drawback as opposed to a virtual filesystem which would automatically unmount and become invalid on a crash.

chyyran commented 3 years ago

Fixed in #819

chyyran commented 2 years ago

I am convinced now that the symlink approach is not sustainable and it would be better to go WinFSP with a driver in Rust.