fix(tvix/store): Fix FUSE support on MacOS

This partially fixes b/312 and gets FUSE to work again on MacOS.

It is mostly small type changes and an update to fuse-backend-rs because
upstream currently doesn't work with MacFuse. It also sets the default
FUSE thread count on MacOS to 1 because otherwise the mount command will
hang when shutting down as only one thread gets ENODEV and all the others
just keep blocking.

Change-Id: Ifb3c4268caf296c487049c1dc4618acb32497f44
Reviewed-on: https://cl.tvl.fyi/c/depot/+/9490
Tested-by: BuildkiteCI
Reviewed-by: Connor Brewster <cbrewster@hey.com>
Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
Brian Olsen 2023-09-29 18:50:50 +02:00 committed by Brian Olsen
parent 5c2cad0ac4
commit cfb810d81a
9 changed files with 42 additions and 22 deletions

View file

@ -34,9 +34,9 @@ tokio-listener = { version = "0.2.1" }
[dependencies.fuse-backend-rs]
optional = true
# TODO: Switch back to upstream version once https://github.com/cloud-hypervisor/fuse-backend-rs/pull/153 lands.
git = "https://github.com/cloud-hypervisor/fuse-backend-rs"
rev = "402e7c531bc75bc44ac366dc59477de8b5d4ca08"
# TODO: Switch back to upstream version once https://github.com/cloud-hypervisor/fuse-backend-rs/pull/157 lands.
git = "https://github.com/griff/fuse-backend-rs"
branch = "macfuse-fix"
[dependencies.vhost]
optional = true

View file

@ -24,8 +24,8 @@ in
(depot.tvix.crates.workspaceMembers.tvix-store.build.override {
runTests = true;
# both fuse and virtiofs features currently fail to build on Darwin.
features = if pkgs.stdenv.isDarwin then [ "tonic-reflection" ] else [ "default" ];
# virtiofs feature currently fails to build on Darwin.
features = if pkgs.stdenv.isDarwin then [ "fuse" "tonic-reflection" ] else [ "default" ];
}).overrideAttrs (_: {
meta.ci.extraSteps = {
import-docs = (mkImportCheck "tvix/store/docs" ./docs);

View file

@ -139,12 +139,19 @@ enum Commands {
},
}
#[cfg(feature = "fuse")]
#[cfg(all(feature = "fuse", not(target_os = "macos")))]
fn default_threads() -> usize {
std::thread::available_parallelism()
.map(|threads| threads.into())
.unwrap_or(4)
}
// On MacFUSE only a single channel will receive ENODEV when the file system is
// unmounted and so all the other channels will block forever.
// See https://github.com/osxfuse/osxfuse/issues/974
#[cfg(all(feature = "fuse", target_os = "macos"))]
fn default_threads() -> usize {
1
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

View file

@ -7,7 +7,7 @@ pub const ROOT_FILE_ATTR: Attr = Attr {
size: 0,
blksize: 1024,
blocks: 0,
mode: libc::S_IFDIR | 0o555,
mode: libc::S_IFDIR as u32 | 0o555,
atime: 0,
mtime: 0,
ctime: 0,
@ -19,6 +19,12 @@ pub const ROOT_FILE_ATTR: Attr = Attr {
gid: 0,
rdev: 0,
flags: 0,
#[cfg(target_os = "macos")]
crtime: 0,
#[cfg(target_os = "macos")]
crtimensec: 0,
#[cfg(target_os = "macos")]
padding: 0,
};
/// for given &Node and inode, construct an [Attr]
@ -36,10 +42,10 @@ pub fn gen_file_attr(inode_data: &InodeData, inode: u64) -> Attr {
}
},
mode: match inode_data {
InodeData::Regular(_, _, false) => libc::S_IFREG | 0o444, // no-executable files
InodeData::Regular(_, _, true) => libc::S_IFREG | 0o555, // executable files
InodeData::Symlink(_) => libc::S_IFLNK | 0o444,
InodeData::Directory(_) => libc::S_IFDIR | 0o555,
InodeData::Regular(_, _, false) => libc::S_IFREG as u32 | 0o444, // no-executable files
InodeData::Regular(_, _, true) => libc::S_IFREG as u32 | 0o555, // executable files
InodeData::Symlink(_) => libc::S_IFLNK as u32 | 0o444,
InodeData::Directory(_) => libc::S_IFDIR as u32 | 0o555,
},
..Default::default()
}

View file

@ -11,6 +11,11 @@ where
channel: fuse_backend_rs::transport::FuseChannel,
}
#[cfg(target_os = "macos")]
const BADFD: libc::c_int = libc::EBADF;
#[cfg(target_os = "linux")]
const BADFD: libc::c_int = libc::EBADFD;
impl<FS> FuseServer<FS>
where
FS: FileSystem + Sync + Send,
@ -29,7 +34,7 @@ where
match e {
// This indicates the session has been shut down.
fuse_backend_rs::Error::EncodeMessage(e)
if e.raw_os_error() == Some(libc::EBADFD) =>
if e.raw_os_error() == Some(BADFD) =>
{
break;
}
@ -63,6 +68,7 @@ impl FuseDaemon {
let mut session = FuseSession::new(mountpoint.as_ref(), "tvix-store", "", true)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
#[cfg(target_os = "linux")]
session.set_allow_other(false);
session
.mount()

View file

@ -13,6 +13,7 @@ mod tests;
use crate::pathinfoservice::PathInfoService;
use fuse_backend_rs::abi::fuse_abi::stat64;
use fuse_backend_rs::api::filesystem::{Context, FileSystem, FsOptions, ROOT_ID};
use futures::StreamExt;
use nix_compat::store_path::StorePath;
@ -253,7 +254,7 @@ impl FileSystem for TvixStoreFs {
_ctx: &Context,
inode: Self::Inode,
_handle: Option<Self::Handle>,
) -> io::Result<(libc::stat64, Duration)> {
) -> io::Result<(stat64, Duration)> {
if inode == ROOT_ID {
return Ok((ROOT_FILE_ATTR.into(), Duration::MAX));
}
@ -441,7 +442,7 @@ impl FileSystem for TvixStoreFs {
let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry {
ino,
offset: offset + i as u64 + 1,
type_: ty,
type_: ty as u32,
name: store_path.to_string().as_bytes(),
})?;
// If the buffer is full, add_entry will return `Ok(0)`.
@ -490,9 +491,9 @@ impl FileSystem for TvixStoreFs {
ino: *ino,
offset: offset + i as u64 + 1,
type_: match child_node {
Node::Directory(_) => libc::S_IFDIR,
Node::File(_) => libc::S_IFREG,
Node::Symlink(_) => libc::S_IFLNK,
Node::Directory(_) => libc::S_IFDIR as u32,
Node::File(_) => libc::S_IFREG as u32,
Node::Symlink(_) => libc::S_IFLNK as u32,
},
name: child_node.get_name(),
})?;