diff options
-rw-r--r-- | tvix/Cargo.lock | 171 | ||||
-rw-r--r-- | tvix/Cargo.nix | 499 | ||||
-rw-r--r-- | tvix/crate-hashes.json | 1 | ||||
-rw-r--r-- | tvix/default.nix | 6 | ||||
-rw-r--r-- | tvix/store/Cargo.toml | 10 | ||||
-rw-r--r-- | tvix/store/src/bin/tvix-store.rs | 32 | ||||
-rw-r--r-- | tvix/store/src/fuse/file_attr.rs | 55 | ||||
-rw-r--r-- | tvix/store/src/fuse/inodes.rs | 10 | ||||
-rw-r--r-- | tvix/store/src/fuse/mod.rs | 640 | ||||
-rw-r--r-- | tvix/store/src/fuse/tests.rs | 92 | ||||
-rw-r--r-- | tvix/store/src/lib.rs | 2 |
11 files changed, 934 insertions, 584 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index f0a73f3a3f51..02726751705c 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -88,6 +88,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] name = "arrayref" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -332,6 +338,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] +name = "caps" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" +dependencies = [ + "libc", + "thiserror", +] + +[[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -477,12 +493,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] +name = "const-zero" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c6565524986fe3225da0beb9b4aa55ebc73cd57ff8cb4ccf016ca4c8d006af" + +[[package]] name = "constant_time_eq" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" [[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] name = "count-write" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -578,7 +606,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.8.0", "scopeguard", ] @@ -791,19 +819,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] -name = "fuser" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5910691a0ececcc6eba8bb14029025c2d123e96a53db1533f6a4602861a5aaf7" +name = "fuse-backend-rs" +version = "0.10.5" +source = "git+https://github.com/cbrewster/fuse-backend-rs.git?branch=optional-allow_other#b553ce526a7267578ae5af4f4cbba717932518ac" dependencies = [ + "arc-swap", + "bitflags", + "caps", + "core-foundation-sys", + "lazy_static", "libc", "log", - "memchr", - "page_size", - "pkg-config", - "smallvec", - "users", - "zerocopy", + "mio", + "nix 0.24.3", + "vm-memory", + "vmm-sys-util", ] [[package]] @@ -1328,6 +1358,15 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" @@ -1379,6 +1418,18 @@ dependencies = [ [[package]] name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" @@ -1501,24 +1552,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "page_size" -version = "0.4.2" +name = "parking_lot" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ - "libc", - "winapi", + "instant", + "lock_api", + "parking_lot_core 0.8.6", ] [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.8", ] [[package]] @@ -1536,6 +1587,19 @@ dependencies = [ ] [[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.0", +] + +[[package]] name = "path-clean" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1590,12 +1654,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] name = "plotters" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1999,7 +2057,7 @@ checksum = "64449cfef9483a475ed56ae30e2da5ee96448789fb2aa240a04beb6a055078bf" dependencies = [ "countme", "hashbrown", - "memoffset", + "memoffset 0.8.0", "rustc-hash", "text-size", ] @@ -2062,7 +2120,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.25.1", "radix_trie", "scopeguard", "unicode-segmentation", @@ -2207,7 +2265,7 @@ dependencies = [ "fxhash", "libc", "log", - "parking_lot", + "parking_lot 0.11.2", "zstd", ] @@ -2869,13 +2927,15 @@ dependencies = [ "blake3", "bytes", "clap 4.2.7", + "const-zero", "count-write", "data-encoding", - "fuser", + "fuse-backend-rs", "futures", "lazy_static", "libc", "nix-compat", + "parking_lot 0.12.1", "pin-project-lite", "prost", "prost-build", @@ -2964,16 +3024,6 @@ dependencies = [ ] [[package]] -name = "users" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc", - "log", -] - -[[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2992,6 +3042,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] +name = "vm-memory" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688a70366615b45575a424d9c665561c1b5ab2224d494f706b6a6812911a827c" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "vmm-sys-util" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd64fe09d8e880e600c324e7d664760a17f56e9672b7495a86381b49e4f72f46" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3282,27 +3352,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] -name = "zerocopy" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3" -dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", -] - -[[package]] name = "zstd" version = "0.9.2+zstd.1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix index e967b345e26f..cd9570c48ac5 100644 --- a/tvix/Cargo.nix +++ b/tvix/Cargo.nix @@ -336,6 +336,18 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "arc-swap" = rec { + crateName = "arc-swap"; + version = "1.6.0"; + edition = "2018"; + sha256 = "19n9j146bpxs9phyh48gmlh9jjsdijr9p9br04qms0g9ypfsvp5x"; + authors = [ + "Michal 'vorner' Vaner <vorner@vorner.cz>" + ]; + features = { + "serde" = [ "dep:serde" ]; + }; + }; "arrayref" = rec { crateName = "arrayref"; version = "0.3.7"; @@ -1020,6 +1032,29 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "caps" = rec { + crateName = "caps"; + version = "0.5.5"; + edition = "2018"; + sha256 = "02vk0w48rncgvfmj2mz2kpzvdgc14z225451w7lvvkwvaansl2qr"; + authors = [ + "Luca Bruno <lucab@lucabruno.net>" + ]; + dependencies = [ + { + name = "libc"; + packageId = "libc"; + } + { + name = "thiserror"; + packageId = "thiserror"; + } + ]; + features = { + "serde" = [ "dep:serde" ]; + "serde_support" = [ "serde" ]; + }; + }; "cast" = rec { crateName = "cast"; version = "0.3.0"; @@ -1399,6 +1434,16 @@ rec { sha256 = "1ix7w85kwvyybwi2jdkl3yva2r2bvdcc3ka2grjfzfgrapqimgxc"; }; + "const-zero" = rec { + crateName = "const-zero"; + version = "0.1.1"; + edition = "2018"; + sha256 = "1bq6s34a8v01rx6cpy3zslycgssmmasbkgm0blif6vwq4iamdim3"; + authors = [ + "Max Blachman <blachmanmax@gmail.com>" + ]; + + }; "constant_time_eq" = rec { crateName = "constant_time_eq"; version = "0.2.5"; @@ -1409,6 +1454,16 @@ rec { ]; }; + "core-foundation-sys" = rec { + crateName = "core-foundation-sys"; + version = "0.8.4"; + edition = "2015"; + sha256 = "1yhf471qj6snnm2mcswai47vsbc9w30y4abmdp4crb4av87sb5p4"; + authors = [ + "The Servo Project Developers" + ]; + features = { }; + }; "count-write" = rec { crateName = "count-write"; version = "0.1.0"; @@ -1692,7 +1747,7 @@ rec { } { name = "memoffset"; - packageId = "memoffset"; + packageId = "memoffset 0.8.0"; } { name = "scopeguard"; @@ -2211,16 +2266,47 @@ rec { ]; }; - "fuser" = rec { - crateName = "fuser"; - version = "0.12.0"; + "fuse-backend-rs" = rec { + crateName = "fuse-backend-rs"; + version = "0.10.5"; edition = "2018"; - sha256 = "1xxalmhjhq54yqribnskdblj7lf24n80455vm3mwdk6f1qd6j42r"; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/cbrewster/fuse-backend-rs.git"; + rev = "b553ce526a7267578ae5af4f4cbba717932518ac"; + sha256 = "0165g94bcwyg03yi4kbr6yw75p42kiz2vd1d7s8f3fj4injy49vx"; + }; authors = [ - "Christopher Berner <christopherberner@gmail.com>" + "Liu Bo <bo.liu@linux.alibaba.com>" + "Liu Jiang <gerry@linux.alibaba.com>" + "Peng Tao <bergwolf@hyper.sh>" ]; dependencies = [ { + name = "arc-swap"; + packageId = "arc-swap"; + } + { + name = "bitflags"; + packageId = "bitflags"; + } + { + name = "caps"; + packageId = "caps"; + optional = true; + target = { target, features }: ("linux" == target."os"); + } + { + name = "core-foundation-sys"; + packageId = "core-foundation-sys"; + optional = true; + target = { target, features }: ("macos" == target."os"); + } + { + name = "lazy_static"; + packageId = "lazy_static"; + } + { name = "libc"; packageId = "libc"; } @@ -2229,63 +2315,53 @@ rec { packageId = "log"; } { - name = "memchr"; - packageId = "memchr"; + name = "mio"; + packageId = "mio"; + features = [ "os-poll" "os-ext" ]; } { - name = "page_size"; - packageId = "page_size"; + name = "nix"; + packageId = "nix 0.24.3"; } { - name = "smallvec"; - packageId = "smallvec"; + name = "vm-memory"; + packageId = "vm-memory"; + features = [ "backend-mmap" ]; } { - name = "users"; - packageId = "users"; + name = "vmm-sys-util"; + packageId = "vmm-sys-util"; + optional = true; } + ]; + devDependencies = [ { - name = "zerocopy"; - packageId = "zerocopy"; + name = "vm-memory"; + packageId = "vm-memory"; + features = [ "backend-mmap" "backend-bitmap" ]; } - ]; - buildDependencies = [ { - name = "pkg-config"; - packageId = "pkg-config"; - optional = true; - } - ]; - features = { - "abi-7-10" = [ "abi-7-9" ]; - "abi-7-11" = [ "abi-7-10" ]; - "abi-7-12" = [ "abi-7-11" ]; - "abi-7-13" = [ "abi-7-12" ]; - "abi-7-14" = [ "abi-7-13" ]; - "abi-7-15" = [ "abi-7-14" ]; - "abi-7-16" = [ "abi-7-15" ]; - "abi-7-17" = [ "abi-7-16" ]; - "abi-7-18" = [ "abi-7-17" ]; - "abi-7-19" = [ "abi-7-18" ]; - "abi-7-20" = [ "abi-7-19" ]; - "abi-7-21" = [ "abi-7-20" ]; - "abi-7-22" = [ "abi-7-21" ]; - "abi-7-23" = [ "abi-7-22" ]; - "abi-7-24" = [ "abi-7-23" ]; - "abi-7-25" = [ "abi-7-24" ]; - "abi-7-26" = [ "abi-7-25" ]; - "abi-7-27" = [ "abi-7-26" ]; - "abi-7-28" = [ "abi-7-27" ]; - "abi-7-29" = [ "abi-7-28" ]; - "abi-7-30" = [ "abi-7-29" ]; - "abi-7-31" = [ "abi-7-30" ]; - "default" = [ "libfuse" ]; - "libfuse" = [ "pkg-config" ]; - "pkg-config" = [ "dep:pkg-config" ]; - "serde" = [ "dep:serde" ]; - "serializable" = [ "serde" ]; + name = "vmm-sys-util"; + packageId = "vmm-sys-util"; + } + ]; + features = { + "async-io" = [ "async-trait" "tokio-uring" "tokio/fs" "tokio/net" "tokio/sync" "tokio/rt" "tokio/macros" "io-uring" ]; + "async-trait" = [ "dep:async-trait" ]; + "caps" = [ "dep:caps" ]; + "core-foundation-sys" = [ "dep:core-foundation-sys" ]; + "default" = [ "fusedev" ]; + "fusedev" = [ "vmm-sys-util" "caps" "core-foundation-sys" ]; + "io-uring" = [ "dep:io-uring" ]; + "tokio" = [ "dep:tokio" ]; + "tokio-uring" = [ "dep:tokio-uring" ]; + "vhost" = [ "dep:vhost" ]; + "vhost-user-fs" = [ "virtiofs" "vhost" "caps" ]; + "virtio-queue" = [ "dep:virtio-queue" ]; + "virtiofs" = [ "virtio-queue" "caps" "vmm-sys-util" ]; + "vmm-sys-util" = [ "dep:vmm-sys-util" ]; }; - resolvedDefaultFeatures = [ "default" "libfuse" "pkg-config" ]; + resolvedDefaultFeatures = [ "caps" "core-foundation-sys" "default" "fusedev" "vmm-sys-util" ]; }; "futures" = rec { crateName = "futures"; @@ -3817,7 +3893,24 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; - "memoffset" = rec { + "memoffset 0.6.5" = rec { + crateName = "memoffset"; + version = "0.6.5"; + edition = "2015"; + sha256 = "1kkrzll58a3ayn5zdyy9i1f1v3mx0xgl29x0chq614zazba638ss"; + authors = [ + "Gilad Naaman <gilad.naaman@gmail.com>" + ]; + buildDependencies = [ + { + name = "autocfg"; + packageId = "autocfg"; + } + ]; + features = { }; + resolvedDefaultFeatures = [ "default" ]; + }; + "memoffset 0.8.0" = rec { crateName = "memoffset"; version = "0.8.0"; edition = "2015"; @@ -3910,7 +4003,7 @@ rec { features = { "os-ext" = [ "os-poll" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_Security" ]; }; - resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ]; + resolvedDefaultFeatures = [ "default" "net" "os-ext" "os-poll" ]; }; "multimap" = rec { crateName = "multimap"; @@ -3942,7 +4035,53 @@ rec { ]; }; - "nix" = rec { + "nix 0.24.3" = rec { + crateName = "nix"; + version = "0.24.3"; + edition = "2018"; + sha256 = "0sc0yzdl51b49bqd9l9cmimp1sw1hxb8iyv4d35ww6d7m5rfjlps"; + authors = [ + "The nix-rust Project Developers" + ]; + dependencies = [ + { + name = "bitflags"; + packageId = "bitflags"; + } + { + name = "cfg-if"; + packageId = "cfg-if"; + } + { + name = "libc"; + packageId = "libc"; + features = [ "extra_traits" ]; + } + { + name = "memoffset"; + packageId = "memoffset 0.6.5"; + optional = true; + target = { target, features }: (!("redox" == target."os")); + } + ]; + features = { + "default" = [ "acct" "aio" "dir" "env" "event" "feature" "fs" "hostname" "inotify" "ioctl" "kmod" "mman" "mount" "mqueue" "net" "personality" "poll" "process" "pthread" "ptrace" "quota" "reboot" "resource" "sched" "signal" "socket" "term" "time" "ucontext" "uio" "user" "zerocopy" ]; + "dir" = [ "fs" ]; + "memoffset" = [ "dep:memoffset" ]; + "mount" = [ "uio" ]; + "mqueue" = [ "fs" ]; + "net" = [ "socket" ]; + "ptrace" = [ "process" ]; + "sched" = [ "process" ]; + "signal" = [ "process" ]; + "socket" = [ "memoffset" ]; + "ucontext" = [ "signal" ]; + "user" = [ "feature" ]; + "zerocopy" = [ "fs" "uio" ]; + }; + resolvedDefaultFeatures = [ "acct" "aio" "default" "dir" "env" "event" "feature" "fs" "hostname" "inotify" "ioctl" "kmod" "memoffset" "mman" "mount" "mqueue" "net" "personality" "poll" "process" "pthread" "ptrace" "quota" "reboot" "resource" "sched" "signal" "socket" "term" "time" "ucontext" "uio" "user" "zerocopy" ]; + }; + "nix 0.25.1" = rec { crateName = "nix"; version = "0.25.1"; edition = "2018"; @@ -4299,52 +4438,55 @@ rec { ]; }; - "page_size" = rec { - crateName = "page_size"; - version = "0.4.2"; - edition = "2015"; - sha256 = "1kgdv7f626jy4i2pq8czp4ppady4g4kqfa5ik4dah7mzzd4fbggf"; + "parking_lot 0.11.2" = rec { + crateName = "parking_lot"; + version = "0.11.2"; + edition = "2018"; + sha256 = "16gzf41bxmm10x82bla8d6wfppy9ym3fxsmdjyvn61m66s0bf5vx"; authors = [ - "Philip Woods <elzairthesorcerer@gmail.com>" + "Amanieu d'Antras <amanieu@gmail.com>" ]; dependencies = [ { - name = "libc"; - packageId = "libc"; - target = { target, features }: (target."unix" or false); + name = "instant"; + packageId = "instant"; } { - name = "winapi"; - packageId = "winapi"; - target = { target, features }: (target."windows" or false); - features = [ "sysinfoapi" ]; + name = "lock_api"; + packageId = "lock_api"; + } + { + name = "parking_lot_core"; + packageId = "parking_lot_core 0.8.6"; } ]; features = { - "no_std" = [ "spin" ]; - "spin" = [ "dep:spin" ]; + "arc_lock" = [ "lock_api/arc_lock" ]; + "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ]; + "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ]; + "owning_ref" = [ "lock_api/owning_ref" ]; + "serde" = [ "lock_api/serde" ]; + "stdweb" = [ "instant/stdweb" ]; + "wasm-bindgen" = [ "instant/wasm-bindgen" ]; }; + resolvedDefaultFeatures = [ "default" ]; }; - "parking_lot" = rec { + "parking_lot 0.12.1" = rec { crateName = "parking_lot"; - version = "0.11.2"; + version = "0.12.1"; edition = "2018"; - sha256 = "16gzf41bxmm10x82bla8d6wfppy9ym3fxsmdjyvn61m66s0bf5vx"; + sha256 = "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip"; authors = [ "Amanieu d'Antras <amanieu@gmail.com>" ]; dependencies = [ { - name = "instant"; - packageId = "instant"; - } - { name = "lock_api"; packageId = "lock_api"; } { name = "parking_lot_core"; - packageId = "parking_lot_core"; + packageId = "parking_lot_core 0.9.8"; } ]; features = { @@ -4353,12 +4495,10 @@ rec { "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ]; "owning_ref" = [ "lock_api/owning_ref" ]; "serde" = [ "lock_api/serde" ]; - "stdweb" = [ "instant/stdweb" ]; - "wasm-bindgen" = [ "instant/wasm-bindgen" ]; }; resolvedDefaultFeatures = [ "default" ]; }; - "parking_lot_core" = rec { + "parking_lot_core 0.8.6" = rec { crateName = "parking_lot_core"; version = "0.8.6"; edition = "2018"; @@ -4403,6 +4543,46 @@ rec { "thread-id" = [ "dep:thread-id" ]; }; }; + "parking_lot_core 0.9.8" = rec { + crateName = "parking_lot_core"; + version = "0.9.8"; + edition = "2018"; + sha256 = "0ixlak319bpzldq20yvyfqk0y1vi736zxbw101jvzjp7by30rw4k"; + authors = [ + "Amanieu d'Antras <amanieu@gmail.com>" + ]; + dependencies = [ + { + name = "cfg-if"; + packageId = "cfg-if"; + } + { + name = "libc"; + packageId = "libc"; + target = { target, features }: (target."unix" or false); + } + { + name = "redox_syscall"; + packageId = "redox_syscall 0.3.5"; + target = { target, features }: ("redox" == target."os"); + } + { + name = "smallvec"; + packageId = "smallvec"; + } + { + name = "windows-targets"; + packageId = "windows-targets 0.48.0"; + target = { target, features }: (target."windows" or false); + } + ]; + features = { + "backtrace" = [ "dep:backtrace" ]; + "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ]; + "petgraph" = [ "dep:petgraph" ]; + "thread-id" = [ "dep:thread-id" ]; + }; + }; "path-clean" = rec { crateName = "path-clean"; version = "0.1.0"; @@ -4511,16 +4691,6 @@ rec { ]; }; - "pkg-config" = rec { - crateName = "pkg-config"; - version = "0.3.27"; - edition = "2015"; - sha256 = "0r39ryh1magcq4cz5g9x88jllsnxnhcqr753islvyk4jp9h2h1r6"; - authors = [ - "Alex Crichton <alex@alexcrichton.com>" - ]; - - }; "plotters" = rec { crateName = "plotters"; version = "0.3.4"; @@ -5682,7 +5852,7 @@ rec { } { name = "memoffset"; - packageId = "memoffset"; + packageId = "memoffset 0.8.0"; } { name = "rustc-hash"; @@ -5937,7 +6107,7 @@ rec { } { name = "nix"; - packageId = "nix"; + packageId = "nix 0.25.1"; usesDefaultFeatures = false; target = { target, features }: (target."unix" or false); features = [ "fs" "ioctl" "poll" "signal" "term" ]; @@ -6363,7 +6533,7 @@ rec { } { name = "parking_lot"; - packageId = "parking_lot"; + packageId = "parking_lot 0.11.2"; } { name = "zstd"; @@ -8548,6 +8718,10 @@ rec { features = [ "derive" "env" ]; } { + name = "const-zero"; + packageId = "const-zero"; + } + { name = "count-write"; packageId = "count-write"; } @@ -8556,8 +8730,8 @@ rec { packageId = "data-encoding"; } { - name = "fuser"; - packageId = "fuser"; + name = "fuse-backend-rs"; + packageId = "fuse-backend-rs"; optional = true; } { @@ -8578,6 +8752,10 @@ rec { packageId = "nix-compat"; } { + name = "parking_lot"; + packageId = "parking_lot 0.12.1"; + } + { name = "pin-project-lite"; packageId = "pin-project-lite"; } @@ -8681,7 +8859,7 @@ rec { ]; features = { "default" = [ "fuse" "reflection" ]; - "fuse" = [ "dep:fuser" "dep:libc" ]; + "fuse" = [ "dep:libc" "dep:fuse-backend-rs" ]; "reflection" = [ "tonic-reflection" ]; "tonic-reflection" = [ "dep:tonic-reflection" ]; }; @@ -8826,33 +9004,6 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; - "users" = rec { - crateName = "users"; - version = "0.11.0"; - edition = "2015"; - sha256 = "0cmhafhhka2yya66yrprlv33kg7rm1xh1pyalbjp6yr6dxnhzk14"; - authors = [ - "Benjamin Sago <ogham@bsago.me>" - ]; - dependencies = [ - { - name = "libc"; - packageId = "libc"; - } - { - name = "log"; - packageId = "log"; - optional = true; - usesDefaultFeatures = false; - } - ]; - features = { - "default" = [ "cache" "mock" "logging" ]; - "log" = [ "dep:log" ]; - "logging" = [ "log" ]; - }; - resolvedDefaultFeatures = [ "cache" "default" "log" "logging" "mock" ]; - }; "utf8parse" = rec { crateName = "utf8parse"; version = "0.2.1"; @@ -8888,6 +9039,57 @@ rec { ]; }; + "vm-memory" = rec { + crateName = "vm-memory"; + version = "0.10.0"; + edition = "2021"; + sha256 = "0z423a8i4s3addq4yjad4ar5l6qwarjwdn94lismbd0mcqv712k8"; + authors = [ + "Liu Jiang <gerry@linux.alibaba.com>" + ]; + dependencies = [ + { + name = "libc"; + packageId = "libc"; + } + { + name = "winapi"; + packageId = "winapi"; + target = { target, features }: (target."windows" or false); + features = [ "errhandlingapi" "sysinfoapi" ]; + } + ]; + features = { + "arc-swap" = [ "dep:arc-swap" ]; + "backend-atomic" = [ "arc-swap" ]; + }; + resolvedDefaultFeatures = [ "backend-mmap" "default" ]; + }; + "vmm-sys-util" = rec { + crateName = "vmm-sys-util"; + version = "0.11.1"; + edition = "2021"; + sha256 = "0iigyzj4j6rqhrd4kdvjjrpga5qafrjddrr4qc0fd078v04zwr6x"; + authors = [ + "Intel Virtualization Team <vmm-maintainers@intel.com>" + ]; + dependencies = [ + { + name = "bitflags"; + packageId = "bitflags"; + target = { target, features }: (("linux" == target."os") || ("android" == target."os")); + } + { + name = "libc"; + packageId = "libc"; + } + ]; + features = { + "serde" = [ "dep:serde" ]; + "serde_derive" = [ "dep:serde_derive" ]; + "with-serde" = [ "serde" "serde_derive" ]; + }; + }; "wait-timeout" = rec { crateName = "wait-timeout"; version = "0.2.0"; @@ -10532,55 +10734,6 @@ rec { ]; }; - "zerocopy" = rec { - crateName = "zerocopy"; - version = "0.6.1"; - edition = "2018"; - sha256 = "0dpj4nd9v56wy93ahjkp95znjzj91waqvidqch8gxwdwq661hbrk"; - authors = [ - "Joshua Liebow-Feeser <joshlf@google.com>" - ]; - dependencies = [ - { - name = "byteorder"; - packageId = "byteorder"; - usesDefaultFeatures = false; - } - { - name = "zerocopy-derive"; - packageId = "zerocopy-derive"; - } - ]; - features = { - "simd-nightly" = [ "simd" ]; - }; - }; - "zerocopy-derive" = rec { - crateName = "zerocopy-derive"; - version = "0.3.2"; - edition = "2018"; - sha256 = "18qr7dqlj89v1xl1g58l2xd6jidv0sbccscgl131gpppba0yc1b5"; - procMacro = true; - authors = [ - "Joshua Liebow-Feeser <joshlf@google.com>" - ]; - dependencies = [ - { - name = "proc-macro2"; - packageId = "proc-macro2 1.0.56"; - } - { - name = "quote"; - packageId = "quote 1.0.26"; - } - { - name = "syn"; - packageId = "syn 1.0.109"; - features = [ "visit" ]; - } - ]; - - }; "zstd" = rec { crateName = "zstd"; version = "0.9.2+zstd.1.5.1"; diff --git a/tvix/crate-hashes.json b/tvix/crate-hashes.json index 3e5b692e0b4b..360ab6937fef 100644 --- a/tvix/crate-hashes.json +++ b/tvix/crate-hashes.json @@ -1,4 +1,5 @@ { + "fuse-backend-rs 0.10.5 (git+https://github.com/cbrewster/fuse-backend-rs.git?branch=optional-allow_other#b553ce526a7267578ae5af4f4cbba717932518ac)": "0165g94bcwyg03yi4kbr6yw75p42kiz2vd1d7s8f3fj4injy49vx", "test-generator 0.3.0 (git+https://github.com/JamesGuthrie/test-generator.git?rev=82e799979980962aec1aa324ec6e0e4cad781f41#82e799979980962aec1aa324ec6e0e4cad781f41)": "08brp3qqa55hijc7xby3lam2cc84hvx1zzfqv6lj7smlczh8k32y", "tonic-mock 0.1.0 (git+https://github.com/brainrake/tonic-mock?branch=bump-dependencies#ec1a15510875de99d709d684190db5d9beab175e)": "0lwa03hpp0mxa6aa1zv5w68k61y4hccfm0q2ykyq392fwal8vb50", "wu-manber 0.1.0 (git+https://github.com/tvlfyi/wu-manber.git#0d5b22bea136659f7de60b102a7030e0daaa503d)": "1zhk83lbq99xzyjwphv2qrb8f8qgfqwa5bbbvyzm0z0bljsjv0pd" diff --git a/tvix/default.nix b/tvix/default.nix index f5f1ca47cbc9..e12ff9522f01 100644 --- a/tvix/default.nix +++ b/tvix/default.nix @@ -28,11 +28,6 @@ let nativeBuildInputs = prev.nativeBuildInputs or [ ] ++ iconvDarwinDep; }; - fuser = prev: { - buildInputs = prev.buildInputs or [ ] ++ [ pkgs.fuse ]; - nativeBuildInputs = prev.nativeBuildInputs or [ ] ++ [ pkgs.pkg-config ]; - }; - prost-build = prev: { nativeBuildInputs = protobufDep prev; }; @@ -59,6 +54,7 @@ let (crateName: (lib.nameValuePair "${crateName}-${crates.internal.crates.${crateName}.version}" crates.internal.crates.${crateName}.src.outputHash) ) [ + "fuse-backend-rs" "test-generator" "tonic-mock" "wu-manber" diff --git a/tvix/store/Cargo.toml b/tvix/store/Cargo.toml index 3fabf4f84bd5..cd71555851ff 100644 --- a/tvix/store/Cargo.toml +++ b/tvix/store/Cargo.toml @@ -30,10 +30,14 @@ smol_str = "0.2.0" serde_json = "1.0" url = "2.4.0" pin-project-lite = "0.2.13" +const-zero = "0.1.1" +parking_lot = "0.12.1" -[dependencies.fuser] +[dependencies.fuse-backend-rs] optional = true -version = "0.12.0" +# TODO: Switch back to upstream version once https://github.com/cloud-hypervisor/fuse-backend-rs/pull/153 lands. +git = "https://github.com/cbrewster/fuse-backend-rs.git" +branch = "optional-allow_other" [dependencies.tonic-reflection] optional = true @@ -54,5 +58,5 @@ tonic-mock = { git = "https://github.com/brainrake/tonic-mock", branch = "bump-d [features] default = ["fuse", "reflection"] -fuse = ["dep:fuser", "dep:libc"] +fuse = ["dep:libc", "dep:fuse-backend-rs"] reflection = ["tonic-reflection"] diff --git a/tvix/store/src/bin/tvix-store.rs b/tvix/store/src/bin/tvix-store.rs index 0da264808efb..f707e3850d4f 100644 --- a/tvix/store/src/bin/tvix-store.rs +++ b/tvix/store/src/bin/tvix-store.rs @@ -22,7 +22,7 @@ use tvix_store::proto::GRPCPathInfoServiceWrapper; use tvix_store::proto::NamedNode; use tvix_store::proto::NarInfo; use tvix_store::proto::PathInfo; -use tvix_store::FUSE; +use tvix_store::{FuseDaemon, FUSE}; #[cfg(feature = "reflection")] use tvix_store::proto::FILE_DESCRIPTOR_SET; @@ -94,6 +94,10 @@ enum Commands { #[arg(long, env, default_value = "grpc+http://[::1]:8000")] path_info_service_addr: String, + /// Number of FUSE threads to spawn. + #[arg(long, env, default_value_t = default_threads())] + threads: usize, + /// Whether to list elements at the root of the mount point. /// This is useful if your PathInfoService doesn't provide an /// (exhaustive) listing. @@ -102,6 +106,13 @@ enum Commands { }, } +#[cfg(feature = "fuse")] +fn default_threads() -> usize { + std::thread::available_parallelism() + .map(|threads| threads.into()) + .unwrap_or(4) +} + #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let cli = Cli::parse(); @@ -280,6 +291,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { directory_service_addr, path_info_service_addr, list_root, + threads, } => { let blob_service = blobservice::from_addr(&blob_service_addr)?; let directory_service = directoryservice::from_addr(&directory_service_addr)?; @@ -289,35 +301,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { directory_service.clone(), )?; - let mut fuse_session = tokio::task::spawn_blocking(move || { + let mut fuse_daemon = tokio::task::spawn_blocking(move || { let f = FUSE::new( blob_service, directory_service, path_info_service, list_root, ); + info!("mounting tvix-store on {:?}", &dest); - fuser::Session::new(f, &dest, &[]) + FuseDaemon::new(f, &dest, threads) }) .await??; // grab a handle to unmount the file system, and register a signal // handler. - let mut fuse_unmounter = fuse_session.unmount_callable(); tokio::spawn(async move { tokio::signal::ctrl_c().await.unwrap(); info!("interrupt received, unmountingโฆ"); - fuse_unmounter.unmount().unwrap(); - }); - - // Start the fuse filesystem and wait for its completion, which - // happens when it's unmounted externally, or via the signal handler - // task. - tokio::task::spawn_blocking(move || -> io::Result<()> { - info!("mounting tvix-store on {:?}", fuse_session.mountpoint()); - fuse_session.run()?; + fuse_daemon.unmount()?; info!("unmount occured, terminatingโฆ"); - Ok(()) + Ok::<_, io::Error>(()) }) .await??; } diff --git a/tvix/store/src/fuse/file_attr.rs b/tvix/store/src/fuse/file_attr.rs index 25cfd28dd1f9..b946aa977a0a 100644 --- a/tvix/store/src/fuse/file_attr.rs +++ b/tvix/store/src/fuse/file_attr.rs @@ -1,20 +1,19 @@ -use std::time::SystemTime; - use super::inodes::{DirectoryInodeData, InodeData}; -use fuser::FileAttr; +use fuse_backend_rs::abi::fuse_abi::Attr; -/// The [FileAttr] describing the root -pub const ROOT_FILE_ATTR: FileAttr = FileAttr { - ino: fuser::FUSE_ROOT_ID, +/// The [Attr] describing the root +pub const ROOT_FILE_ATTR: Attr = Attr { + ino: fuse_backend_rs::api::filesystem::ROOT_ID, size: 0, blksize: 1024, blocks: 0, - atime: SystemTime::UNIX_EPOCH, - mtime: SystemTime::UNIX_EPOCH, - ctime: SystemTime::UNIX_EPOCH, - crtime: SystemTime::UNIX_EPOCH, - kind: fuser::FileType::Directory, - perm: 0o555, + mode: libc::S_IFDIR | 0o555, + atime: 0, + mtime: 0, + ctime: 0, + atimensec: 0, + mtimensec: 0, + ctimensec: 0, nlink: 0, uid: 0, gid: 0, @@ -22,10 +21,12 @@ pub const ROOT_FILE_ATTR: FileAttr = FileAttr { flags: 0, }; -/// for given &Node and inode, construct a [FileAttr] -pub fn gen_file_attr(inode_data: &InodeData, inode: u64) -> FileAttr { - FileAttr { +/// for given &Node and inode, construct an [Attr] +pub fn gen_file_attr(inode_data: &InodeData, inode: u64) -> Attr { + Attr { ino: inode, + // FUTUREWORK: play with this numbers, as it affects read sizes for client applications. + blocks: 1024, size: match inode_data { InodeData::Regular(_, size, _) => *size as u64, InodeData::Symlink(target) => target.len() as u64, @@ -34,24 +35,12 @@ pub fn gen_file_attr(inode_data: &InodeData, inode: u64) -> FileAttr { children.len() as u64 } }, - // FUTUREWORK: play with this numbers, as it affects read sizes for client applications. - blksize: 1024, - blocks: 0, - atime: SystemTime::UNIX_EPOCH, - mtime: SystemTime::UNIX_EPOCH, - ctime: SystemTime::UNIX_EPOCH, - crtime: SystemTime::UNIX_EPOCH, - kind: inode_data.into(), - perm: match inode_data { - InodeData::Regular(_, _, false) => 0o444, // no-executable files - InodeData::Regular(_, _, true) => 0o555, // executable files - InodeData::Symlink(_) => 0o444, - InodeData::Directory(..) => 0o555, + 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, }, - nlink: 0, - uid: 0, - gid: 0, - rdev: 0, - flags: 0, + ..Default::default() } } diff --git a/tvix/store/src/fuse/inodes.rs b/tvix/store/src/fuse/inodes.rs index f44dde7b804f..e8959ce3629b 100644 --- a/tvix/store/src/fuse/inodes.rs +++ b/tvix/store/src/fuse/inodes.rs @@ -66,13 +66,3 @@ impl From<proto::Directory> for InodeData { InodeData::Directory(DirectoryInodeData::Populated(digest, children)) } } - -impl From<&InodeData> for fuser::FileType { - fn from(val: &InodeData) -> Self { - match val { - InodeData::Regular(..) => fuser::FileType::RegularFile, - InodeData::Symlink(_) => fuser::FileType::Symlink, - InodeData::Directory(..) => fuser::FileType::Directory, - } - } -} diff --git a/tvix/store/src/fuse/mod.rs b/tvix/store/src/fuse/mod.rs index 978fd50e2634..1a5d884ef7a9 100644 --- a/tvix/store/src/fuse/mod.rs +++ b/tvix/store/src/fuse/mod.rs @@ -8,25 +8,34 @@ mod tests; use crate::{ blobservice::{BlobReader, BlobService}, directoryservice::DirectoryService, - fuse::{ - file_attr::gen_file_attr, - inodes::{DirectoryInodeData, InodeData}, - }, + fuse::inodes::{DirectoryInodeData, InodeData}, pathinfoservice::PathInfoService, proto::{node::Node, NamedNode}, B3Digest, Error, }; -use fuser::{FileAttr, ReplyAttr, Request}; +use fuse_backend_rs::{ + api::filesystem::{Context, FileSystem, FsOptions, ROOT_ID}, + transport::FuseSession, +}; use nix_compat::store_path::StorePath; -use std::io; -use std::os::unix::ffi::OsStrExt; -use std::str::FromStr; -use std::sync::Arc; -use std::{collections::HashMap, time::Duration}; +use parking_lot::RwLock; +use std::{ + collections::HashMap, + io, + path::Path, + str::FromStr, + sync::atomic::AtomicU64, + sync::{atomic::Ordering, Arc}, + thread, + time::Duration, +}; use tokio::io::{AsyncBufReadExt, AsyncSeekExt}; -use tracing::{debug, info_span, warn}; +use tracing::{debug, error, info_span, warn}; -use self::inode_tracker::InodeTracker; +use self::{ + file_attr::{gen_file_attr, ROOT_FILE_ATTR}, + inode_tracker::InodeTracker, +}; /// This implements a read-only FUSE filesystem for a tvix-store /// with the passed [BlobService], [DirectoryService] and [PathInfoService]. @@ -71,15 +80,15 @@ pub struct FUSE { list_root: bool, /// This maps a given StorePath to the inode we allocated for the root inode. - store_paths: HashMap<StorePath, u64>, + store_paths: RwLock<HashMap<StorePath, u64>>, /// This keeps track of inodes and data alongside them. - inode_tracker: InodeTracker, + inode_tracker: RwLock<InodeTracker>, /// This holds all open file handles - file_handles: HashMap<u64, Box<dyn BlobReader>>, + file_handles: RwLock<HashMap<u64, Arc<tokio::sync::Mutex<Box<dyn BlobReader>>>>>, - next_file_handle: u64, + next_file_handle: AtomicU64, tokio_handle: tokio::runtime::Handle, } @@ -98,11 +107,11 @@ impl FUSE { list_root, - store_paths: HashMap::default(), - inode_tracker: Default::default(), + store_paths: RwLock::new(HashMap::default()), + inode_tracker: RwLock::new(Default::default()), - file_handles: Default::default(), - next_file_handle: 1, + file_handles: RwLock::new(Default::default()), + next_file_handle: AtomicU64::new(1), tokio_handle: tokio::runtime::Handle::current(), } } @@ -114,11 +123,11 @@ impl FUSE { /// or otherwise fetch from [self.path_info_service], and then insert into /// [self.inode_tracker]. fn name_in_root_to_ino_and_data( - &mut self, - name: &std::ffi::OsStr, + &self, + name: &std::ffi::CStr, ) -> Result<Option<(u64, Arc<InodeData>)>, Error> { // parse the name into a [StorePath]. - let store_path = if let Some(name) = name.to_str() { + let store_path = if let Some(name) = name.to_str().ok() { match StorePath::from_str(name) { Ok(store_path) => store_path, Err(e) => { @@ -129,19 +138,26 @@ impl FUSE { } } } else { - debug!("{name:?} is no string"); + debug!("{name:?} is not a valid utf-8 string"); // same here. return Ok(None); }; - if let Some(ino) = self.store_paths.get(&store_path) { + let ino = { + // This extra scope makes sure we drop the read lock + // immediately after reading, to prevent deadlocks. + let store_paths = self.store_paths.read(); + store_paths.get(&store_path).cloned() + }; + + if let Some(ino) = ino { // If we already have that store path, lookup the inode from // self.store_paths and then get the data from [self.inode_tracker], // which in the case of a [InodeData::Directory] will be fully // populated. Ok(Some(( - *ino, - self.inode_tracker.get(*ino).expect("must exist"), + ino, + self.inode_tracker.read().get(ino).expect("must exist"), ))) } else { // If we don't have it, look it up in PathInfoService. @@ -157,15 +173,29 @@ impl FUSE { return Ok(None); } + // Let's check if someone else beat us to updating the inode tracker and + // store_paths map. + let mut store_paths = self.store_paths.write(); + if let Some(ino) = store_paths.get(&store_path).cloned() { + return Ok(Some(( + ino, + self.inode_tracker.read().get(ino).expect("must exist"), + ))); + } + // insert the (sparse) inode data and register in // self.store_paths. // FUTUREWORK: change put to return the data after // inserting, so we don't need to lookup a second // time? - let ino = self.inode_tracker.put((&root_node).into()); - self.store_paths.insert(store_path, ino); + let (ino, inode) = { + let mut inode_tracker = self.inode_tracker.write(); + let ino = inode_tracker.put((&root_node).into()); + (ino, inode_tracker.get(ino).unwrap()) + }; + store_paths.insert(store_path, ino); - Ok(Some((ino, self.inode_tracker.get(ino).unwrap()))) + Ok(Some((ino, inode))) } } } @@ -194,136 +224,152 @@ impl FUSE { } } -impl fuser::Filesystem for FUSE { - #[tracing::instrument(skip_all, fields(rq.inode = ino))] - fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { - debug!("getattr"); +impl FileSystem for FUSE { + type Inode = u64; + type Handle = u64; + + fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> { + Ok(FsOptions::empty()) + } - if ino == fuser::FUSE_ROOT_ID { - reply.attr(&Duration::MAX, &file_attr::ROOT_FILE_ATTR); - return; + #[tracing::instrument(skip_all, fields(rq.inode = inode))] + fn getattr( + &self, + _ctx: &Context, + inode: Self::Inode, + _handle: Option<Self::Handle>, + ) -> io::Result<(libc::stat64, Duration)> { + if inode == ROOT_ID { + return Ok((ROOT_FILE_ATTR.into(), Duration::MAX)); } - match self.inode_tracker.get(ino) { - None => reply.error(libc::ENOENT), + match self.inode_tracker.read().get(inode) { + None => return Err(io::Error::from_raw_os_error(libc::ENOENT)), Some(node) => { debug!(node = ?node, "found node"); - reply.attr(&Duration::MAX, &file_attr::gen_file_attr(&node, ino)); + Ok((gen_file_attr(&node, inode).into(), Duration::MAX)) } } } - #[tracing::instrument(skip_all, fields(rq.parent_inode = parent_ino, rq.name = ?name))] + #[tracing::instrument(skip_all, fields(rq.parent_inode = parent, rq.name = ?name))] fn lookup( - &mut self, - _req: &Request, - parent_ino: u64, - name: &std::ffi::OsStr, - reply: fuser::ReplyEntry, - ) { + &self, + _ctx: &Context, + parent: Self::Inode, + name: &std::ffi::CStr, + ) -> io::Result<fuse_backend_rs::api::filesystem::Entry> { debug!("lookup"); // This goes from a parent inode to a node. - // - If the parent is [fuser::FUSE_ROOT_ID], we need to check + // - If the parent is [ROOT_ID], we need to check // [self.store_paths] (fetching from PathInfoService if needed) // - Otherwise, lookup the parent in [self.inode_tracker] (which must be // a [InodeData::Directory]), and find the child with that name. - if parent_ino == fuser::FUSE_ROOT_ID { - match self.name_in_root_to_ino_and_data(name) { + if parent == ROOT_ID { + return match self.name_in_root_to_ino_and_data(name) { Err(e) => { warn!("{}", e); - reply.error(libc::EIO); - } - Ok(None) => { - reply.error(libc::ENOENT); + Err(io::Error::from_raw_os_error(libc::ENOENT)) } + Ok(None) => Err(io::Error::from_raw_os_error(libc::ENOENT)), Ok(Some((ino, inode_data))) => { debug!(inode_data=?&inode_data, ino=ino, "Some"); - reply_with_entry(reply, &gen_file_attr(&inode_data, ino)); + Ok(fuse_backend_rs::api::filesystem::Entry { + inode: ino, + attr: gen_file_attr(&inode_data, ino).into(), + attr_timeout: Duration::MAX, + entry_timeout: Duration::MAX, + ..Default::default() + }) } + }; + } + + // This is the "lookup for "a" inside inode 42. + // We already know that inode 42 must be a directory. + // It might not be populated yet, so if it isn't, we do (by + // fetching from [self.directory_service]), and save the result in + // [self.inode_tracker]. + // Now it for sure is populated, so we search for that name in the + // list of children and return the FileAttrs. + + // TODO: Reduce the critical section of this write lock. + let mut inode_tracker = self.inode_tracker.write(); + let parent_data = inode_tracker.get(parent).unwrap(); + let parent_data = match *parent_data { + InodeData::Regular(..) | InodeData::Symlink(_) => { + // if the parent inode was not a directory, this doesn't make sense + return Err(io::Error::from_raw_os_error(libc::ENOTDIR)); } - } else { - // This is the "lookup for "a" inside inode 42. - // We already know that inode 42 must be a directory. - // It might not be populated yet, so if it isn't, we do (by - // fetching from [self.directory_service]), and save the result in - // [self.inode_tracker]. - // Now it for sure is populated, so we search for that name in the - // list of children and return the FileAttrs. - - let parent_data = self.inode_tracker.get(parent_ino).unwrap(); - let parent_data = match *parent_data { - InodeData::Regular(..) | InodeData::Symlink(_) => { - // if the parent inode was not a directory, this doesn't make sense - reply.error(libc::ENOTDIR); - return; - } - InodeData::Directory(DirectoryInodeData::Sparse(ref parent_digest, _)) => { - match self.fetch_directory_inode_data(parent_digest) { - Ok(new_data) => { - // update data in [self.inode_tracker] with populated variant. - // FUTUREWORK: change put to return the data after - // inserting, so we don't need to lookup a second - // time? - let ino = self.inode_tracker.put(new_data); - self.inode_tracker.get(ino).unwrap() - } - Err(_e) => { - reply.error(libc::EIO); - return; - } + InodeData::Directory(DirectoryInodeData::Sparse(ref parent_digest, _)) => { + match self.fetch_directory_inode_data(parent_digest) { + Ok(new_data) => { + // update data in [self.inode_tracker] with populated variant. + // FUTUREWORK: change put to return the data after + // inserting, so we don't need to lookup a second + // time? + let ino = inode_tracker.put(new_data); + inode_tracker.get(ino).unwrap() + } + Err(_e) => { + return Err(io::Error::from_raw_os_error(libc::EIO)); } } - InodeData::Directory(DirectoryInodeData::Populated(..)) => parent_data, - }; - - // now parent_data can only be a [InodeData::Directory(DirectoryInodeData::Populated(..))]. - let (parent_digest, children) = if let InodeData::Directory( - DirectoryInodeData::Populated(ref parent_digest, ref children), - ) = *parent_data - { - (parent_digest, children) - } else { - panic!("unexpected type") - }; - let span = info_span!("lookup", directory.digest = %parent_digest); - let _enter = span.enter(); - - // in the children, find the one with the desired name. - if let Some((child_ino, _)) = - children.iter().find(|e| e.1.get_name() == name.as_bytes()) - { - // lookup the child [InodeData] in [self.inode_tracker]. - // We know the inodes for children have already been allocated. - let child_inode_data = self.inode_tracker.get(*child_ino).unwrap(); - - // Reply with the file attributes for the child. - // For child directories, we still have all data we need to reply. - reply_with_entry(reply, &gen_file_attr(&child_inode_data, *child_ino)); - } else { - // Child not found, return ENOENT. - reply.error(libc::ENOENT); } + InodeData::Directory(DirectoryInodeData::Populated(..)) => parent_data, + }; + + // now parent_data can only be a [InodeData::Directory(DirectoryInodeData::Populated(..))]. + let (parent_digest, children) = if let InodeData::Directory( + DirectoryInodeData::Populated(ref parent_digest, ref children), + ) = *parent_data + { + (parent_digest, children) + } else { + panic!("unexpected type") + }; + let span = info_span!("lookup", directory.digest = %parent_digest); + let _enter = span.enter(); + + // in the children, find the one with the desired name. + if let Some((child_ino, _)) = children.iter().find(|e| e.1.get_name() == name.to_bytes()) { + // lookup the child [InodeData] in [self.inode_tracker]. + // We know the inodes for children have already been allocated. + let child_inode_data = inode_tracker.get(*child_ino).unwrap(); + + // Reply with the file attributes for the child. + // For child directories, we still have all data we need to reply. + Ok(fuse_backend_rs::api::filesystem::Entry { + inode: *child_ino, + attr: gen_file_attr(&child_inode_data, *child_ino).into(), + attr_timeout: Duration::MAX, + entry_timeout: Duration::MAX, + ..Default::default() + }) + } else { + // Child not found, return ENOENT. + Err(io::Error::from_raw_os_error(libc::ENOENT)) } } // TODO: readdirplus? - #[tracing::instrument(skip_all, fields(rq.inode = ino, rq.offset = offset))] + #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.offset = offset))] fn readdir( - &mut self, - _req: &Request<'_>, - ino: u64, - _fh: u64, - offset: i64, - mut reply: fuser::ReplyDirectory, - ) { + &self, + _ctx: &Context, + inode: Self::Inode, + _handle: Self::Handle, + _size: u32, + offset: u64, + add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry) -> io::Result<usize>, + ) -> io::Result<()> { debug!("readdir"); - if ino == fuser::FUSE_ROOT_ID { + if inode == ROOT_ID { if !self.list_root { - reply.error(libc::EPERM); // same error code as ipfs/kubo - return; + return Err(io::Error::from_raw_os_error(libc::EPERM)); // same error code as ipfs/kubo } else { for (i, path_info) in self .path_info_service @@ -334,8 +380,7 @@ impl fuser::Filesystem for FUSE { let path_info = match path_info { Err(e) => { warn!("failed to retrieve pathinfo: {}", e); - reply.error(libc::EPERM); - return; + return Err(io::Error::from_raw_os_error(libc::EPERM)); } Ok(path_info) => path_info, }; @@ -344,41 +389,51 @@ impl fuser::Filesystem for FUSE { let root_node = path_info.node.unwrap().node.unwrap(); let store_path = StorePath::from_bytes(root_node.get_name()).unwrap(); - let ino = match self.store_paths.get(&store_path) { - Some(ino) => *ino, + let ino = { + // This extra scope makes sure we drop the read lock + // immediately after reading, to prevent deadlocks. + let store_paths = self.store_paths.read(); + store_paths.get(&store_path).cloned() + }; + let ino = match ino { + Some(ino) => ino, None => { // insert the (sparse) inode data and register in // self.store_paths. - let ino = self.inode_tracker.put((&root_node).into()); - self.store_paths.insert(store_path.clone(), ino); + let ino = self.inode_tracker.write().put((&root_node).into()); + self.store_paths.write().insert(store_path.clone(), ino); ino } }; let ty = match root_node { - Node::Directory(_) => fuser::FileType::Directory, - Node::File(_) => fuser::FileType::RegularFile, - Node::Symlink(_) => fuser::FileType::Symlink, + Node::Directory(_) => libc::S_IFDIR, + Node::File(_) => libc::S_IFREG, + Node::Symlink(_) => libc::S_IFLNK, }; - let full = - reply.add(ino, offset + i as i64 + 1_i64, ty, store_path.to_string()); - if full { + let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry { + ino, + offset: offset + i as u64 + 1, + type_: ty, + name: store_path.to_string().as_bytes(), + })?; + // If the buffer is full, add_entry will return `Ok(0)`. + if written == 0 { break; } } - reply.ok(); - return; + return Ok(()); } } // lookup the inode data. - let dir_inode_data = self.inode_tracker.get(ino).unwrap(); + let mut inode_tracker = self.inode_tracker.write(); + let dir_inode_data = inode_tracker.get(inode).unwrap(); let dir_inode_data = match *dir_inode_data { InodeData::Regular(..) | InodeData::Symlink(..) => { warn!("Not a directory"); - reply.error(libc::ENOTDIR); - return; + return Err(io::Error::from_raw_os_error(libc::ENOTDIR)); } InodeData::Directory(DirectoryInodeData::Sparse(ref directory_digest, _)) => { match self.fetch_directory_inode_data(directory_digest) { @@ -387,12 +442,11 @@ impl fuser::Filesystem for FUSE { // FUTUREWORK: change put to return the data after // inserting, so we don't need to lookup a second // time? - let ino = self.inode_tracker.put(new_data); - self.inode_tracker.get(ino).unwrap() + let ino = inode_tracker.put(new_data.clone()); + inode_tracker.get(ino).unwrap() } Err(_e) => { - reply.error(libc::EIO); - return; + return Err(io::Error::from_raw_os_error(libc::EIO)); } } } @@ -405,42 +459,49 @@ impl fuser::Filesystem for FUSE { { for (i, (ino, child_node)) in children.iter().skip(offset as usize).enumerate() { // the second parameter will become the "offset" parameter on the next call. - let full = reply.add( - *ino, - offset + i as i64 + 1_i64, - match child_node { - Node::Directory(_) => fuser::FileType::Directory, - Node::File(_) => fuser::FileType::RegularFile, - Node::Symlink(_) => fuser::FileType::Symlink, + let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry { + 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, }, - std::ffi::OsStr::from_bytes(child_node.get_name()), - ); - if full { + name: child_node.get_name(), + })?; + // If the buffer is full, add_entry will return `Ok(0)`. + if written == 0 { break; } } - reply.ok(); } else { panic!("unexpected type") } - } - #[tracing::instrument(skip_all, fields(rq.inode = ino))] - fn open(&mut self, _req: &Request<'_>, ino: u64, _flags: i32, reply: fuser::ReplyOpen) { - // get a new file handle - let fh = self.next_file_handle; + Ok(()) + } - if ino == fuser::FUSE_ROOT_ID { - reply.error(libc::ENOSYS); - return; + #[tracing::instrument(skip_all, fields(rq.inode = inode))] + fn open( + &self, + _ctx: &Context, + inode: Self::Inode, + _flags: u32, + _fuse_flags: u32, + ) -> io::Result<( + Option<Self::Handle>, + fuse_backend_rs::api::filesystem::OpenOptions, + )> { + if inode == ROOT_ID { + return Err(io::Error::from_raw_os_error(libc::ENOSYS)); } // lookup the inode - match *self.inode_tracker.get(ino).unwrap() { + match *self.inode_tracker.read().get(inode).unwrap() { // read is invalid on non-files. InodeData::Directory(..) | InodeData::Symlink(_) => { warn!("is directory"); - reply.error(libc::EISDIR); + return Err(io::Error::from_raw_os_error(libc::EISDIR)); } InodeData::Regular(ref blob_digest, _blob_size, _) => { let span = info_span!("read", blob.digest = %blob_digest); @@ -458,79 +519,87 @@ impl fuser::Filesystem for FUSE { match blob_reader { Ok(None) => { warn!("blob not found"); - reply.error(libc::EIO); + return Err(io::Error::from_raw_os_error(libc::EIO)); } Err(e) => { warn!(e=?e, "error opening blob"); - reply.error(libc::EIO); + return Err(io::Error::from_raw_os_error(libc::EIO)); } Ok(Some(blob_reader)) => { - debug!("add file handle {}", fh); - self.file_handles.insert(fh, blob_reader); - reply.opened(fh, 0); - + // get a new file handle // TODO: this will overflow after 2**64 operations, // which is fine for now. // See https://cl.tvl.fyi/c/depot/+/8834/comment/a6684ce0_d72469d1 // for the discussion on alternatives. - self.next_file_handle += 1; + let fh = self.next_file_handle.fetch_add(1, Ordering::SeqCst); + + debug!("add file handle {}", fh); + self.file_handles + .write() + .insert(fh, Arc::new(tokio::sync::Mutex::new(blob_reader))); + + Ok(( + Some(fh), + fuse_backend_rs::api::filesystem::OpenOptions::empty(), + )) } } } } } - #[tracing::instrument(skip_all, fields(rq.inode = ino, fh = fh))] + #[tracing::instrument(skip_all, fields(rq.inode = inode, fh = handle))] fn release( - &mut self, - _req: &Request<'_>, - ino: u64, - fh: u64, - _flags: i32, - _lock_owner: Option<u64>, + &self, + _ctx: &Context, + inode: Self::Inode, + _flags: u32, + handle: Self::Handle, _flush: bool, - reply: fuser::ReplyEmpty, - ) { + _flock_release: bool, + _lock_owner: Option<u64>, + ) -> io::Result<()> { // remove and get ownership on the blob reader - match self.file_handles.remove(&fh) { + match self.file_handles.write().remove(&handle) { // drop it, which will close it. Some(blob_reader) => drop(blob_reader), None => { // These might already be dropped if a read error occured. - debug!("file_handle {} not found", fh); + debug!("file_handle {} not found", handle); } } - reply.ok(); + Ok(()) } - #[tracing::instrument(skip_all, fields(rq.inode = ino, rq.offset = offset, rq.size = size))] + #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.offset = offset, rq.size = size))] fn read( - &mut self, - _req: &Request<'_>, - ino: u64, - fh: u64, - offset: i64, + &self, + _ctx: &Context, + inode: Self::Inode, + handle: Self::Handle, + w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter, size: u32, - _flags: i32, + offset: u64, _lock_owner: Option<u64>, - reply: fuser::ReplyData, - ) { + _flags: u32, + ) -> io::Result<usize> { debug!("read"); // We need to take out the blob reader from self.file_handles, so we can // interact with it in the separate task. // On success, we pass it back out of the task, so we can put it back in self.file_handles. - let mut blob_reader = match self.file_handles.remove(&fh) { - Some(blob_reader) => blob_reader, + let blob_reader = match self.file_handles.read().get(&handle) { + Some(blob_reader) => blob_reader.clone(), None => { - warn!("file handle {} unknown", fh); - reply.error(libc::EIO); - return; + warn!("file handle {} unknown", handle); + return Err(io::Error::from_raw_os_error(libc::EIO)); } }; let task = self.tokio_handle.spawn(async move { + let mut blob_reader = blob_reader.lock().await; + // seek to the offset specified, which is relative to the start of the file. let resp = blob_reader.seek(io::SeekFrom::Start(offset as u64)).await; @@ -540,68 +609,163 @@ impl fuser::Filesystem for FUSE { } Err(e) => { warn!("failed to seek to offset {}: {}", offset, e); - return Err(libc::EIO); + return Err(io::Error::from_raw_os_error(libc::EIO)); } } - // As written in the fuser docs, read should send exactly the number + // As written in the fuse docs, read should send exactly the number // of bytes requested except on EOF or error. let mut buf: Vec<u8> = Vec::with_capacity(size as usize); while (buf.len() as u64) < size as u64 { - match blob_reader.fill_buf().await { - Ok(int_buf) => { - // copy things from the internal buffer into buf to fill it till up until size + let int_buf = blob_reader.fill_buf().await?; + // copy things from the internal buffer into buf to fill it till up until size - // an empty buffer signals we reached EOF. - if int_buf.is_empty() { - break; - } + // an empty buffer signals we reached EOF. + if int_buf.is_empty() { + break; + } - // calculate how many bytes we can read from int_buf. - // It's either all of int_buf, or the number of bytes missing in buf to reach size. - let len_to_copy = std::cmp::min(int_buf.len(), size as usize - buf.len()); + // calculate how many bytes we can read from int_buf. + // It's either all of int_buf, or the number of bytes missing in buf to reach size. + let len_to_copy = std::cmp::min(int_buf.len(), size as usize - buf.len()); - // copy these bytes into our buffer - buf.extend_from_slice(&int_buf[..len_to_copy]); - // and consume them in the buffered reader. - blob_reader.consume(len_to_copy); - } - Err(e) => return Err(e.raw_os_error().unwrap()), - } + // copy these bytes into our buffer + buf.extend_from_slice(&int_buf[..len_to_copy]); + // and consume them in the buffered reader. + blob_reader.consume(len_to_copy); } - Ok((buf, blob_reader)) + + Ok(buf) }); - let resp = self.tokio_handle.block_on(task).unwrap(); + let buf = self.tokio_handle.block_on(task).unwrap()?; - match resp { - Err(e) => reply.error(e), - Ok((buf, blob_reader)) => { - reply.data(&buf); - self.file_handles.insert(fh, blob_reader); - } - } + w.write(&buf) } - #[tracing::instrument(skip_all, fields(rq.inode = ino))] - fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: fuser::ReplyData) { - if ino == fuser::FUSE_ROOT_ID { - reply.error(libc::ENOSYS); - return; + #[tracing::instrument(skip_all, fields(rq.inode = inode))] + fn readlink(&self, _ctx: &Context, inode: Self::Inode) -> io::Result<Vec<u8>> { + if inode == ROOT_ID { + return Err(io::Error::from_raw_os_error(libc::ENOSYS)); } // lookup the inode - match *self.inode_tracker.get(ino).unwrap() { + match *self.inode_tracker.read().get(inode).unwrap() { InodeData::Directory(..) | InodeData::Regular(..) => { - reply.error(libc::EINVAL); + Err(io::Error::from_raw_os_error(libc::EINVAL)) } - InodeData::Symlink(ref target) => reply.data(target), + InodeData::Symlink(ref target) => Ok(target.to_vec()), } } } -fn reply_with_entry(reply: fuser::ReplyEntry, file_attr: &FileAttr) { - reply.entry(&Duration::MAX, file_attr, 1 /* TODO: generation */); +struct FuseServer<FS> +where + FS: FileSystem + Sync + Send, +{ + server: Arc<fuse_backend_rs::api::server::Server<Arc<FS>>>, + channel: fuse_backend_rs::transport::FuseChannel, +} + +impl<FS> FuseServer<FS> +where + FS: FileSystem + Sync + Send, +{ + fn start(&mut self) -> io::Result<()> { + loop { + if let Some((reader, writer)) = self + .channel + .get_request() + .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))? + { + if let Err(e) = self + .server + .handle_message(reader, writer.into(), None, None) + { + match e { + // This indicates the session has been shut down. + fuse_backend_rs::Error::EncodeMessage(e) + if e.raw_os_error() == Some(libc::EBADFD) => + { + break; + } + error => { + error!(?error, "failed to handle fuse request"); + continue; + } + } + } + } else { + break; + } + } + Ok(()) + } +} + +pub struct FuseDaemon { + session: FuseSession, + threads: Vec<thread::JoinHandle<()>>, +} + +impl FuseDaemon { + pub fn new<FS, P>(fs: FS, mountpoint: P, threads: usize) -> Result<Self, io::Error> + where + FS: FileSystem + Sync + Send + 'static, + P: AsRef<Path>, + { + let server = Arc::new(fuse_backend_rs::api::server::Server::new(Arc::new(fs))); + + let mut session = FuseSession::new(mountpoint.as_ref(), "tvix-store", "", true) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + + session.set_allow_other(false); + session + .mount() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + let mut join_handles = Vec::with_capacity(threads); + for _ in 0..threads { + let mut server = FuseServer { + server: server.clone(), + channel: session + .new_channel() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?, + }; + let join_handle = thread::Builder::new() + .name("fuse_server".to_string()) + .spawn(move || { + let _ = server.start(); + })?; + join_handles.push(join_handle); + } + + Ok(FuseDaemon { + session, + threads: join_handles, + }) + } + + pub fn unmount(&mut self) -> Result<(), io::Error> { + self.session + .umount() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + + for thread in self.threads.drain(..) { + thread.join().map_err(|_| { + io::Error::new(io::ErrorKind::Other, "failed to join fuse server thread") + })?; + } + + Ok(()) + } +} + +impl Drop for FuseDaemon { + fn drop(&mut self) { + if let Err(error) = self.unmount() { + error!(?error, "failed to unmont fuse filesystem") + } + } } diff --git a/tvix/store/src/fuse/tests.rs b/tvix/store/src/fuse/tests.rs index 015e27ee9988..81de8b13de58 100644 --- a/tvix/store/src/fuse/tests.rs +++ b/tvix/store/src/fuse/tests.rs @@ -12,7 +12,7 @@ use crate::pathinfoservice::PathInfoService; use crate::proto::{DirectoryNode, FileNode, PathInfo}; use crate::tests::fixtures; use crate::tests::utils::{gen_blob_service, gen_directory_service, gen_pathinfo_service}; -use crate::{proto, FUSE}; +use crate::{proto, FuseDaemon, FUSE}; const BLOB_A_NAME: &str = "00000000000000000000000000000000-test"; const BLOB_B_NAME: &str = "55555555555555555555555555555555-test"; @@ -40,14 +40,14 @@ fn do_mount<P: AsRef<Path>>( path_info_service: Arc<dyn PathInfoService>, mountpoint: P, list_root: bool, -) -> io::Result<fuser::BackgroundSession> { +) -> io::Result<FuseDaemon> { let fs = FUSE::new( blob_service, directory_service, path_info_service, list_root, ); - fuser::spawn_mount2(fs, mountpoint, &[]) + FuseDaemon::new(fs, mountpoint.as_ref(), 4) } async fn populate_blob_a( @@ -294,7 +294,7 @@ async fn mount() { let tmpdir = TempDir::new().unwrap(); let (blob_service, directory_service, path_info_service) = gen_svcs(); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -303,7 +303,7 @@ async fn mount() { ) .expect("must succeed"); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure listing the root isn't allowed @@ -317,7 +317,7 @@ async fn root() { let tmpdir = TempDir::new().unwrap(); let (blob_service, directory_service, path_info_service) = gen_svcs(); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -334,7 +334,7 @@ async fn root() { assert_eq!(std::io::ErrorKind::PermissionDenied, err.kind()); } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure listing the root is allowed if configured explicitly @@ -350,7 +350,7 @@ async fn root_with_listing() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_blob_a(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -371,7 +371,7 @@ async fn root_with_listing() { assert_eq!(fixtures::BLOB_A.len() as u64, metadata.len()); } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure we can stat a file at the root @@ -387,7 +387,7 @@ async fn stat_file_at_root() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_blob_a(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -405,7 +405,7 @@ async fn stat_file_at_root() { assert!(metadata.permissions().readonly()); assert_eq!(fixtures::BLOB_A.len() as u64, metadata.len()); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure we can read a file at the root @@ -421,7 +421,7 @@ async fn read_file_at_root() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_blob_a(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -439,7 +439,7 @@ async fn read_file_at_root() { assert_eq!(fixtures::BLOB_A.len(), data.len()); assert_eq!(fixtures::BLOB_A.to_vec(), data); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure we can read a large file at the root @@ -455,7 +455,7 @@ async fn read_large_file_at_root() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_blob_b(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -481,7 +481,7 @@ async fn read_large_file_at_root() { assert_eq!(fixtures::BLOB_B.len(), data.len()); assert_eq!(fixtures::BLOB_B.to_vec(), data); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Read the target of a symlink @@ -497,7 +497,7 @@ async fn symlink_readlink() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_symlink(&blob_service, &directory_service, &path_info_service); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -524,7 +524,7 @@ async fn symlink_readlink() { let e = fs::read(p).expect_err("must fail"); assert_eq!(std::io::ErrorKind::NotFound, e.kind()); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Read and stat a regular file through a symlink pointing to it. @@ -541,7 +541,7 @@ async fn read_stat_through_symlink() { populate_blob_a(&blob_service, &directory_service, &path_info_service).await; populate_symlink(&blob_service, &directory_service, &path_info_service); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -567,7 +567,7 @@ async fn read_stat_through_symlink() { std::fs::read(p_symlink).expect("must succeed"), ); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Read a directory in the root, and validate some attributes. @@ -583,7 +583,7 @@ async fn read_stat_directory() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_directory_with_keep(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -599,7 +599,7 @@ async fn read_stat_directory() { assert!(metadata.is_dir()); assert!(metadata.permissions().readonly()); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -615,7 +615,7 @@ async fn read_blob_inside_dir() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_directory_with_keep(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -635,7 +635,7 @@ async fn read_blob_inside_dir() { let data = fs::read(&p).expect("must succeed"); assert_eq!(fixtures::EMPTY_BLOB_CONTENTS.to_vec(), data); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -652,7 +652,7 @@ async fn read_blob_deep_inside_dir() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_directory_complicated(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -676,7 +676,7 @@ async fn read_blob_deep_inside_dir() { let data = fs::read(&p).expect("must succeed"); assert_eq!(fixtures::EMPTY_BLOB_CONTENTS.to_vec(), data); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure readdir works. @@ -692,7 +692,7 @@ async fn readdir() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_directory_complicated(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -732,7 +732,7 @@ async fn readdir() { assert!(e.file_type().expect("must succeed").is_dir()); } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } #[tokio::test] @@ -748,7 +748,7 @@ async fn readdir_deep() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_directory_complicated(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -775,7 +775,7 @@ async fn readdir_deep() { assert_eq!(0, e.metadata().expect("must succeed").len()); } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Check attributes match how they show up in /nix/store normally. @@ -794,7 +794,7 @@ async fn check_attributes() { populate_symlink(&blob_service, &directory_service, &path_info_service); populate_helloworld_blob(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -840,7 +840,7 @@ async fn check_attributes() { // crtime seems MacOS only } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } #[tokio::test] @@ -858,7 +858,7 @@ async fn compare_inodes_directories() { populate_directory_with_keep(&blob_service, &directory_service, &path_info_service).await; populate_directory_complicated(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -876,7 +876,7 @@ async fn compare_inodes_directories() { fs::metadata(p_sibling_dir).expect("must succeed").ino() ); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure we allocate the same inodes for the same directory contents. @@ -893,7 +893,7 @@ async fn compare_inodes_files() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_directory_complicated(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -915,7 +915,7 @@ async fn compare_inodes_files() { fs::metadata(p_keep2).expect("must succeed").ino() ); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Ensure we allocate the same inode for symlinks pointing to the same targets. @@ -933,7 +933,7 @@ async fn compare_inodes_symlinks() { populate_directory_complicated(&blob_service, &directory_service, &path_info_service).await; populate_symlink2(&blob_service, &directory_service, &path_info_service); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -951,7 +951,7 @@ async fn compare_inodes_symlinks() { fs::symlink_metadata(p2).expect("must succeed").ino() ); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Check we match paths exactly. @@ -967,7 +967,7 @@ async fn read_wrong_paths_in_root() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_blob_a(&blob_service, &directory_service, &path_info_service).await; - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -1000,7 +1000,7 @@ async fn read_wrong_paths_in_root() { .join("00000000000000000000000000000000-tes") .exists()); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } /// Make sure writes are not allowed @@ -1016,7 +1016,7 @@ async fn disallow_writes() { let (blob_service, directory_service, path_info_service) = gen_svcs(); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -1028,9 +1028,9 @@ async fn disallow_writes() { let p = tmpdir.path().join(BLOB_A_NAME); let e = std::fs::File::create(p).expect_err("must fail"); - assert_eq!(std::io::ErrorKind::Unsupported, e.kind()); + assert_eq!(Some(libc::EROFS), e.raw_os_error()); - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } #[tokio::test] @@ -1045,7 +1045,7 @@ async fn missing_directory() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_pathinfo_without_directory(&blob_service, &directory_service, &path_info_service); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -1073,7 +1073,7 @@ async fn missing_directory() { fs::metadata(p.join(".keep")).expect_err("must fail"); } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -1088,7 +1088,7 @@ async fn missing_blob() { let (blob_service, directory_service, path_info_service) = gen_svcs(); populate_blob_a_without_blob(&blob_service, &directory_service, &path_info_service); - let fuser_session = do_mount( + let mut fuse_daemon = do_mount( blob_service, directory_service, path_info_service, @@ -1109,5 +1109,5 @@ async fn missing_blob() { fs::read(p).expect_err("must fail"); } - fuser_session.join() + fuse_daemon.unmount().expect("unmount"); } diff --git a/tvix/store/src/lib.rs b/tvix/store/src/lib.rs index 417faa39237b..252506de5977 100644 --- a/tvix/store/src/lib.rs +++ b/tvix/store/src/lib.rs @@ -14,7 +14,7 @@ pub use digests::B3Digest; pub use errors::Error; #[cfg(feature = "fuse")] -pub use fuse::FUSE; +pub use fuse::{FuseDaemon, FUSE}; #[cfg(test)] mod tests; |