Small Grant: Sia NFS Gateway (sia-nfs)

Project Name:

Sia NFS Gateway (sia-nfs)

Name of the organization or individual submitting the proposal:

Roland Rauch

Describe your project:

sia-nfs provides access to one or more Sia buckets via a regular NFS interface, allowing any compatible client to access the hosted files and directories directly without the need to run a Sia client or renterd. This gateway can be used from localhost to mount a Sia filesystem locally or made available to an entire network. Accessing and mounting are not limited to Linux; clients on MacOS, Windows, and possibly other operating systems should work without major issues. sia-nfs will be implemented in Rust, a safe and performant modern programming language, and will be available on all platforms where renterd is available. The resulting binary will be a standalone solution with no other dependencies besides renterd and common system libraries. This project proposal is in response to an RFP found at https://sia.tech/grants.

How does the projected outcome serve the Foundation’s mission of user-owned data?

Direct NFS accessibility offers several advantages:

  • It allows transparent access to Sia-hosted content from most applications.
  • A single gateway can make Sia content available to an entire network.
  • NFS clients are well-established and available on many platforms.
  • Enterprise integration will be much easier with the gateway, fitting into existing IT strategies.

Grant Specifics

Amount of money requested and justification with a reasonable breakdown of expenses:

The total amount requested is USD 6,400, which covers:

  • 8 weeks of full-time work (320 hours @ USD 20/hour).

No additional equipment is required. During development, the testnet will be used, so no SC are required.

What are the goals of this small grant?

The goal of the grant is to provide enough funding to allow the development of the Sia NFS Gateway. The time estimate is based on previous experience building virtual file systems and the existence of an NFS server library that can be used.

Development Timeline:

Two milestones are planned:

  • Milestone 1: Version 0.1.0 at the end of week 4. This version will be very basic and mostly untested. Basic functionality will be mostly complete, but performance will be slow, metadata caching will not be present, and write support might not be stable.
  • Milestone 2: Version 0.2.0 at the end of week 8. Metadata caching will be implemented, and performance will have improved accordingly. Write support will now be stable (however, partial writes might not be supported). Clients on major platforms (Linux, Windows, MacOS) will have been tested. Usage documentation and a Docker image will be available. This will be the first generally usable release.

Features & Scope:

  • A single, standalone program that runs on every platform where renterd is available.
  • Integrated metadata cache to improve access performance, reduce latency, and lower usage costs.
  • Fully open source (Apache-2.0 license) with a public repository on GitHub.
  • Basic usage documentation and example configurations.
  • A small, standalone Docker image that includes sia-nfs and renterd for a complete out-of-the-box solution.

Potential risks that may affect the outcome of the project:

  • The current renterd API is not ideal for some filesystem usage patterns. This can lead to performance issues and will require certain trade-offs such as proactive metadata caching and usage limitations, e.g., partial writes cannot be easily supported.
  • Excessive metadata caching could lead to stale data as the current renterd API has no mechanism to subscribe to external change events.
  • NFS is a somewhat archaic protocol with a diverse number of clients. The NFS server library this project is going to use (https://github.com/xetdata/nfsserve) is a young project, and compatibility is still limited. Depending on the use case and the client used, this could lead to issues that could limit the gateway’s usefulness.
  • Windows does not come with its NFS client preinstalled. It needs to be installed first and requires Windows Pro, so Home users will not be able to use it. If widespread Windows support is required, WebDAV support could be added. However, this would add 2-3 weeks of additional development and testing time and would therefore require additional funding.

Development Information

Will all of your project’s code be open-source?

Yes, the code will be fully open source and will be made available on GitHub (Apache-2.0 license). Furthermore, all libraries used are also open source.

Leave a link where the code will be accessible for review.

A repository will be created on GitHub once the grant is approved.

Do you agree to submit monthly progress reports?

Certainly!

Contact Info

Email: [email protected]

1 Like

Great proposal, finally an app that I could use

If there are any questions, or if anything is unclear, please let me know.

Hello,
Can you please provide some more information about this so that i can understand this .

Sure, what do you want to know in particular?

Thanks for your proposal to the Sia Foundation Grants Program. After review, the committee has decided to approve this proposal!

The committee saw your existing work on Github and appreciated your efforts. They liked the focus of your proposal and appreciated your realistic milestones and development goals.

We’ll reach out to the email address provided to start onboarding to the program. Onboarding can take up to a couple of weeks, so we’ll re-align on your milestone dates and deliverables once we’re close. Congratulations!

Here is my first progress report, as agreed upon:

What progress was made on your grant this month?

  • Set up a dev environment, including a renterd instance connected to the testnet.
  • Familiarized myself with the renterd API & the renterd source code
  • Developed a robust, idiomatic Rust client library for renterd, covering nearly the entire API. This library serves as a stable foundation for future development.
  • Worked through the semantic differences between how Sia objects and NFS operate, finding a good way to bridge the gap without too many drawbacks.
  • Began implementing the actual application and its required functionality, nearly reaching Milestone 1 (which is due in 2 weeks).

Links to repos worked on this month:

What will you be working on this month?

  • The immediate priority is to complete basic write support, the only missing feature for Milestone 1. This should be finished in the next few days.
  • Next, some cleanup and improved error handling are necessary.
  • After that, the metadata cache will be implemented, which shouldn’t take long since caching was considered in the overall design.
  • A decision needs to be made regarding “partial writing” (= modifying existing files).
  • Finally, more testing will be done, including with clients other than the Linux NFS client.
1 Like

In addition to the above progress report, I would like to provide further details about what has happened so far, why certain decisions were made, and how these decisions affect the project. Furthermore, I am asking for community feedback on a few aspects and the overall direction from here.

1. About renterd_client - the Rust client library

Although not strictly part of the initial grant proposal, I decided to move the low-level renterd API interactions into a separate library (or crate, as they are called in Rust-Land). My reasons were:

  • Separation of concerns: It is easier to reason about and work with a dedicated library.
  • Better testability: renterd_client has almost 100 Sans-IO unit tests.
  • Improved productivity: Working with a tested, idiomatic library streamlines development.
  • Reusability: The code needs to be implemented anyway. As a standalone crate, it can be reused across projects. Future Rust projects will only need to add a single dependency to interface with renterd.

This approach required extra effort initially, but I believed that it would pay off in the end. The implementation took about two weeks, during which I had to simultaneously look at the API docs, the renterd source code, and send manual requests with curl to double-check everything. Frankly this process was somewhat tedious, but the resulting library has been rock-solid.

Once this project progresses further, I plan to publish the library on crates.io, the main Rust crate repository, making it easily accessible to any Rust-based project. I hope this library will be a useful tool for the Sia ecosystem, providing a reusable, well-tested component that more projects can be built upon.

There is still some work to be done, as I had to modify a third-party
crate (reqwest-file) to make everything work. I intend to contribute my changes back to the original crate. However, reqwest-file appears to be very inactive, and I might need to fork it.

2. NFS vs Sia Objects - Semantic Differences and the “Need for State”

When I started working on the NFS gateway, my plan was to keep it basically stateless—no persistent state across restarts. This would not only simplify development but also improve the user experience. Just the binary and config, no other state. Simple.

However, when examining the differences between how NFS expects a file system to work and the reality of how Sia Objects function, I encountered a significant issue:

NFS is Inode-Based, Not Path-Based

NFS expects every inode to have a permanent ID (a 64-bit uint). Emphasis on permanent—this ID is never supposed to change. Nearly all operations use this ID to refer to the inode in question. Sia Objects, on the other hand, are path-based and lack a separate ID (at least not via the renterd object API; I’m not sure about the low-level details). This semantic gap needs to be bridged somehow. Here are a few approaches:

Idea 1: Naive Stateless Mapping

Derive an ID from the object’s path using a hasher. 64-bit is large enough to disregard the collision risk, provided a decent hashing algorithm is used, e.g., XXH3.

Pros:

  • Easy to implement, no persistent state needed

Cons:

  • Changing the object’s path (e.g., rename or move) will change its ID
  • Worse, if it’s a directory, all underlying object IDs will change recursively

Conclusion: Disqualified

Idea 2: Manually Assigning an ID

Build an in-memory representation of the file system hierarchy by getting the full list of all objects in a bucket at startup. Assign each object an ID (using a simple counter) and store it in a data structure on the heap. Periodically resync in the background to add/remove inodes as needed.

Pros:

  • Inode IDs are independent of the object’s path
  • In-band modifications, such as rename/move, are immediately reflected in the data structure
  • Super-fast metadata operations with no need for a separate cache

Cons:

  • Slow startup as all buckets need to be read completely before the gateway is ready
  • Increased memory usage
  • Risk of stale metadata since resyncs won’t happen often
  • Changes could lead to IDs being assigned differently after a restart

Conclusion: Disqualified

The Showstopper: Stale IDs Can Lead to Data Loss

The major issue with the above non-persistent state approaches is that IDs can change while NFS clients assume they cannot. The worst-case scenario would be a user deleting the file /tmp/useless.tmp which the NFS client believes has ID 100413, while sia-nfs links 100413 to /very/important/file, resulting in the wrong file being deleted. This scenario is unacceptable. Thus, I accepted that some persistent state is necessary.

Idea 3: Assign IDs at First Sight and Store Persistently

This approach stores the state in an embedded SQLite database. The schema is intentionally simple. Here’s an example:

id entry_type name parent
10001 D dir_name 101
10002 F file_name 10002

Whenever metadata is received from renterd, it gets synced with the relevant entries in the database. New entries get assigned a new ID, missing entries get deleted, and existing entries get mapped to their ID. Importantly, this approach uses an AUTOINCREMENT ID, ensuring IDs always increase. I’m using SQLite via the excellent sqlx crate. The database engine is embedded in the binary, along with the schema and any migration scripts. sia-nfs remains a single binary and runs as a single process.

Pros:

  • Inodes are independent of the object path and persist across restarts
  • The aforementioned data-loss scenario is not possible (as long as the database file remains available)
  • Fast startup time (no prefetching of objects)
  • Memory usage doesn’t change significantly
  • All normal file system operations remain possible
  • Performance is not noticeably affected
  • No additional runtime dependencies or maintenance required

Cons:

  • Persistent storage is required (a couple of MiB should suffice for most cases)
  • More implementation work compared to other approaches
  • Larger binary, as SQLite runtime is linked into it
  • Requires a C compiler when building from source, in addition to rustc

Conclusion: Winner, winner, chicken dinner

3. NFS(v3) Read and Write Operations Are Stateless

Reading from and writing to a file in a typical file system involves the following steps:

  1. open(inode) -> file_handle
  2. read(file_handle, offset, buffer) -> bytes_read
  3. write(file_handle, offset, buffer) -> bytes_written
  4. close(fh)

However, NFS version 3 (as implemented by nfsserve) simplifies this process:

  • reading: read(inode, offset, buffer) -> bytes_read
  • writing: write(inode, offset, buffer) -> bytes_written

NFSv3 skips the open/close steps, using the inode ID instead of file handles. This design likely reduces network traffic, which is beneficial when open/close operations are inexpensive, such as in a local file system.

However, this stateless approach presents specific challenges:

Reading

Each read operation requires a full GET request, rather than streaming the file content via a single request. Small read buffers can result in numerous tiny requests to the renterd object API, which is resource-intensive and slows down read speed.

To mitigate this, I’ve implemented a DownloadManager. It reuses connections for the same file and offset, opening new ones as needed and closing idle connections after a period of inactivity.

While this approach theoretically reduces the problem, in practise NFS clients often read ahead, sending multiple, non-contiguous read requests simultaneously, which undermines the DownloadManager. I’ve made further adjustments to address this behavior, but more work is needed.

Writing

Writing is more complex. Sia Objects are immutable, they can not be modified or amended. Therefore, uploads have to be done in a single operation. In a stateful environment (open, write, write …, close), this is manageable. However, NFSv3’s stateless operations require a workaround.

I’ve created an UploadManager, analogous to the DownloadManager, which starts and reuses uploads for matching file and offset writes. Without a close command, the upload automatically closes after a period of inactivity.

This solution isn’t perfect. Very slow writers may cause incomplete uploads, and clients expecting immediate read access after writing will experience blocking until the upload completes.

Conclusion

This aspect of the project has been particularly challenging. The current workarounds are functional but not flawless. The UploadManager is still in progress, and the DownloadManager requires further refinement to handle read-ahead behavior more effectively.

Given these constraints, the solutions provided are satisfactory. NFS v4, which follows a more traditional " open/read/write/close" model, does not have these issues. Unfortunately, nfsserve does not yet support NFS v4.

4. Editing Existing File (aka Partial Writing)

As mentioned earlier, Sia Objects are immutable and cannot be edited or amended, only overwritten. This differs from typical file system behaviour, creating a gap that needs addressing. However, it’s unclear if implementing this is a good idea. Here’s why:

The way to emulate traditional file system behavior is to implement a WAL (Write-Ahead-Log). When a client writes to an existing file, the data is first written to a local WAL. The WAL tracks the data and offset for all write operations until the client is finished. Meanwhile, we “overlay” the WAL over the Sia Object for the local user to create the illusion of a modified file.

After the user completes all modifications (detected again through inactivity due to the lack of a close operation), we create a new temporary object in the bucket. We then stream data from the original object, mixed with data from the WAL, to the new object. Once done, the old object is deleted, and the temporary object is renamed, effectively replacing the old object.

This process is costly, both in implementation effort and for the user. To illustrate:

A user edits a 10 GiB file, modifying 100 bytes. The user expects a simple, quick, and cheap operation, as only 100 bytes were changed. In reality, we must download 10 GiB and upload 10 GiB and store a new 10 GiB object. This behavior is unexpected and can be costly and frustrating for users.

Request for Feedback

I am reaching out to the community for discussion on whether this should be implemented. Personally, I believe this is not a good idea. However, I welcome other opinions. If it’s decided that this is necessary, I will proceed with the implementation. Until then, I will put this on hold.

5. Potential Extension of Project Scope

Below is a rough diagram illustrating the workings of sia-nfs:

                ---------------------- sia-nfs -------------------- 
NFS client <--> | NFS Frontend <--> VFS Layer <--> renterd_client | <--> renterd

Most of the heavy lifting is done in either the VFS Layer or within the renterd_client crate. The NFS Frontend part is NFS-specific, but it’s actually only a few lines of code (aside from the DownloadManager and UploadManager mentioned earlier). All the complex logic is implemented in a protocol-agnostic way. At this point, adding support for additional frontend protocols is relatively simple.

Request for Feedback

The project scope has been clear from the start and was outlined in the grant proposal. However, as the project has progressed - and it has progressed better than expected, putting me ahead of my original timeline - it has become evident that adding additional protocols could be an easy enhancement.

I am now seeking feedback from the community on whether there is interest in extending the project scope to become a multi-protocol Sia gateway, instead of only supporting NFS. My proposal includes adding support for:

  • FTP - due to its widespread use
  • WebDav - because all major operating systems support mounting it out of the box, including Windows

Both protocols seem to have usable Rust server libraries, making their implementation straightforward. To clarify: I am not requesting additional funding, as this extra work fits within the already granted budget. This opportunity arises from the architectural decisions made and the better-than-expected progress. Adding additional protocols is essentially free. However, it does modify the project scope and would necessitate a name change (possibly to sia-vfs).

I am opening this up for community discussion. In my opinion, this would be a beneficial enhancement. Please share your thoughts on this proposal.

My thoughts:

  1. You identified a risk which is using a young NFS server library that may not have the widest compatibility yet. Given the impressive logic of the rest of your post I would have thought this to be an issue for you too, may I understand the reasoning for choosing this particular library? As I think aiming for the widest compatibility is always the best choice when it comes to something like this that faces the user. They will not understand the nuance between NFS interface provided by Sia vs one provided by a more traditional storage appliance. And will simply move on if it doesn’t work.

  2. Based on reading through your reasoning I agree idea 3 sounds sane but at the same time I have not built something like this myself.

  3. My feedback for your post would be that overall you seem to have a good head on your shoulders and what you have written demonstrates that you are thinking and approaching this in the right way. WIsh you luck with it. However, don’t get lost in the sauce and dream too big extending the scope before you nail the original intent of the grant. Whilst FTP and WebDav both seem useful, if you do NFS right it could be a really big deal.

Thanks a lot for your swift feedback!

To your points:

  1. The reason this particular NFS server library was chosen is that it’s pretty much the only available option! There are several client libraries as well as stand-alone NFS servers, but not actual server libraries that could be used for something like this project.
    The standalone servers simply export a local file system. This is what most appliances that support NFS do - they run a separate process (or possibly use a kernel-space server) and export local files.

    However, for this project we need to export a virtual file system. We really need a server library for this and implement the VFS ourselves.

    On the plus side:

    • The library is written in Rust, which I am currently most productive in and which I wanted to use for this project anyway.
    • It’s fairly modern and uses async, which fits perfectly into the rest of the project, which also uses async everywhere where it makes sense.
    • The author wrote it as an integral part of a commercial product. There has been a discussion about it on HN. It links to a blog post about why the author chose NFSv3. This gives me confidence that the library will be around for a while.
    • Another poster on the HN discussion links to a Microsoft page mentioning that Windows still only supports NFSv2 & NFSv3. Link
    • In the discussion, the author does mention that he considers adding NFSv4 in the future. This is also mentioned on the GitHub. So hopefully, this will come one day - however, nothing has been done yet it seems.

    tldr: I did do my homework when picking this library. nfsserve is definitely not perfect, but it is currently the best option available.

  2. Yup, I couldn’t come up with a safe solution that doesn’t require some kind of persistent state.

  3. Thanks! The reason I am talking about supporting additional protocols at this point is to get feedback if this is something people actually want. My focus hasn’t changed of course. I am still working on NFS first and foremost. Things have been progressing well (hope I am not jinxing it) and it currently looks like I might have some extra time. Additional protocols were the first thing that came to my mind for what to do with this potential extra time. By asking now I will have an answer by the time I get around to it.

Blockquote

  • The library is written in Rust, which I am currently most productive in and which I wanted to use for this project anyway.
  • It’s fairly modern and uses async, which fits perfectly into the rest of the project, which also uses async everywhere where it makes sense.
  • The author wrote it as an integral part of a commercial product. There has been a discussion about it on HN. It links to a blog post about why the author chose NFSv3. This gives me confidence that the library will be around for a while.
  • Another poster on the HN discussion links to a Microsoft page mentioning that Windows still only supports NFSv2 & NFSv3. Link
  • In the discussion, the author does mention that he considers adding NFSv4 in the future. This is also mentioned on the GitHub. So hopefully, this will come one day - however, nothing has been done yet it seems.

With the last point you made and after checking the repo, lets suppose the library never sees an update again from this point forward. In your opinion, how complicated is the code base for someone else to pick up?

To me the code base looks fairly clean and straightforward. It’s well structured and I do not see any immediate red flags or anything like that. The biggest hurdle is probably the lack of unit tests. That being said, basic maintenance / bugfixing should not be too difficult for someone familiar with both, Rust and NFS.

If you’re wondering how difficult it would be to add NFSv4 support to the current codebase, then I cannot give you a definite answer - I simply don’t know enough about the differences between NFSv3 and NFSv4 on the protocol level to make this judgement.

Cool, all sounds reasonable to me. Thanks for explaining it all.

1 Like

Hello @rrauch

Thank you for your progress report as well as the detailed breakdown!

Regards,
Kino on behalf of the Sia Foundation and Grants Committee

Milestone 1 Released

Milestone 1 of sia_nfs, the first public release, is now available!

This version is considered a preview, although it provides the expected functionality of an NFS gateway:

  • Multi-Bucket Support: One or more buckets can be made available for a unified file system.
  • Mounting and Unmounting: Mounting and unmounting works as expected.
  • Directory Listing: Files and directories can be listed and navigated.
  • Directory Creation: New empty directories can be created.
  • Directory Removal: Empty directories can be deleted.
  • File Deletion: Files can be deleted.
  • Renaming and Moving: Files and directories can be renamed and moved (within the same bucket).
  • File Reading: Files can be read as expected, including seeking.
  • File Writing: New files can be created and written to, including copying an existing file.

As mentioned in the previous update, partial writing (e.g., editing an existing file) is NOT supported. The reasons for this are explained in detail. However, if there is strong community support for this feature, it can be added. It would be disabled by default and would need to be enabled by the user, who should understand the associated costs.

Progress Since the Previous Update

In my previous update, I discussed the progress of sia_nfs, including certain decisions, tradeoffs, and challenges encountered.

One significant aspect was how read and write operations are stateless, which presents certain challenges. I explained my approach and the solutions implemented, though they are not perfect.

Problem: Out-of-Order IO

As the project progressed, I learned more about the real-world access patterns encountered by sia_nfs. It turns out that not only read-ahead operations are problematic, but in practice, nearly all read and even write operations can arrive out-of-order.

The relatively simple approach (DownloadManager, UploadManager) previously used was not sufficient to handle this satisfactorily. Through a lengthy iterative process, I arrived at a more suitable solution:

Solution: IO Scheduler

I implemented a complex Scheduler capable of queuing and managing all ongoing IO operations while supporting multiple strategies—separate ones for reads and writes.

Efficient reading turned out to be a significantly more difficult problem to solve than writing. Implementing a good strategy for reading requires a fairly complete picture of what is, was, and probably will be happening.

This was more complex than anticipated and required an iterative approach, taking most of my time since the previous update. However, the Scheduler is now implemented and performs well. The write strategy was straightforward to implement and should require little work in the future. The read strategy works well for many usage patterns encountered so far, but some patterns are not handled ideally. More fine-tuning will be needed in the future as more usage patterns become apparent. Ultimately, this will always be a trade-off, and no single perfect solution will exist for all usage patterns.

Test Drive

If you want to try it out, you can! I have included a Dockerfile and easy-to-follow instructions on how to get started. All you need is Docker and a reachable renterd instance. Use it at your own risk, and avoid using it with important data for now.

Onward

Overall project progress has been good, and the current trajectory suggests that the next release will also be on time. The main functionality is here, and the focus in the coming weeks will be on stabilizing, testing, fine-tuning, and even more testing. Metadata caching is still missing, but I do not anticipate it to take much work to implement. I expect the next release to be generally usable.

Feedback

I invite everyone interested in sia_nfs to give it a try and post feedback in this thread. I am particularly interested in hearing about experiences with non-Linux NFS clients.

August 2024 Progress Report

What progress was made on your grant this month?

  • Milestone 1 was released. For detailed information, refer to the release announcement above.
  • The metadata cache has been added.
  • Intensive testing against the built-in NFS clients on Linux, Windows, and macOS was conducted.
  • The io_scheduler was completely rewritten.
  • Work was started on adding a deduplicating content cache (Cachalot).

Links to repos worked on this month:

What will you be working on this month?

  • Implementation of the content cache will be completed.
  • More cross-platform testing will be conducted.
  • Better error handling and some other polishing.
  • The release of Milestone 2!

Hello @rrauch

Thank you for your perfect Progress Report!

Regards,
Kino on behalf of the Sia Foundation and Grants Committee