diff --git a/Cargo.lock b/Cargo.lock index fe913a535d99ff79747d16824432a6de34dc90d5..fb701b3afe9ff6508ef09293e2987a66f4c00296 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,298 +4,361 @@ name = "StratoVirt" version = "0.2.0" dependencies = [ - "device_model 0.2.0", - "error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "machine_manager 0.2.0", - "util 0.2.0", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "device_model", + "error-chain", + "libc", + "log", + "machine_manager", + "util", + "vhost_user_fs", + "vmm-sys-util", ] [[package]] name = "addr2line" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" dependencies = [ - "gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gimli", ] [[package]] name = "address_space" version = "0.2.0" dependencies = [ - "error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-bindings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-ioctls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "machine_manager 0.2.0", - "util 0.2.0", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain", + "kvm-bindings", + "kvm-ioctls", + "libc", + "log", + "machine_manager", + "util", + "vmm-sys-util", ] [[package]] name = "adler32" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" [[package]] name = "backtrace" version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" dependencies = [ - "addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", ] [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "boot_loader" version = "0.2.0" dependencies = [ - "address_space 0.2.0", - "error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-bindings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-ioctls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "util 0.2.0", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "address_space", + "error-chain", + "kvm-bindings", + "kvm-ioctls", + "libc", + "log", + "util", + "vmm-sys-util", ] [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "device_model" version = "0.2.0" dependencies = [ - "address_space 0.2.0", - "boot_loader 0.2.0", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-bindings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-ioctls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "machine_manager 0.2.0", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)", - "util 0.2.0", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "address_space", + "boot_loader", + "byteorder", + "error-chain", + "kvm-bindings", + "kvm-ioctls", + "libc", + "log", + "machine_manager", + "serde", + "serde_json", + "util", + "vmm-sys-util", +] + +[[package]] +name = "errno" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", ] [[package]] name = "error-chain" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ - "backtrace 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace", + "version_check", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "gimli" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "kvm-bindings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1c07667561c3d12d77342baf28ca5d0f8c3ea2160778485640ec84a4571da2" dependencies = [ - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vmm-sys-util", ] [[package]] name = "kvm-ioctls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158d15da895bddca8223fa31dc9e8b9317bdc2fbc4635dea8dd575fc40dae37f" dependencies = [ - "kvm-bindings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "kvm-bindings", + "libc", + "vmm-sys-util", ] [[package]] name = "libc" version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "machine_manager" version = "0.2.0" dependencies = [ - "error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)", - "util 0.2.0", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain", + "libc", + "log", + "serde", + "serde_json", + "util", + "vmm-sys-util", ] [[package]] name = "miniz_oxide" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ - "adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "adler32", ] [[package]] name = "object" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" [[package]] name = "proc-macro2" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" dependencies = [ - "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" dependencies = [ - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "syn" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239f255b9e3429350f188c27b807fc9920a15eb9145230ff1a7d054c08fec319" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "util" version = "0.2.0" dependencies = [ - "error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-bindings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kvm-ioctls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain", + "kvm-bindings", + "kvm-ioctls", + "libc", + "log", + "vmm-sys-util", ] [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "vhost_user_fs" +version = "0.1.0" +dependencies = [ + "address_space", + "device_model", + "errno", + "error-chain", + "libc", + "log", + "machine_manager", + "util", + "vmm-sys-util", +] [[package]] name = "vmm-sys-util" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183d25b56a61a6f518ef464ac578e790f04added34dfaab59a453d8a03cb7bd0" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "libc", ] -[metadata] -"checksum addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" -"checksum adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" -"checksum backtrace 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum error-chain 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -"checksum gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" -"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -"checksum kvm-bindings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1c07667561c3d12d77342baf28ca5d0f8c3ea2160778485640ec84a4571da2" -"checksum kvm-ioctls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158d15da895bddca8223fa31dc9e8b9317bdc2fbc4635dea8dd575fc40dae37f" -"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -"checksum object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" -"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" -"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" -"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" -"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" -"checksum serde_json 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" -"checksum syn 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "239f255b9e3429350f188c27b807fc9920a15eb9145230ff1a7d054c08fec319" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" -"checksum vmm-sys-util 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "183d25b56a61a6f518ef464ac578e790f04added34dfaab59a453d8a03cb7bd0" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index ad1c31e47a006294fb99efe2fabf06c039216835..445fcad6516ecf2261aaada330cdfbb7d1b8752f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ license = "Mulan PSL v2" util = { path = "util" } machine_manager = { path = "machine_manager" } device_model = { path = "device_model" } +vhost_user_fs = { path = "vhost_user_fs" } libc = "0.2.71" log = "0.4.8" @@ -23,12 +24,17 @@ members = [ "boot_loader", "util", "device_model", + "vhost_user_fs", ] [[bin]] name = "stratovirt" path = "src/main.rs" +[[bin]] +name = "vhost_user_fs" +path = "src/vhost_user_fs.rs" + [features] default = [] diff --git a/device_model/src/lib.rs b/device_model/src/lib.rs index e9ba4bf0f25759f607634e54c8e211a743ce37f8..003bf6c47048a239c1d9b394065d1928309a2cc0 100644 --- a/device_model/src/lib.rs +++ b/device_model/src/lib.rs @@ -43,7 +43,7 @@ mod interrupt_controller; mod legacy; mod micro_vm; mod mmio; -mod virtio; +pub mod virtio; pub use error_chain::*; pub use micro_vm::{ diff --git a/device_model/src/micro_vm/mod.rs b/device_model/src/micro_vm/mod.rs index 62b48153400e5cf602ede7db4e76245a8df1fd7b..9563dbeaccddbdacc7cfbd0cb9dee6c27bb2fd2e 100644 --- a/device_model/src/micro_vm/mod.rs +++ b/device_model/src/micro_vm/mod.rs @@ -60,8 +60,8 @@ use machine_manager::machine::{ use machine_manager::{ config::BalloonConfig, config::{ - BootSource, ConsoleConfig, DriveConfig, NetworkInterfaceConfig, SerialConfig, VmConfig, - VsockConfig, + BootSource, ConsoleConfig, DriveConfig, FsConfig, NetworkInterfaceConfig, SerialConfig, + VmConfig, VsockConfig, }, event_loop::EventLoop, qmp::{qmp_schema, QmpChannel, Response}, @@ -178,6 +178,19 @@ impl ConfigDevBuilder for SerialConfig { } } +impl ConfigDevBuilder for FsConfig { + fn build_dev(&self, sys_mem: Arc, bus: &mut Bus) -> Result<()> { + let fs = Arc::new(Mutex::new(vhost::user::Fs::new( + self.clone(), + sys_mem.clone(), + ))); + let device = Arc::new(Mutex::new(VirtioMmioDevice::new(sys_mem, fs))); + bus.attach_device(device) + .chain_err(|| "build dev from fs config failed")?; + Ok(()) + } +} + /// A wrapper around creating and using a kvm-based micro VM. pub struct LightMachine { /// KVM VM file descriptor, represent VM entry in kvm module. @@ -597,6 +610,13 @@ impl LightMachine { .chain_err(|| "Failed to register balloon device")?; } + if let Some(fs_cfgs) = vm_config.fs { + for fs in fs_cfgs { + self.register_device(&fs) + .chain_err(|| format!("Failed to register fs device {}", fs.tag))? + } + } + Ok(()) } diff --git a/device_model/src/mmio/bus.rs b/device_model/src/mmio/bus.rs index 10983151fa516cb9cce2073277e077fa1254ec3e..e8fda3d3c07f8b9d901a82ab5ea870e0fd289686 100644 --- a/device_model/src/mmio/bus.rs +++ b/device_model/src/mmio/bus.rs @@ -107,7 +107,7 @@ impl Bus { }; for _ in 0..MMIO_REPLACEABLE_BLK_NR { - let block = Arc::new(Mutex::new(Block::new())); + let block = Arc::new(Mutex::new(Block::default())); let device = Arc::new(Mutex::new(VirtioMmioDevice::new(sys_mem.clone(), block))); if let Ok(dev) = bus.attach_device(device.clone()) { bus.replaceable_info @@ -123,7 +123,7 @@ impl Bus { } for _ in 0..MMIO_REPLACEABLE_NET_NR { - let net = Arc::new(Mutex::new(Net::new())); + let net = Arc::new(Mutex::new(Net::default())); let device = Arc::new(Mutex::new(VirtioMmioDevice::new(sys_mem.clone(), net))); if let Ok(dev) = bus.attach_device(device.clone()) { bus.replaceable_info diff --git a/device_model/src/virtio/block.rs b/device_model/src/virtio/block.rs index ecfe8fd09eaf5d3f9ba428de16b5c37d88907a01..c7be340f4e4a937feb987c9122a3e6231d2d7353 100644 --- a/device_model/src/virtio/block.rs +++ b/device_model/src/virtio/block.rs @@ -767,6 +767,12 @@ impl Block { } } +impl Default for Block { + fn default() -> Self { + Self::new() + } +} + impl VirtioDevice for Block { /// Realize virtio block device. fn realize(&mut self) -> Result<()> { diff --git a/device_model/src/virtio/mod.rs b/device_model/src/virtio/mod.rs index 10b7e9cb279f51f8ae9487d1e54eb082c884882c..c1e994577651d2faf86c2fdad89cf69fa56d2b83 100644 --- a/device_model/src/virtio/mod.rs +++ b/device_model/src/virtio/mod.rs @@ -28,7 +28,7 @@ pub mod balloon; pub mod block; pub mod console; pub mod net; -mod queue; +pub mod queue; pub mod vhost; pub use self::block::Block; @@ -55,7 +55,7 @@ pub const VIRTIO_TYPE_CONSOLE: u32 = 3; pub const _VIRTIO_TYPE_RNG: u32 = 4; pub const VIRTIO_TYPE_BALLOON: u32 = 5; pub const VIRTIO_TYPE_VSOCK: u32 = 19; -pub const _VIRTIO_TYPE_FS: u32 = 26; +pub const VIRTIO_TYPE_FS: u32 = 26; /// Feature Bits, refer to Virtio Spec. /// Negotiating this feature indicates that the driver can use descriptors diff --git a/device_model/src/virtio/net.rs b/device_model/src/virtio/net.rs index 075b165037c67e3f27bf61ab56e1c5ebfacf5bc3..755c6a99aec321fcd923f47d3230f9b7c231cbae 100644 --- a/device_model/src/virtio/net.rs +++ b/device_model/src/virtio/net.rs @@ -566,6 +566,12 @@ impl Net { } } +impl Default for Net { + fn default() -> Self { + Self::new() + } +} + impl VirtioDevice for Net { /// Realize virtio network device. fn realize(&mut self) -> Result<()> { diff --git a/device_model/src/virtio/vhost/kernel/mod.rs b/device_model/src/virtio/vhost/kernel/mod.rs index 47c2a13f43cac9eeb95414da0147ba9edd496f7b..bf80e479bfc91f6b694dbf7dd033518efbae85bb 100644 --- a/device_model/src/virtio/vhost/kernel/mod.rs +++ b/device_model/src/virtio/vhost/kernel/mod.rs @@ -126,7 +126,7 @@ pub struct VhostMemInfo { } impl VhostMemInfo { - pub fn new() -> VhostMemInfo { + fn new() -> VhostMemInfo { VhostMemInfo { regions: Arc::new(Mutex::new(Vec::new())), } @@ -183,6 +183,12 @@ impl VhostMemInfo { } } +impl Default for VhostMemInfo { + fn default() -> Self { + Self::new() + } +} + impl Listener for VhostMemInfo { fn priority(&self) -> i32 { 0 @@ -232,7 +238,7 @@ impl VhostBackend { .open(path) .chain_err(|| format!("Failed to open {} for vhost backend.", path))?, }; - let mem_info = VhostMemInfo::new(); + let mem_info = VhostMemInfo::default(); mem_space.register_listener(Box::new(mem_info.clone()))?; Ok(VhostBackend { fd, mem_info }) diff --git a/device_model/src/virtio/vhost/mod.rs b/device_model/src/virtio/vhost/mod.rs index b6669f9e8ab2026aac4097bae843245551139e65..8c5c6c8725bb8ed3d36416e09faef8a931e85217 100644 --- a/device_model/src/virtio/vhost/mod.rs +++ b/device_model/src/virtio/vhost/mod.rs @@ -11,6 +11,7 @@ // See the Mulan PSL v2 for more details. pub mod kernel; +pub mod user; use std::sync::{Arc, Mutex}; @@ -79,4 +80,13 @@ pub trait VhostOps { /// * `queue_idx` - Index of the queue to modify. /// * `fd` - EventFd that will be signaled from guest. fn set_vring_kick(&self, queue_idx: usize, fd: &EventFd) -> Result<()>; + + /// Set the status of ring. + /// + /// # Arguments + /// * `queue_idx` - Index of the queue to set. + /// * `status` - Status of the virtqueue. + fn set_vring_enable(&self, _queue_idx: usize, _status: bool) -> Result<()> { + Ok(()) + } } diff --git a/device_model/src/virtio/vhost/user/fs.rs b/device_model/src/virtio/vhost/user/fs.rs new file mode 100644 index 0000000000000000000000000000000000000000..6d697fabe857179553d6b3614d1819d77b74d09f --- /dev/null +++ b/device_model/src/virtio/vhost/user/fs.rs @@ -0,0 +1,304 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::cmp; +use std::io::Write; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Arc, Mutex}; + +use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::eventfd::EventFd; + +use address_space::AddressSpace; +use machine_manager::{ + config::{FsConfig, MAX_TAG_LENGTH}, + event_loop::EventLoop, +}; +use util::byte_code::ByteCode; +use util::loop_context::{read_fd, EventNotifier, EventNotifierHelper, NotifierOperation}; +use util::num_ops::{read_u32, write_u32}; + +use super::super::super::errors::{ErrorKind, Result, ResultExt}; +use super::super::super::{Queue, VirtioDevice, VIRTIO_MMIO_INT_VRING, VIRTIO_TYPE_FS}; +use super::super::{VhostNotify, VhostOps}; +use super::VhostUserClient; + +// The num of high priority queue +const VIRIOT_FS_HIGH_PRIO_QUEUE_NUM: usize = 1; +// The num of request queue +const VIRTIO_FS_REQ_QUEUES_NUM: usize = 1; +// The size of queue for virtio fs +const VIRTIO_FS_QUEUE_SIZE: u16 = 1024; + +#[derive(Copy, Clone)] +#[repr(C, packed)] +struct VirtioFsConfig { + tag: [u8; MAX_TAG_LENGTH], + num_request_queues: u32, +} + +impl Default for VirtioFsConfig { + fn default() -> Self { + VirtioFsConfig { + tag: [0; MAX_TAG_LENGTH], + num_request_queues: 0, + } + } +} + +impl ByteCode for VirtioFsConfig {} + +struct VhostUserFsHandler { + interrupt_evt: EventFd, + interrupt_status: Arc, + host_notifies: Vec, +} + +impl EventNotifierHelper for VhostUserFsHandler { + fn internal_notifiers(vhost_user_handler: Arc>) -> Vec { + let mut notifiers = Vec::new(); + let vhost_user = vhost_user_handler.clone(); + + let handler: Box Option>> = + Box::new(move |_, fd: RawFd| { + read_fd(fd); + + let v = vhost_user.lock().unwrap(); + v.interrupt_status + .fetch_or(VIRTIO_MMIO_INT_VRING, Ordering::SeqCst); + if v.interrupt_evt.write(1).is_err() { + error!("Failed to write interrupt eventfd for virtio fs"); + } + + None as Option> + }); + let h = Arc::new(Mutex::new(handler)); + + for host_notify in vhost_user_handler.lock().unwrap().host_notifies.iter() { + notifiers.push(EventNotifier::new( + NotifierOperation::AddShared, + host_notify.notify_evt.as_raw_fd(), + None, + EventSet::IN, + vec![h.clone()], + )); + } + + notifiers + } +} + +pub struct Fs { + fs_cfg: FsConfig, + config: VirtioFsConfig, + client: Option, + avail_features: u64, + acked_features: u64, + mem_space: Arc, +} + +impl Fs { + pub fn new(fs_cfg: FsConfig, mem_space: Arc) -> Self { + Fs { + fs_cfg, + config: VirtioFsConfig::default(), + client: None, + avail_features: 0_u64, + acked_features: 0_u64, + mem_space, + } + } +} + +impl VirtioDevice for Fs { + fn realize(&mut self) -> Result<()> { + let tag_bytes_vec = self.fs_cfg.tag.clone().into_bytes(); + self.config.tag[..tag_bytes_vec.len()].copy_from_slice(tag_bytes_vec.as_slice()); + self.config.num_request_queues = VIRTIO_FS_REQ_QUEUES_NUM as u32; + + let queues_num = VIRIOT_FS_HIGH_PRIO_QUEUE_NUM + VIRTIO_FS_REQ_QUEUES_NUM; + let client = VhostUserClient::new(&self.mem_space, &self.fs_cfg.sock, queues_num as u64) + .chain_err(|| { + "Failed to create the client which communicates with the server for virtio fs" + })?; + client + .add_event_notifier() + .chain_err(|| "Failed to add event nitifier for virtio fs")?; + + self.avail_features = client + .get_features() + .chain_err(|| "Failed to get features for virtio fs")?; + self.client = Some(client); + + Ok(()) + } + + fn device_type(&self) -> u32 { + VIRTIO_TYPE_FS as u32 + } + + fn queue_num(&self) -> usize { + VIRIOT_FS_HIGH_PRIO_QUEUE_NUM + VIRTIO_FS_REQ_QUEUES_NUM + } + + fn queue_size(&self) -> u16 { + VIRTIO_FS_QUEUE_SIZE + } + + fn get_device_features(&self, features_select: u32) -> u32 { + read_u32(self.avail_features, features_select) + } + + fn set_driver_features(&mut self, page: u32, value: u32) { + let mut features = write_u32(value, page); + let unsupported_features = features & !self.avail_features; + if unsupported_features != 0 { + warn!( + "Received acknowledge request with unsupported feature for virtio fs: 0x{:x}", + features + ); + features &= !unsupported_features; + } + self.acked_features |= features; + } + + fn read_config(&self, offset: u64, mut data: &mut [u8]) -> Result<()> { + let config_slice = self.config.as_bytes(); + let config_size = config_slice.len() as u64; + if offset >= config_size { + return Err(ErrorKind::DevConfigOverflow(offset, config_size).into()); + } + if let Some(end) = offset.checked_add(data.len() as u64) { + data.write_all(&config_slice[offset as usize..cmp::min(end, config_size) as usize])?; + } + + Ok(()) + } + + fn write_config(&mut self, offset: u64, data: &[u8]) -> Result<()> { + let data_len = data.len(); + let config_slice = self.config.as_mut_bytes(); + let config_len = config_slice.len(); + if offset as usize + data_len > config_len { + return Err(ErrorKind::DevConfigOverflow(offset, config_len as u64).into()); + } + + config_slice[(offset as usize)..(offset as usize + data_len)].copy_from_slice(&data[..]); + + Ok(()) + } + + fn activate( + &mut self, + _mem_space: Arc, + interrupt_evt: EventFd, + interrupt_status: Arc, + queues: Vec>>, + queue_evts: Vec, + ) -> Result<()> { + let mut host_notifies = Vec::new(); + let client = match &self.client { + None => return Err("Failed to get client for virtio fs".into()), + Some(client_) => client_, + }; + + client + .set_owner() + .chain_err(|| "Failed to set owner for virtio fs")?; + client + .set_features(self.acked_features) + .chain_err(|| "Failed to set features for virtio fs")?; + client + .set_mem_table() + .chain_err(|| "Failed to set mem table for virtio fs")?; + + for (queue_index, queue_mutex) in queues.to_vec().iter().enumerate() { + let queue = queue_mutex.lock().unwrap(); + let actual_size = queue.vring.actual_size(); + let queue_config = queue.vring.get_queue_config(); + + client.set_vring_enable(queue_index, false).chain_err(|| { + format!( + "Failed to set vring enable for virtio fs, index: {}, false", + queue_index, + ) + })?; + client + .set_vring_num(queue_index, actual_size) + .chain_err(|| { + format!( + "Failed to set vring num for virtio fs, index: {}, size: {}", + queue_index, actual_size, + ) + })?; + client + .set_vring_addr(&queue_config, queue_index, 0) + .chain_err(|| { + format!( + "Failed to set vring addr for virtio fs, index: {}", + queue_index, + ) + })?; + client.set_vring_base(queue_index, 0).chain_err(|| { + format!( + "Failed to set vring base for virtio fs, index: {}", + queue_index, + ) + })?; + client + .set_vring_kick(queue_index, &queue_evts[queue_index]) + .chain_err(|| { + format!( + "Failed to set vring kick for virtio fs, index: {}", + queue_index, + ) + })?; + + drop(queue); + + let host_notify = VhostNotify { + notify_evt: EventFd::new(libc::EFD_NONBLOCK) + .chain_err(|| ErrorKind::EventFdCreate)?, + queue: queue_mutex.clone(), + }; + client + .set_vring_call(queue_index, &host_notify.notify_evt) + .chain_err(|| { + format!( + "Failed to set vring call for virtio fs, index: {}", + queue_index, + ) + })?; + host_notifies.push(host_notify); + + client.set_vring_enable(queue_index, true).chain_err(|| { + format!( + "Failed to set vring enable for virtio fs, index: {}, true", + queue_index, + ) + })?; + } + + let handler = VhostUserFsHandler { + interrupt_evt: interrupt_evt.try_clone()?, + interrupt_status, + host_notifies, + }; + EventLoop::update_event( + EventNotifierHelper::internal_notifiers(Arc::new(Mutex::new(handler))), + None, + )?; + + Ok(()) + } +} diff --git a/device_model/src/virtio/vhost/user/mod.rs b/device_model/src/virtio/vhost/user/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..99bf78e9bac37840260f7116a7b13438442f7b7c --- /dev/null +++ b/device_model/src/virtio/vhost/user/mod.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +pub mod fs; +pub mod vhost_user_client; +pub mod vhost_user_msg; +pub mod vhost_user_sock; + +pub use self::fs::*; +pub use self::vhost_user_client::*; +pub use self::vhost_user_msg::*; +pub use self::vhost_user_sock::*; diff --git a/device_model/src/virtio/vhost/user/vhost_user_client.rs b/device_model/src/virtio/vhost/user/vhost_user_client.rs new file mode 100644 index 0000000000000000000000000000000000000000..423072be77aaa3ad6f1418cba4564a023595c617 --- /dev/null +++ b/device_model/src/virtio/vhost/user/vhost_user_client.rs @@ -0,0 +1,468 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::mem::size_of; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::{Arc, Mutex}; + +use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; + +use address_space::{ + AddressSpace, FileBackend, FlatRange, Listener, ListenerReqType, RegionIoEventFd, RegionType, +}; +use machine_manager::event_loop::EventLoop; +use util::loop_context::{EventNotifier, EventNotifierHelper, NotifierOperation}; + +use super::super::super::{ + errors::{Result, ResultExt}, + QueueConfig, +}; +use super::super::VhostOps; +use super::vhost_user_msg::*; +use super::vhost_user_sock::UnixDomainSock; + +struct ClientInternal { + // Used to send requests to the process of filesystem in userspace. + sock: UnixDomainSock, + // Maxinum number of queues which is supported. + max_queue_num: u64, +} + +impl ClientInternal { + fn new(sock: UnixDomainSock, max_queue_num: u64) -> Self { + ClientInternal { + sock, + max_queue_num, + } + } + + fn wait_ack_msg(&self, request: u32) -> Result { + let mut hdr = VhostUserMsgHeader::default(); + let mut body: T = Default::default(); + let payload_opt: Option<&mut [u8]> = None; + + let (recv_len, _fds_num) = self + .sock + .vhostuser_msgrecv(Some(&mut hdr), Some(&mut body), payload_opt, &mut []) + .chain_err(|| "Failed to recv ack msg")?; + + if request != hdr.request + || recv_len != (size_of::() + size_of::()) + || !hdr.is_reply() + { + bail!("The ack msg is invalid, request: {}, header request: {}, reply type: {}, recv len: {}, len: {}", + request, hdr.request, hdr.is_reply(), recv_len, size_of::() + size_of::(), + ); + } + + Ok(body) + } +} + +impl EventNotifierHelper for ClientInternal { + fn internal_notifiers(client_handler: Arc>) -> Vec { + let mut notifiers = Vec::new(); + let mut handlers = Vec::new(); + + let handler: Box Option>> = + Box::new(move |event, _| { + if event & EventSet::HANG_UP == EventSet::HANG_UP { + panic!("Receive the event of HANG_UP from vhost_user_fs"); + } else { + None + } + }); + handlers.push(Arc::new(Mutex::new(handler))); + + notifiers.push(EventNotifier::new( + NotifierOperation::AddShared, + client_handler.lock().unwrap().sock.get_stream_raw_fd(), + None, + EventSet::HANG_UP, + handlers, + )); + notifiers + } +} + +#[derive(Clone)] +struct VhostUserMemoryRegionInfo { + region: VhostUserMemoryRegion, + file_back: FileBackend, +} + +#[derive(Clone)] +struct VhostUserMemInfo { + regions: Arc>>, +} + +impl VhostUserMemInfo { + fn new() -> Self { + VhostUserMemInfo { + regions: Arc::new(Mutex::new(Vec::new())), + } + } + + fn check_vhost_mem_range(fr: &FlatRange) -> bool { + fr.owner.region_type() == RegionType::Ram + } + + fn add_mem_range(&self, fr: &FlatRange) { + let guest_phys_addr = fr.addr_range.base.raw_value(); + let memory_size = fr.addr_range.size; + let host_address = if let Some(addr) = fr.owner.get_host_address() { + addr + } else { + panic!("Failed to get host address to add mem range for virtio fs"); + }; + let userspace_addr = host_address + fr.offset_in_region; + let file_back = match fr.owner.get_file_backend() { + Some(file_back_) => file_back_, + _ => { + panic!("It is not share memory for virtio fs"); + } + }; + + let region = VhostUserMemoryRegion { + guest_phys_addr, + memory_size, + userspace_addr, + mmap_offset: file_back.offset, + }; + + let region_info = VhostUserMemoryRegionInfo { region, file_back }; + self.regions.lock().unwrap().push(region_info); + } + + fn delete_mem_range(&self, fr: &FlatRange) { + let file_back = fr.owner.get_file_backend().unwrap(); + let mut mem_regions = self.regions.lock().unwrap(); + let host_address = if let Some(addr) = fr.owner.get_host_address() { + addr + } else { + panic!("Failed to get host address to delete mem range for virtio fs"); + }; + + let target = VhostUserMemoryRegion { + guest_phys_addr: fr.addr_range.base.raw_value(), + memory_size: fr.addr_range.size, + userspace_addr: host_address + fr.offset_in_region, + mmap_offset: file_back.offset, + }; + for (index, region_info) in mem_regions.iter().enumerate() { + let mr = ®ion_info.region; + if mr.guest_phys_addr == target.guest_phys_addr + && mr.memory_size == target.memory_size + && mr.userspace_addr == target.userspace_addr + && mr.mmap_offset == target.mmap_offset + && region_info.file_back.file.as_raw_fd() == file_back.file.as_raw_fd() + { + mem_regions.remove(index); + return; + } + } + error!( + "Vhost user: deleting mem region {:?} failed: not matched", + target + ); + } +} + +impl Listener for VhostUserMemInfo { + fn priority(&self) -> i32 { + 0 + } + + fn handle_request( + &self, + range: Option<&FlatRange>, + _evtfd: Option<&RegionIoEventFd>, + req_type: ListenerReqType, + ) -> std::result::Result<(), address_space::errors::Error> { + match req_type { + ListenerReqType::AddRegion => { + if Self::check_vhost_mem_range(&range.unwrap()) { + self.add_mem_range(range.unwrap()); + } + } + ListenerReqType::DeleteRegion => { + if Self::check_vhost_mem_range(&range.unwrap()) { + self.delete_mem_range(range.unwrap()); + } + } + _ => {} + } + Ok(()) + } +} + +/// Struct for virtio fs device which can communicate with the process +/// of filesystem in userspace +#[derive(Clone)] +pub struct VhostUserClient { + client: Arc>, + mem_info: VhostUserMemInfo, +} + +impl VhostUserClient { + pub fn new(mem_space: &Arc, path: &str, max_queue_num: u64) -> Result { + let mut sock = UnixDomainSock::new(path); + sock.client_connect().chain_err(|| { + format!( + "Failed to connect the socket {} for vhost user client", + path + ) + })?; + + let mem_info = VhostUserMemInfo::new(); + mem_space + .register_listener(Box::new(mem_info.clone())) + .chain_err(|| "Failed to register listener for memory for vhost user client")?; + + let client = Arc::new(Mutex::new(ClientInternal::new(sock, max_queue_num))); + Ok(VhostUserClient { client, mem_info }) + } + + pub fn add_event_notifier(&self) -> Result<()> { + EventLoop::update_event( + EventNotifierHelper::internal_notifiers(self.client.clone()), + None, + ) + .chain_err(|| "Failed to update event for client sock")?; + + Ok(()) + } +} + +impl VhostOps for VhostUserClient { + fn set_owner(&self) -> Result<()> { + let hdr = VhostUserMsgHeader::new(VhostUserMsgReq::SetOwner as u32, 0, 0); + + let body_opt: Option<&u32> = None; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + client + .sock + .vhostuser_msgsend(Some(&hdr), body_opt, payload_opt, &[]) + .chain_err(|| "Failed to send msg for setting owner")?; + + Ok(()) + } + + fn get_features(&self) -> Result { + let request = VhostUserMsgReq::GetFeatures as u32; + let hdr = VhostUserMsgHeader::new(request, VhostUserHeaderFlag::NeedReply as u32, 0); + let body_opt: Option<&u32> = None; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + client + .sock + .vhostuser_msgsend(Some(&hdr), body_opt, payload_opt, &[]) + .chain_err(|| "Failed to send msg for getting features")?; + + let features = client + .wait_ack_msg::(request) + .chain_err(|| "Failed to wait ack msg for getting features")?; + + Ok(features) + } + + fn set_features(&self, features: u64) -> Result<()> { + let hdr = VhostUserMsgHeader::new( + VhostUserMsgReq::SetFeatures as u32, + 0, + size_of::() as u32, + ); + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&features), payload_opt, &[]) + .chain_err(|| "Failed to send msg for setting features")?; + + Ok(()) + } + + fn set_mem_table(&self) -> Result<()> { + let num_region = self.mem_info.regions.lock().unwrap().len(); + let mut fds = Vec::with_capacity(num_region); + + let memhdr = VhostUserMemoryHdr::new(num_region as u32, 0); + let mut memcontext = VhostUserMemoryContext::default(); + + for region_info in self.mem_info.regions.lock().unwrap().iter() { + memcontext.region_add(region_info.region.clone()); + fds.push(region_info.file_back.file.as_raw_fd()); + } + + let len = size_of::() + num_region * size_of::(); + let request = VhostUserMsgReq::SetMemTable as u32; + let client = self.client.lock().unwrap(); + let flags = 0; + let hdr = VhostUserMsgHeader::new(request, flags, len as u32); + + client + .sock + .vhostuser_msgsend( + Some(&hdr), + Some(&memhdr), + Some(memcontext.regions.as_slice()), + &fds, + ) + .chain_err(|| "Failed to send msg for setting mem table")?; + + Ok(()) + } + + fn set_vring_num(&self, queue_idx: usize, num: u16) -> Result<()> { + let request = VhostUserMsgReq::SetVringNum as u32; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + if queue_idx as u64 > client.max_queue_num { + bail!( + "The queue index {} is invaild {} for setting vring num", + queue_idx, + client.max_queue_num + ); + } + + let vringstate = VhostUserVringState::new(queue_idx as u32, num as u32); + let hdr = VhostUserMsgHeader::new(request, 0, size_of::() as u32); + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&vringstate), payload_opt, &[]) + .chain_err(|| "Failed to send msg for setting vring num")?; + + Ok(()) + } + + fn set_vring_addr(&self, queue: &QueueConfig, index: usize, flags: u32) -> Result<()> { + let vringaddr = VhostUserVringAddr { + index: index as u32, + flags, + desc_user_addr: queue.desc_table.0, + used_user_addr: queue.used_ring.0, + avail_user_addr: queue.avail_ring.0, + log_guest_addr: 0, + }; + let payload_opt: Option<&[u8]> = None; + + let hdr = VhostUserMsgHeader::new( + VhostUserMsgReq::SetVringAddr as u32, + 0, + size_of::() as u32, + ); + let client = self.client.lock().unwrap(); + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&vringaddr), payload_opt, &[]) + .chain_err(|| "Failed to send msg for setting vring addr")?; + + Ok(()) + } + + fn set_vring_base(&self, queue_idx: usize, last_avail_idx: u16) -> Result<()> { + let request = VhostUserMsgReq::SetVringBase as u32; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + if queue_idx as u64 > client.max_queue_num { + bail!( + "The queue index {} is invaild {} for setting vring base", + queue_idx, + client.max_queue_num + ); + } + + let vringstate = VhostUserVringState::new(queue_idx as u32, last_avail_idx as u32); + let hdr = VhostUserMsgHeader::new(request, 0, size_of::() as u32); + + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&vringstate), payload_opt, &[]) + .chain_err(|| "Failed to send msg for setting vring base")?; + + Ok(()) + } + + fn set_vring_call(&self, queue_idx: usize, fd: &EventFd) -> Result<()> { + let request = VhostUserMsgReq::SetVringCall as u32; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + if queue_idx as u64 > client.max_queue_num { + bail!( + "The queue index {} is invaild {} for setting vring call", + queue_idx, + client.max_queue_num + ); + } + + let hdr = VhostUserMsgHeader::new(request, 0, size_of::() as u32); + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&queue_idx), payload_opt, &[fd.as_raw_fd()]) + .chain_err(|| "Failed to send msg for setting vring call")?; + + Ok(()) + } + + fn set_vring_kick(&self, queue_idx: usize, fd: &EventFd) -> Result<()> { + let request = VhostUserMsgReq::SetVringKick as u32; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + if queue_idx as u64 > client.max_queue_num { + bail!( + "The queue index {} is invaild {} for setting vring kick", + queue_idx, + client.max_queue_num + ); + } + + let hdr = VhostUserMsgHeader::new(request, 0, size_of::() as u32); + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&queue_idx), payload_opt, &[fd.as_raw_fd()]) + .chain_err(|| "Failed to send msg for setting vring kick")?; + + Ok(()) + } + + fn set_vring_enable(&self, queue_idx: usize, status: bool) -> Result<()> { + let request = VhostUserMsgReq::SetVringEnable as u32; + let payload_opt: Option<&[u8]> = None; + + let client = self.client.lock().unwrap(); + if queue_idx as u64 > client.max_queue_num { + bail!( + "The queue index {} is invaild {} for setting vring enable", + queue_idx, + client.max_queue_num + ); + } + + let vringstate = VhostUserVringState::new(queue_idx as u32, status as u32); + let hdr = VhostUserMsgHeader::new(request, 0, size_of::() as u32); + + client + .sock + .vhostuser_msgsend(Some(&hdr), Some(&vringstate), payload_opt, &[]) + .chain_err(|| "Failed to send msg for setting vring enable")?; + + Ok(()) + } +} diff --git a/device_model/src/virtio/vhost/user/vhost_user_msg.rs b/device_model/src/virtio/vhost/user/vhost_user_msg.rs new file mode 100644 index 0000000000000000000000000000000000000000..738bb295a2b392c12222474da7acf4d492453396 --- /dev/null +++ b/device_model/src/virtio/vhost/user/vhost_user_msg.rs @@ -0,0 +1,283 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use super::vhost_user_sock::VHOST_USER_MSG_MAX_SIZE; + +/// Type of requests sending from virtio fs device to the userspace filesystem process. +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum VhostUserMsgReq { + /// Null operation. + Noop = 0, + /// Get from the underlying vhost implementation the features bit mask. + GetFeatures = 1, + /// Enable features in the underlying vhost implementation using a bit mask. + SetFeatures = 2, + /// Set the current Master as an owner of the session. + SetOwner = 3, + /// No longer used. + ResetOwner = 4, + /// Set the memory map regions on the slave so it can translate the vring addresses. + SetMemTable = 5, + /// Set logging shared memory space. + SetLogBase = 6, + /// Set the logging file descriptor, which is passed as ancillary data. + SetLogFd = 7, + /// Set the size of the queue. + SetVringNum = 8, + /// Set the addresses of the different aspects of the vring. + SetVringAddr = 9, + /// Set the base offset in the available vring. + SetVringBase = 10, + /// Get the available vring base offset. + GetVringBase = 11, + /// Set the event file descriptor for adding buffers to the vring. + SetVringKick = 12, + /// Set the event file descriptor to signal when buffers are used. + SetVringCall = 13, + /// Set the event file descriptor to signal when error occurs. + SetVringErr = 14, + /// Get the protocol feature bit mask from the underlying vhost implementation. + GetProtocolFeatures = 15, + /// Enable protocol features in the underlying vhost implementation. + SetProtocolFeatures = 16, + /// Query how many queues the backend supports. + GetQueueNum = 17, + /// Signal slave to enable or disable corresponding vring. + SetVringEnable = 18, + /// Ask vhost user backend to broadcast a fake RARP to notify the migration is terminated + /// for guest that does not support GUEST_ANNOUNCE. + SendRarp = 19, + /// Set host MTU value exposed to the guest. + NetSetMtu = 20, + /// Set the socket file descriptor for slave initiated requests. + SetSlaveReqFd = 21, + /// Send IOTLB messages with struct vhost_iotlb_msg as payload. + IotlbMsg = 22, + /// Set the endianness of a VQ for legacy devices. + SetVringEndian = 23, + /// Fetch the contents of the virtio device configuration space. + GetConfig = 24, + /// Change the contents of the virtio device configuration space. + SetConfig = 25, + /// Create a session for crypto operation. + CreateCryptoSession = 26, + /// Close a session for crypto operation. + CloseCryptoSession = 27, + /// Advise slave that a migration with postcopy enabled is underway. + PostcopyAdvise = 28, + /// Advise slave that a transition to postcopy mode has happened. + PostcopyListen = 29, + /// Advise that postcopy migration has now completed. + PostcopyEnd = 30, + /// Get a shared buffer from slave. + GetInflightFd = 31, + /// Send the shared inflight buffer back to slave + SetInflightFd = 32, + /// Upper bound of valid commands. + MaxCmd = 33, +} + +impl From for VhostUserMsgReq { + fn from(t: u32) -> Self { + match t { + 0 => VhostUserMsgReq::Noop, + 1 => VhostUserMsgReq::GetFeatures, + 2 => VhostUserMsgReq::SetFeatures, + 3 => VhostUserMsgReq::SetOwner, + 4 => VhostUserMsgReq::ResetOwner, + 5 => VhostUserMsgReq::SetMemTable, + 6 => VhostUserMsgReq::SetLogBase, + 7 => VhostUserMsgReq::SetLogFd, + 8 => VhostUserMsgReq::SetVringNum, + 9 => VhostUserMsgReq::SetVringAddr, + 10 => VhostUserMsgReq::SetVringBase, + 11 => VhostUserMsgReq::GetVringBase, + 12 => VhostUserMsgReq::SetVringKick, + 13 => VhostUserMsgReq::SetVringCall, + 14 => VhostUserMsgReq::SetVringErr, + 15 => VhostUserMsgReq::GetProtocolFeatures, + 16 => VhostUserMsgReq::SetProtocolFeatures, + 17 => VhostUserMsgReq::GetQueueNum, + 18 => VhostUserMsgReq::SetVringEnable, + 19 => VhostUserMsgReq::SendRarp, + 20 => VhostUserMsgReq::NetSetMtu, + 21 => VhostUserMsgReq::SetSlaveReqFd, + 22 => VhostUserMsgReq::IotlbMsg, + 23 => VhostUserMsgReq::SetVringEndian, + 24 => VhostUserMsgReq::GetConfig, + 25 => VhostUserMsgReq::SetConfig, + 26 => VhostUserMsgReq::CreateCryptoSession, + 27 => VhostUserMsgReq::CloseCryptoSession, + 28 => VhostUserMsgReq::PostcopyAdvise, + 29 => VhostUserMsgReq::PostcopyListen, + 30 => VhostUserMsgReq::PostcopyEnd, + 31 => VhostUserMsgReq::GetInflightFd, + 32 => VhostUserMsgReq::SetInflightFd, + _ => VhostUserMsgReq::MaxCmd, + } + } +} + +/// The meaning of flag bits for header of vhost user message +pub enum VhostUserHeaderFlag { + /// Bits[0..1] is message version number. + _Version = 0x3, + /// Bits[2] Mark message as reply. + Reply = 0x4, + /// Bits[3] Sender anticipates a reply message from the peer. + NeedReply = 0x8, + /// All valid bits. + AllFlags = 0xc, + /// All reserved bits. + ReservedBits = !0xf, +} + +///the struct for the header of vhost user message +pub struct VhostUserMsgHeader { + /// The request id for vhost-user message + pub request: u32, + /// The flags for property setting + pub flags: u32, + /// The total length of vhost user message + pub size: u32, +} + +impl VhostUserMsgHeader { + /// Create a new instance of `VhostUserMsgHeader`. + pub fn new(request: u32, flags: u32, size: u32) -> Self { + // Default to protocol version 1 + let flag = (flags & VhostUserHeaderFlag::AllFlags as u32) | 0x1; + VhostUserMsgHeader { + request, + flags: flag, + size, + } + } + + /// Get message version number. + #[allow(dead_code)] + fn get_version(&self) -> u32 { + self.flags & 0x3 + } + + /// Check whether reply for this message is requested. + #[allow(dead_code)] + pub fn is_need_reply(&self) -> bool { + (self.flags & VhostUserHeaderFlag::NeedReply as u32) != 0 + } + + /// Check whether reply for message. + pub fn is_reply(&self) -> bool { + (self.flags & VhostUserHeaderFlag::Reply as u32) != 0 + } + + /// Check the header of vhost user message is invaild + #[allow(dead_code)] + pub fn is_invalid(&self) -> bool { + self.request >= VhostUserMsgReq::MaxCmd as u32 + || self.size > VHOST_USER_MSG_MAX_SIZE as u32 + || self.flags & (VhostUserHeaderFlag::ReservedBits as u32) != 0 + || self.get_version() != 0x1 + } +} + +impl Default for VhostUserMsgHeader { + fn default() -> Self { + VhostUserMsgHeader { + request: 0, + flags: 0x1, + size: 0, + } + } +} + +/// Memory region information for the message of memory table +#[derive(Clone, Debug)] +pub struct VhostUserMemoryRegion { + /// Guest physical address of the memory region. + pub guest_phys_addr: u64, + /// Size of the memory region. + pub memory_size: u64, + /// Virtual address in the current process. + pub userspace_addr: u64, + /// Offset where region starts in the mapped memory. + pub mmap_offset: u64, +} + +/// The header for the message of memory table +pub struct VhostUserMemoryHdr { + /// Number of memory regions in the payload. + pub nregion: u32, + /// Padding for alignment. + pub padding: u32, +} + +impl VhostUserMemoryHdr { + pub fn new(nregion: u32, padding: u32) -> Self { + VhostUserMemoryHdr { nregion, padding } + } +} + +/// The context for the message of memory table +pub struct VhostUserMemoryContext { + /// The vector of memory region information + pub regions: Vec, +} + +impl VhostUserMemoryContext { + fn new() -> Self { + VhostUserMemoryContext { + regions: Vec::new(), + } + } + + pub fn region_add(&mut self, region: VhostUserMemoryRegion) { + self.regions.push(region); + } +} + +impl Default for VhostUserMemoryContext { + fn default() -> Self { + Self::new() + } +} + +/// The configuraton for the state of virtual ring +pub struct VhostUserVringState { + /// Index for virtual ring + pub index: u32, + /// A common 32bit value to encapsulate vring state etc. + pub value: u32, +} + +impl VhostUserVringState { + pub fn new(index: u32, value: u32) -> Self { + VhostUserVringState { index, value } + } +} + +///The configuration for the address of virtual ring +pub struct VhostUserVringAddr { + /// Index for virtual ring + pub index: u32, + /// The option for virtual ring + pub flags: u32, + /// Address of the descriptor table. + pub desc_user_addr: u64, + /// Address of the used ring. + pub used_user_addr: u64, + /// Address of the available ring. + pub avail_user_addr: u64, + /// Guest address for logging. + pub log_guest_addr: u64, +} diff --git a/device_model/src/virtio/vhost/user/vhost_user_sock.rs b/device_model/src/virtio/vhost/user/vhost_user_sock.rs new file mode 100644 index 0000000000000000000000000000000000000000..22cb617b9cb154e8e60f8d91df90c60c2c8e780e --- /dev/null +++ b/device_model/src/virtio/vhost/user/vhost_user_sock.rs @@ -0,0 +1,383 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use libc::{ + c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, MSG_WAITALL, + SCM_RIGHTS, SOL_SOCKET, +}; +use std::mem::size_of; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned}; + +use super::super::super::errors::{Result, ResultExt}; +use vmm_sys_util::errno::Error; + +pub const VHOST_USER_MSG_MAX_SIZE: usize = 0x1000; +pub const MAX_ATTACHED_FD_ENTRIES: usize = 32; + +#[cfg(not(target_env = "musl"))] +macro_rules! CMSG_ALIGN { + ($len:expr) => { + (($len) + size_of::() - 1) & !(size_of::() - 1) + }; +} + +#[cfg(target_env = "musl")] +macro_rules! CMSG_ALIGN { + ($len:expr) => { + (($len) as usize + size_of::() - 1) & !(size_of::() - 1) + }; +} + +macro_rules! CMSG_SPACE { + ($len:expr) => { + CMSG_ALIGN!(size_of::()) + CMSG_ALIGN!($len) + }; +} + +macro_rules! CMSG_LEN { + ($len:expr) => { + CMSG_ALIGN!(size_of::()) + ($len) + }; +} + +#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] +fn cmsg_data(cmsg_buffer: *mut cmsghdr) -> *mut RawFd { + (cmsg_buffer as *mut u8).wrapping_add(CMSG_LEN!(0)) as *mut RawFd +} + +#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] +fn get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr { + let next_cmsg = (cmsg_ptr as *mut u8).wrapping_add(CMSG_ALIGN!(cmsg.cmsg_len)) as *mut cmsghdr; + let nex_cmsg_pos = (next_cmsg as *mut u8).wrapping_sub(msghdr.msg_control as usize) as usize; + + if nex_cmsg_pos.wrapping_add(CMSG_LEN!(0)) > msghdr.msg_controllen as usize { + null_mut() + } else { + next_cmsg + } +} + +pub struct UnixDomainSock { + path: String, + listener: Option, + sock: Option, +} + +impl Clone for UnixDomainSock { + fn clone(&self) -> Self { + let listener = match &self.listener { + Some(listener_) => Some(listener_.try_clone().unwrap()), + None => None, + }; + + let sock = match &self.sock { + Some(sock_) => Some(sock_.try_clone().unwrap()), + None => None, + }; + + UnixDomainSock { + path: self.path.clone(), + listener, + sock, + } + } +} + +impl UnixDomainSock { + pub fn new(path: &str) -> Self { + UnixDomainSock { + path: path.to_string(), + listener: None, + sock: None, + } + } + + #[allow(dead_code)] + pub fn server_bind(&mut self, unlink: bool) -> Result<()> { + if unlink { + let _ = std::fs::remove_file(self.path.as_str()); + } + + let listener = UnixListener::bind(self.path.as_str()) + .chain_err(|| format!("Failed to bind the socket {}", self.path))?; + + self.listener = Some(listener); + Ok(()) + } + + #[allow(dead_code)] + pub fn server_accept(&mut self) -> Result<()> { + let (sock, _addr) = self + .listener + .as_ref() + .unwrap() + .accept() + .chain_err(|| format!("Failed to accept the socket {}", self.path))?; + + self.sock = Some(sock); + Ok(()) + } + + #[allow(dead_code)] + pub fn is_server_accept(&self) -> bool { + self.sock.is_some() + } + + #[allow(dead_code)] + pub fn server_connection_refuse(&mut self) -> Result<()> { + //refuse connection by finishing life cycle of stream fd from listener fd + self.listener.as_ref().unwrap().accept().chain_err(|| { + format!( + "Failed to accept the socket for refused connection {}", + self.path + ) + })?; + + Ok(()) + } + + pub fn client_connect(&mut self) -> Result<()> { + let sock = UnixStream::connect(self.path.as_str()) + .chain_err(|| format!("Failed to connect the socket {}", self.path))?; + + self.sock = Some(sock); + Ok(()) + } + + #[allow(dead_code)] + pub fn get_stream_raw_fd(&self) -> RawFd { + self.sock.as_ref().unwrap().as_raw_fd() + } + + #[allow(dead_code)] + pub fn get_listener_raw_fd(&self) -> RawFd { + self.listener.as_ref().unwrap().as_raw_fd() + } + + fn sendmsg(&self, iovecs: &mut [iovec], out_fds: &[RawFd]) -> Result { + #[cfg(not(target_env = "musl"))] + let cmsg_capacity = CMSG_SPACE!(size_of::() * out_fds.len()); + #[cfg(target_env = "musl")] + let cmsg_capacity = CMSG_SPACE!(size_of::() * out_fds.len()) as u32; + #[cfg(not(target_env = "musl"))] + let iovecs_len = iovecs.len(); + #[cfg(target_env = "musl")] + let iovecs_len = iovecs.len() as i32; + #[cfg(not(target_env = "musl"))] + let cmsg_len = CMSG_LEN!(size_of::() * out_fds.len()); + #[cfg(target_env = "musl")] + let cmsg_len = CMSG_LEN!(size_of::() * out_fds.len()) as u32; + + let mut cmsg_buffer = vec![0u64; cmsg_capacity as usize]; + + let mut msg: msghdr = unsafe { std::mem::zeroed() }; + msg.msg_name = null_mut(); + msg.msg_namelen = 0; + msg.msg_iov = iovecs.as_mut_ptr(); + msg.msg_iovlen = iovecs_len; + msg.msg_control = null_mut(); + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if !out_fds.is_empty() { + let cmsg = cmsghdr { + cmsg_len, + #[cfg(target_env = "musl")] + __pad1: 0, + cmsg_level: SOL_SOCKET, + cmsg_type: SCM_RIGHTS, + }; + unsafe { + write_unaligned(cmsg_buffer.as_mut_ptr() as *mut cmsghdr, cmsg); + + copy_nonoverlapping( + out_fds.as_ptr(), + cmsg_data(cmsg_buffer.as_mut_ptr() as *mut cmsghdr), + out_fds.len(), + ); + } + + msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void; + msg.msg_controllen = cmsg_capacity; + } + + let write_count = + unsafe { sendmsg(self.sock.as_ref().unwrap().as_raw_fd(), &msg, MSG_NOSIGNAL) }; + + if write_count == -1 { + bail!("Failed to send msg, err: {}", Error::last()); + } else { + Ok(write_count as usize) + } + } + + fn recvmsg(&self, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<(usize, usize)> { + #[cfg(not(target_env = "musl"))] + let cmsg_capacity = CMSG_SPACE!(size_of::() * in_fds.len()); + #[cfg(target_env = "musl")] + let cmsg_capacity = CMSG_SPACE!(size_of::() * in_fds.len()) as u32; + #[cfg(not(target_env = "musl"))] + let iovecs_len = iovecs.len(); + #[cfg(target_env = "musl")] + let iovecs_len = iovecs.len() as i32; + + let mut cmsg_buffer = vec![0u64; cmsg_capacity as usize]; + + let mut msg: msghdr = unsafe { std::mem::zeroed() }; + msg.msg_name = null_mut(); + msg.msg_namelen = 0; + msg.msg_iov = iovecs.as_mut_ptr(); + msg.msg_iovlen = iovecs_len; + msg.msg_control = null_mut(); + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if !in_fds.is_empty() { + msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void; + msg.msg_controllen = cmsg_capacity; + } + + let total_read = unsafe { + recvmsg( + self.sock.as_ref().unwrap().as_raw_fd(), + &mut msg, + MSG_WAITALL, + ) + }; + + if total_read == -1 { + bail!("Failed to recv msg, err: {}", Error::last()); + } + + if total_read == 0 && (msg.msg_controllen as usize) < size_of::() { + bail!( + "The length of control message is invalid, {} {}", + msg.msg_controllen, + size_of::() + ); + } + + let mut cmsg_ptr = msg.msg_control as *mut cmsghdr; + let mut in_fds_count = 0usize; + while !cmsg_ptr.is_null() { + let cmsg = unsafe { (cmsg_ptr as *mut cmsghdr).read_unaligned() }; + + if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS { + let fd_count = (cmsg.cmsg_len as usize - CMSG_LEN!(0)) / size_of::(); + unsafe { + copy_nonoverlapping( + cmsg_data(cmsg_ptr), + in_fds[in_fds_count..(in_fds_count + fd_count)].as_mut_ptr(), + fd_count, + ); + } + in_fds_count += fd_count; + } + + cmsg_ptr = get_next_cmsg(&msg, &cmsg, cmsg_ptr); + } + + Ok((total_read as usize, in_fds_count as usize)) + } + + pub fn vhostuser_msgsend( + &self, + hdr_opt: Option<&D>, + body_opt: Option<&T>, + payload_opt: Option<&[P]>, + fds: &[RawFd], + ) -> Result<()> { + let mut iovs = Vec::with_capacity(3); + let mut total_len = size_of::(); + + if let Some(hdr) = hdr_opt { + iovs.push(iovec { + iov_base: hdr as *const D as *const u8 as *mut c_void, + iov_len: size_of::(), + }); + } + + if let Some(body) = body_opt { + iovs.push(iovec { + iov_base: body as *const T as *const u8 as *mut c_void, + iov_len: size_of::(), + }); + total_len += size_of::(); + } + + if let Some(payload) = payload_opt { + iovs.push(iovec { + iov_base: payload.as_ptr() as *const u8 as *mut c_void, + iov_len: payload.len() * size_of::

(), + }); + total_len += payload.len() * size_of::

(); + } + + if (total_len - size_of::()) > VHOST_USER_MSG_MAX_SIZE { + bail!( + "The total length is invaild {}", + (total_len - size_of::()) + ); + } + + if fds.len() > MAX_ATTACHED_FD_ENTRIES { + bail!("The number of fds is invaild {}", fds.len()); + } + + let snd_len = self.sendmsg(&mut iovs, fds)?; + if snd_len != total_len { + bail!( + "The actual sending length {} is less than the sending length {}", + snd_len, + total_len + ); + } + Ok(()) + } + + pub fn vhostuser_msgrecv( + &self, + hdr_opt: Option<&mut D>, + body_opt: Option<&mut T>, + payload_opt: Option<&mut [P]>, + fds: &mut [RawFd], + ) -> Result<(usize, usize)> { + let mut iovs = Vec::with_capacity(3); + + if let Some(hdr) = hdr_opt { + iovs.push(iovec { + iov_base: hdr as *const D as *const u8 as *mut c_void, + iov_len: size_of::(), + }); + } + + if let Some(body) = body_opt { + iovs.push(iovec { + iov_base: body as *const T as *const u8 as *mut c_void, + iov_len: size_of::(), + }); + } + + if let Some(payload) = payload_opt { + iovs.push(iovec { + iov_base: payload.as_ptr() as *const u8 as *mut c_void, + iov_len: payload.len() * size_of::

(), + }); + } + + let (rcv_len, fds_num) = self.recvmsg(&mut iovs, fds)?; + + Ok((rcv_len, fds_num)) + } +} diff --git a/docs/config_guidebook.md b/docs/config_guidebook.md index a127e4e607e1087826015a63e1584ed2eb645492..6b60571da4da1171de15c7f53e760ed054e19d22 100644 --- a/docs/config_guidebook.md +++ b/docs/config_guidebook.md @@ -411,6 +411,43 @@ This feature can prevent OOM occur in guest. } ``` +### 2.8 Virtio-fs +Virtio-fs is a shared file system for VM, it can access a directory tree on the host. + +Two properties are supported for virtio fs device in StratoVirt. +* tag: the alias of virtio fs device +* sock: the path of socket file in the host which can communicate with the process that implements userspace filesystem + +```shell +# cmdline +-fs tag=fs_tag,sock=path_on_host +# json +{ + "fs": [ + { + "tag": "fs_tag", + "sock": "/path/to/socket/path" + } + ], + ... +} +``` + +Two properties are supported for the process of vhost_user_fs that implements userspace filesystem +* sock: the path of socket file in the host that can communicate with StratoVirt +* dir: the directory in host + +$ ./vhost_user_fs -sock /path/to/socket/path -dir /path/to/host/directory + +# In host +Firstly, start the process of vhost_user_fs like the example above + +Secondly, start the process of StratoVirt with the cmdline to configure virtio fs device + +# In guest +Finally, mount the filesystem to the directory in guest + +$ mount -t virtiofs fs_tag /path/to/guest/directory ## 3. StratoVirt Management diff --git a/machine_manager/src/cmdline.rs b/machine_manager/src/cmdline.rs index a80bfc7c927d45002c87eb8f21d7ad0a2bfa46d5..fe6d272c2253e9a37cbe604338e46f925e6c14e0 100644 --- a/machine_manager/src/cmdline.rs +++ b/machine_manager/src/cmdline.rs @@ -157,6 +157,14 @@ pub fn create_args_parser<'a>() -> ArgParser<'a> { .help("use 'file' as a drive image") .takes_values(true), ) + .arg( + Arg::with_name("fs") + .multiple(true) + .long("fs") + .value_name("fs[,tag=str][,sock=socket_path]") + .help("config a virtio fs with tag 'str'") + .takes_values(true), + ) .arg( Arg::with_name("netdev") .multiple(true) @@ -304,6 +312,7 @@ pub fn create_vmconfig(args: &ArgMatches) -> Result { update_args_to_config_multi!((args.values_of("netdev")), vm_cfg, update_net); update_args_to_config_multi!((args.values_of("chardev")), vm_cfg, update_console); update_args_to_config_multi!((args.values_of("iothread")), vm_cfg, update_iothread); + update_args_to_config_multi!((args.values_of("fs")), vm_cfg, update_fs); // Check the mini-set for Vm to start is ok vm_cfg diff --git a/machine_manager/src/config/fs.rs b/machine_manager/src/config/fs.rs index 4466fbfb1eafe339e8838de1b7b01db145c55c19..a769ae3eba7162d6c0fb5c7d95cc277cdc29c185 100644 --- a/machine_manager/src/config/fs.rs +++ b/machine_manager/src/config/fs.rs @@ -22,6 +22,7 @@ const MAX_STRING_LENGTH: usize = 255; const MAX_PATH_LENGTH: usize = 4096; const MAX_SERIAL_NUM: usize = 20; const MAX_IOPS: u64 = 1_000_000; +pub const MAX_TAG_LENGTH: usize = 36; /// Config struct for `drive`. /// Contains block device's attr. @@ -170,6 +171,101 @@ impl VmConfig { } } +/// Config struct for `fs`. +/// Contains fs device's attr. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FsConfig { + pub tag: String, + pub sock: String, +} + +impl FsConfig { + /// Create `FsConfig` from `Value` structure. + /// + /// # Arguments + /// + /// * `Value` - structure can be gotten by `json_file`. + pub fn from_value(value: &serde_json::Value) -> Result> { + let ret = serde_json::from_value(value.clone())?; + Ok(ret) + } +} + +impl Default for FsConfig { + fn default() -> Self { + FsConfig { + tag: "".to_string(), + sock: "".to_string(), + } + } +} + +impl ConfigCheck for FsConfig { + fn check(&self) -> Result<()> { + if self.tag.len() >= MAX_TAG_LENGTH { + return Err(ErrorKind::StringLengthTooLong( + "fs device tag".to_string(), + MAX_TAG_LENGTH - 1, + ) + .into()); + } + + if self.sock.len() > MAX_PATH_LENGTH { + return Err(ErrorKind::StringLengthTooLong( + "fs sock path".to_string(), + MAX_PATH_LENGTH, + ) + .into()); + } + + Ok(()) + } +} + +impl VmConfig { + /// Add new fs device to `VmConfig`. + fn add_fs(&mut self, fs_cfg: FsConfig) -> Result<()> { + if let Some(mut fs) = self.fs.clone() { + for exist_cfg in &fs { + if exist_cfg.tag == fs_cfg.tag { + bail!("The tag {} for virtio fs is existed", exist_cfg.tag); + } + + if exist_cfg.sock == fs_cfg.sock { + bail!("The sock {} for virtio fs is existed", exist_cfg.tag); + } + } + fs.push(fs_cfg); + self.fs = Some(fs); + } else { + let mut fs: Vec = Vec::new(); + fs.push(fs_cfg); + self.fs = Some(fs); + } + + Ok(()) + } + + /// Update '-fs ...' fs config to `VmConfig`. + pub fn update_fs(&mut self, fs_config: &str) -> Result<()> { + let mut cmd_parser = CmdParser::new("fs"); + cmd_parser.push("tag").push("sock"); + + cmd_parser.parse(fs_config)?; + + let mut fs_cfg = FsConfig::default(); + if let Some(tag) = cmd_parser.get_value::("tag")? { + fs_cfg.tag = tag; + } + if let Some(sock) = cmd_parser.get_value::("sock")? { + fs_cfg.sock = sock; + } + + self.add_fs(fs_cfg) + } +} + #[cfg(test)] mod tests { use super::*; @@ -228,4 +324,32 @@ mod tests { drive_configs[0].serial_num = Some(String::from("222222222222222222222")); assert!(drive_configs[0].check().is_err()); } + + #[test] + fn test_fs_config_json_parser() { + let json = r#" + [{ + "tag": "test", + "sock": "fs.sock" + }] + "#; + let value = serde_json::from_str(json).unwrap(); + let configs = FsConfig::from_value(&value); + assert!(configs.is_ok()); + let fs_configs = configs.unwrap(); + assert_eq!(fs_configs[0].tag, "test"); + assert_eq!(fs_configs[0].sock, "fs.sock"); + } + + #[test] + fn test_fs_config_cmdline_parser() { + let mut vm_config = VmConfig::default(); + assert!(vm_config.update_fs("tag=test,sock=fs.sock").is_ok()); + let configs = vm_config.fs.clone(); + assert!(configs.is_some()); + let fs_configs = configs.unwrap(); + assert_eq!(fs_configs[0].tag, "test"); + assert_eq!(fs_configs[0].sock, "fs.sock"); + assert!(fs_configs[0].check().is_ok()); + } } diff --git a/machine_manager/src/config/mod.rs b/machine_manager/src/config/mod.rs index 7d940c66b2c0f7e323c4c81c0c2ec878308f6ed6..0ed873769a5da849c102414a79f0d9e497b75c6b 100644 --- a/machine_manager/src/config/mod.rs +++ b/machine_manager/src/config/mod.rs @@ -109,6 +109,7 @@ pub struct VmConfig { pub drives: Option>, pub nets: Option>, pub consoles: Option>, + pub fs: Option>, pub vsock: Option, pub serial: Option, pub iothreads: Option>, @@ -127,6 +128,7 @@ impl VmConfig { let mut drives = None; let mut nets = None; let mut consoles = None; + let mut fs = None; let mut vsock = None; let mut serial = None; let mut iothreads = None; @@ -140,6 +142,7 @@ impl VmConfig { "drive" => drives = Some(DriveConfig::from_value(&item)?), "net" => nets = Some(NetworkInterfaceConfig::from_value(&item)?), "console" => consoles = Some(ConsoleConfig::from_value(&item)?), + "fs" => fs = Some(FsConfig::from_value(&item)?), "vsock" => vsock = Some(VsockConfig::from_value(&item)?), "serial" => serial = Some(SerialConfig::from_value(&item)?), "iothread" => iothreads = Some(IothreadConfig::from_value(&item)?), @@ -156,6 +159,7 @@ impl VmConfig { drives, nets, consoles, + fs, vsock, serial, iothreads, @@ -194,6 +198,12 @@ impl VmConfig { } } + if self.fs.is_some() { + for fs_cfg in self.fs.as_ref().unwrap() { + fs_cfg.check()?; + } + } + if self.vsock.is_some() { self.vsock.as_ref().unwrap().check()?; } diff --git a/src/vhost_user_fs.rs b/src/vhost_user_fs.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a85e88f9128128a957b2776dffe9bf0fd5c7656 --- /dev/null +++ b/src/vhost_user_fs.rs @@ -0,0 +1,107 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate log; +extern crate vhost_user_fs; + +use std::os::unix::fs::OpenOptionsExt; +use std::sync::Arc; + +use machine_manager::event_loop::EventLoop; +use util::{arg_parser, logger}; +use vhost_user_fs::cmdline::{create_args_parser, create_fs_config, FsConfig}; +use vhost_user_fs::vhost_user_fs::VhostUserFs; + +error_chain! { + links { + VhostUserFs(vhost_user_fs::errors::Error, vhost_user_fs::errors::ErrorKind); + Util(util::errors::Error, util::errors::ErrorKind); + } + foreign_links { + Io(std::io::Error); + } +} + +quick_main!(run); + +fn run() -> Result<()> { + let cmd_args = create_args_parser().get_matches()?; + + if let Some(logfile_path) = cmd_args.value_of("display log") { + init_log(logfile_path)?; + } + + set_panic_hook(); + + match real_main(&cmd_args) { + Ok(()) => info!("EventLoop over, Vm exit"), + Err(ref e) => { + error!("{}", error_chain::ChainedError::display_chain(e)); + } + } + + Ok(()) +} + +fn real_main(cmd_args: &arg_parser::ArgMatches) -> Result<()> { + let fsconfig: FsConfig = create_fs_config(cmd_args)?; + info!("FsConfig is {:?}", fsconfig); + + EventLoop::object_init(&None)?; + + let vhost_user_fs = + Arc::new(VhostUserFs::new(fsconfig).chain_err(|| "Failed to create vhost use fs")?); + EventLoop::set_manager(vhost_user_fs.clone(), None); + + vhost_user_fs + .add_event_notifier() + .chain_err(|| "Failed to add event")?; + + EventLoop::loop_run().chain_err(|| "EventLoop exits unexpectedly: error occurs")?; + + Ok(()) +} + +fn init_log(logfile_path: String) -> Result<()> { + if logfile_path.is_empty() { + logger::init_logger_with_env(Some(Box::new(std::io::stdout()))) + .chain_err(|| "Failed to init logger")?; + } else { + let logfile = std::fs::OpenOptions::new() + .read(false) + .write(true) + .append(true) + .create(true) + .mode(0o640) + .open(logfile_path.clone()) + .chain_err(|| format!("Failed to open log file {}", logfile_path))?; + logger::init_logger_with_env(Some(Box::new(logfile))) + .chain_err(|| format!("Failed to init logger {}", logfile_path))?; + } + + Ok(()) +} + +fn set_panic_hook() { + std::panic::set_hook(Box::new(|panic_msg| { + let panic_file = panic_msg.location().map_or("", |loc| loc.file()); + let panic_line = panic_msg.location().map_or(0, |loc| loc.line()); + if let Some(msg) = panic_msg.payload().downcast_ref::<&str>() { + error!("Panic at [{}: {}]: {}.", panic_file, panic_line, msg); + } else { + error!("Panic at [{}: {}].", panic_file, panic_line); + } + })); +} diff --git a/vhost_user_fs/Cargo.toml b/vhost_user_fs/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..db2a88daf81f9f70ffd1fb712f5ee9457d78e0e0 --- /dev/null +++ b/vhost_user_fs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "vhost_user_fs" +version = "0.1.0" +authors = ["Huawei StratoVirt Team"] +license = "Mulan PSL v2" +description = "provide virtio fs for VM" + +[dependencies] +errno = "0.2.7" +log = "0.4.8" +libc = "0.2.71" +error-chain = "0.12.4" +vmm-sys-util = "0.6.1" + +util = { path = "../util" } +device_model = { path = "../device_model" } +machine_manager = { path = "../machine_manager" } +address_space = { path = "../address_space" } diff --git a/vhost_user_fs/src/cmdline.rs b/vhost_user_fs/src/cmdline.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf4998b23abce0b5cd64142aaa9c291af9921a5b --- /dev/null +++ b/vhost_user_fs/src/cmdline.rs @@ -0,0 +1,129 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::path::PathBuf; + +use util::arg_parser::{Arg, ArgMatches, ArgParser}; + +use crate::errors::{Result, ResultExt}; + +// Read the programe version in `Cargo.toml`. +const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); + +const MAX_STRING_LENGTH: usize = 255; + +/// This function is to define all command line arguments. +pub fn create_args_parser<'a>() -> ArgParser<'a> { + ArgParser::new("VhostUserFs") + .version(VERSION.unwrap_or("unknown")) + .author("Huawei Technologies Co., Ltd") + .about("The process of Virtio fs for StratoVirt.") + .arg( + Arg::with_name("source dir") + .long("dir") + .value_name("source directory in host") + .help("set source directory in host") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("sock") + .long("sock") + .value_name("sock file path which communicates with StratoVirt") + .help("sock file path which communicates with StratoVirt") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("rlimit nofile") + .long("rlimit-nofile") + .value_name("file resource limits for the process") + .help("set file resource limits for the process") + .takes_value(true), + ) + .arg( + Arg::with_name("display log") + .long("D") + .value_name("log_path") + .help("output log to logfile") + .takes_value(true) + .can_no_value(true), + ) +} + +/// Filesystem configuration parsed from command line for the process +#[derive(Clone, Default, Debug)] +pub struct FsConfig { + /// Source directory in host which can be accessed by guest + pub source_dir: String, + /// The path of socket file which communicates with StratoVirt + pub sock_path: String, + /// The limit of file resources which can be opened for the process + pub rlimit_nofile: Option, +} + +impl FsConfig { + fn check_config(&self) -> Result<()> { + if self.source_dir.len() > MAX_STRING_LENGTH { + bail!( + "The length of source directory is too long {}", + self.source_dir.len() + ); + } + + if self.sock_path.len() > MAX_STRING_LENGTH { + bail!( + "The length of socket file path is too long {}", + self.sock_path.len() + ); + } + + let source_dir = PathBuf::from(&self.source_dir); + if !source_dir.is_dir() { + bail!( + "The source directory is not a directory {}", + self.source_dir + ); + } + + Ok(()) + } +} + +/// Construct a filesystem configuration parsed from command line. +/// +/// # Arguments +/// * `args` - The collection of information about the arguments from command line. +pub fn create_fs_config(args: &ArgMatches) -> Result { + let mut fs_config = FsConfig::default(); + + if let Some(source_dir) = args.value_of("source dir") { + fs_config.source_dir = source_dir; + } + + if let Some(sock_path) = args.value_of("sock") { + fs_config.sock_path = sock_path; + } + + if let Some(rlimit_nofile) = args.value_of("rlimit nofile") { + let limit = rlimit_nofile + .parse::() + .chain_err(|| "Failed to parse rlimit nofile")?; + fs_config.rlimit_nofile = Some(limit); + } + + fs_config + .check_config() + .chain_err(|| "Precheck failed, Config is unhealthy, stop running")?; + + Ok(fs_config) +} diff --git a/vhost_user_fs/src/fs.rs b/vhost_user_fs/src/fs.rs new file mode 100644 index 0000000000000000000000000000000000000000..418b7d71ecbdf233f841b35b219f21e2196f70dd --- /dev/null +++ b/vhost_user_fs/src/fs.rs @@ -0,0 +1,1939 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::collections::{BTreeMap, HashMap}; +use std::ffi::CString; +use std::fs::{read_to_string, File}; +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; + +use util::byte_code::ByteCode; +use util::num_ops::round_up; + +use super::fs_ops::*; +use super::fuse_msg::*; + +use crate::errors::{Result, ResultExt}; + +const MAP_EXTEND_LENGTH: usize = 256; + +const F_RDLCK: u32 = 0; +const F_WDLCK: u32 = 1; +const F_UNLCK: u32 = 2; + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] +struct StatKey { + ino: libc::ino64_t, + dev: libc::dev_t, +} + +struct Entry { + value: Option, + used: bool, + free_next: usize, +} + +struct Map { + list: Vec>, + free_head: usize, +} + +impl Map { + fn new() -> Self { + Map { + list: Vec::new(), + free_head: 1, + } + } + + fn destroy_map(&mut self) { + self.list = Vec::new(); + self.free_head = 1; + } + + fn extend_map(&mut self) { + let mut next = self.list.len(); + + for _ in 0..MAP_EXTEND_LENGTH { + next += 1; + self.list.push(Entry { + value: None, + used: false, + free_next: next, + }); + } + } + + fn get_map(&mut self, value: T) -> usize { + let id = self.free_head; + if id == 1 || id == self.list.len() { + self.extend_map(); + } + + match self.list.get_mut(id) { + Some(e) => { + e.value = Some(value); + e.used = true; + self.free_head = e.free_next; + + id + } + None => 0, + } + } + + fn put_map(&mut self, id: usize) { + if id >= self.list.len() { + return; + } + + if let Some(e) = self.list.get_mut(id) { + if !e.used { + return; + } + + e.value = None; + e.used = false; + e.free_next = self.free_head; + self.free_head = id + } + } + + fn get_value(&self, id: usize) -> Option<&T> { + if let Some(e) = self.list.get(id) { + e.value.as_ref().map(|v| v) + } else { + None + } + } + + fn get_value_mut(&mut self, id: usize) -> Option<&mut T> { + if let Some(e) = self.list.get_mut(id) { + e.value.as_mut().map(|v| v) + } else { + None + } + } +} + +struct FileLock { + lock_owner: u64, + file: File, +} + +impl FileLock { + fn new(file: File, lock_owner: u64) -> Self { + FileLock { lock_owner, file } + } +} + +impl Clone for FileLock { + fn clone(&self) -> Self { + FileLock { + lock_owner: self.lock_owner, + file: self.file.try_clone().unwrap(), + } + } +} + +struct Inode { + file: File, + nlookup: u64, + node_id: usize, + file_type: u32, + key: StatKey, + locks: HashMap, +} + +impl Inode { + fn new(file: File, nlookup: u64, node_id: usize, file_type: u32, key: StatKey) -> Self { + Inode { + file, + nlookup, + node_id, + file_type, + key, + locks: HashMap::new(), + } + } + + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } +} + +impl Clone for Inode { + fn clone(&self) -> Self { + Inode { + file: self.file.try_clone().unwrap(), + nlookup: self.nlookup, + node_id: self.node_id, + file_type: self.file_type, + key: self.key, + locks: self.locks.clone(), + } + } +} + +fn array_to_cstring( + #[cfg(target_arch = "x86_64")] array: &[i8], + #[cfg(target_arch = "aarch64")] array: &[u8], +) -> Result<(usize, CString)> { + let mut vec = Vec::new(); + for item in array { + if *item == 0 { + break; + } + vec.push(*item as u8); + } + + let len = vec.len(); + if len == 0 { + bail!("convert array to CString failed") + } + + let cstring = match CString::new(vec) { + Ok(c) => c, + Err(_) => bail!("convert array to CString failed"), + }; + + Ok((len, cstring)) +} + +fn path_is_dot(path: &CString) -> bool { + let bytes = path.as_bytes(); + if bytes.len() == 1 && bytes[0] == b'.' { + return true; + } + + false +} + +fn path_is_dotdot(path: &CString) -> bool { + let bytes = path.as_bytes(); + if bytes.len() == 2 && bytes[0] == b'.' && bytes[1] == b'.' { + return true; + } + + false +} + +const RLIMIT_NOFILE_MIN: u64 = 20; +/// Set file resources limits for the process. The limit value +/// must be more than or equal to 20 to ensure that the process +/// can start normally. The limit value must be less than or equal +/// to the value of "/proc/sys/fs/file-max" and "/proc/sys/fs/nr_open". +/// +/// # Arguments +/// +/// * `limit` - The limit value which needs to be set. +pub fn set_rlimit_nofile(limit: u64) -> Result<()> { + if limit < RLIMIT_NOFILE_MIN { + bail!( + "The limit {} exceeds minimum of files {}", + limit, + RLIMIT_NOFILE_MIN + ); + } + + let max_file_str = + read_to_string("/proc/sys/fs/file-max").chain_err(|| "Failed to read file-max")?; + let max_file = max_file_str + .trim() + .parse::() + .chain_err(|| "Failed to convert the string of max files")?; + if limit > max_file { + bail!("The limit {} exceeds maximum of files {}", limit, max_file); + } + + let nr_open_str = + read_to_string("/proc/sys/fs/nr_open").chain_err(|| "Failed to read nr_open")?; + let max_file = nr_open_str + .trim() + .parse::() + .chain_err(|| "Failed to convert the string of nr_open")?; + if limit > max_file { + bail!( + "The limit {} exceeds maximum of nr_open {}", + limit, + max_file + ); + } + + let ret = set_rlimit(limit, limit); + if ret != FUSE_OK { + bail!("Failed to set rlimit, err: {}", ret); + } + + Ok(()) +} + +/// The management structure of filesystem that contains the management of inodes +/// and the information of files in host directory which needs to be shared. +pub struct FileSystem { + root_inode: Inode, + inodes: BTreeMap, + inode_key_map: Map, + file_map: Map, + proc_dir: File, +} + +impl FileSystem { + /// Create a filesystem management structure. + /// + /// # Arguments + /// + /// * `source_dir` - The path of the host directory which needs to be shared. + pub fn new(source_dir: &str) -> Result { + let (root_file_opt, ret) = open(CString::new(source_dir).unwrap(), libc::O_PATH); + if ret != FUSE_OK { + bail!("Failed to open root file {}", source_dir); + } + let root_file = root_file_opt.unwrap(); + let (stat, ret) = fstat_at( + &root_file, + CString::new("").unwrap(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + bail!("Failed to get stat of root file {}", source_dir); + } + let key = StatKey { + ino: stat.st_ino, + dev: stat.st_dev, + }; + let mut inode_key_map = Map::new(); + let root_id = inode_key_map.get_map(key); + let root_inode = Inode::new(root_file, 2, root_id, libc::S_IFDIR, key); + let mut inodes = BTreeMap::new(); + inodes.insert(key, root_inode.clone()); + let (proc_dir_opt, ret) = open(CString::new("/proc/self/fd").unwrap(), libc::O_PATH); + if ret != FUSE_OK { + bail!("Failed to open proc dir"); + } + Ok(FileSystem { + root_inode, + inodes, + inode_key_map, + file_map: Map::new(), + proc_dir: proc_dir_opt.unwrap(), + }) + } + + fn find_inode(&self, node_id: usize) -> Option<&Inode> { + match self.inode_key_map.get_value(node_id) { + Some(k) => self.inodes.get(k), + _ => None, + } + } + + fn find_mut_inode(&mut self, node_id: usize) -> Option<&mut Inode> { + match self.inode_key_map.get_value(node_id) { + Some(k) => self.inodes.get_mut(k), + _ => None, + } + } + + fn unref_inode(&mut self, inode: &mut Inode, count: u64) { + if count > inode.nlookup { + inode.nlookup = 0; + } else { + inode.nlookup -= count; + } + + if inode.nlookup == 0 { + self.inodes.remove(&inode.key); + self.inode_key_map.put_map(inode.node_id); + } else if let Some(inode_) = self.find_mut_inode(inode.node_id) { + inode_.nlookup = inode.nlookup; + } + } + + fn create_file_lock(&mut self, node_id: usize, owner: u64) -> (Option, i32) { + let proc_file = self.proc_dir.try_clone().unwrap(); + let inode = match self.find_mut_inode(node_id) { + Some(inode_) => inode_, + None => return (None, libc::EBADF as i32), + }; + + if let Some(lock) = inode.locks.get_mut(&owner) { + return (Some(lock.file.try_clone().unwrap()), FUSE_OK); + } + + if inode.file_type & libc::S_IFDIR == 0 && inode.file_type & libc::S_IFREG == 0 { + return (None, libc::EBADF as i32); + } + + let (file_opt, ret) = open_at( + &proc_file, + CString::new(format!("{}", inode.as_raw_fd())).unwrap(), + libc::O_RDWR, + 0, + ); + + if ret != FUSE_OK { + return (None, ret); + } + + let file = file_opt.unwrap().try_clone().unwrap(); + let file_lock = FileLock::new(file.try_clone().unwrap(), owner); + inode.locks.insert(owner, file_lock); + + (Some(file), FUSE_OK) + } + + fn delete_file_lock(&mut self, node_id: usize, owner: u64) -> i32 { + let inode = match self.find_mut_inode(node_id) { + Some(inode_) => inode_, + None => return libc::EBADF as i32, + }; + + inode.locks.remove(&owner); + + FUSE_OK + } + + fn internal_lookup( + &mut self, + parent_inode: &Inode, + name: CString, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let mut son_name = name.clone(); + if parent_inode.node_id == self.root_inode.node_id && path_is_dotdot(&name) { + son_name = CString::new(".").unwrap(); + } + + let (stat, ret) = fstat_at( + &parent_inode.file, + son_name.clone(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + + let key = StatKey { + ino: stat.st_ino, + dev: stat.st_dev, + }; + if let Some(inode) = self.inodes.get_mut(&key) { + inode.nlookup += 1; + *node_id = inode.node_id as u64; + } else { + let (file_opt, ret) = open_at( + &parent_inode.file, + son_name, + libc::O_PATH | libc::O_NOFOLLOW, + 0, + ); + if ret != FUSE_OK { + return ret; + } + + let map_id = self.inode_key_map.get_map(key); + if let Some(file) = file_opt { + self.inodes.insert( + key, + Inode::new(file, 1, map_id, stat.st_mode & libc::S_IFMT, key), + ); + } + *node_id = map_id as u64; + }; + + *fuse_attr = FuseAttr::from_stat(stat); + + FUSE_OK + } + + /// Look up the directory or file information by name and reply attributes. + /// + /// # Arguments + /// + /// * `parent_nodeid` - The parent node id that is the starting directory to look up. + /// * `name` - The name that needs to be looked up. + /// * `node_id` - The node id that needs to be looked up by name. + /// * `fuse_attr` - The attributes that needs to be looked up by name. + pub fn lookup( + &mut self, + parent_nodeid: usize, + name: CString, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let inode = match self.find_inode(parent_nodeid) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + self.internal_lookup(&inode, name, node_id, fuse_attr) + } + + /// When the nlookup of inode is reduced to 0, delete the inode from the management structure. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to find the inode. + /// * `nlookup` - The number of nlookup for inode needs to be reduced. + pub fn forget(&mut self, node_id: usize, nlookup: u64) -> i32 { + let mut inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + self.unref_inode(&mut inode, nlookup); + FUSE_OK + } + + /// Get the attributes of a file or directory. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to find the inode. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn getattr(&mut self, node_id: usize, fuse_attr: &mut FuseAttr) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + let (stat, ret) = fstat_at( + &inode.file, + CString::new("").unwrap(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + *fuse_attr = FuseAttr::from_stat(stat); + FUSE_OK + } + + /// Set the attributes of a file or directory. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to find the inode. + /// * `attr` - The attributes will be set to the found inode. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn setattr( + &mut self, + node_id: usize, + attr: &FuseSetattrIn, + fuse_attr: &mut FuseAttr, + ) -> i32 { + if attr.valid & FUSE_SET_ATTR_MODE != 0 { + if attr.valid & FATTR_FH != 0 { + match self.file_map.get_value(attr.fh as usize) { + Some(file) => { + let ret = fchmod(&file, attr.mode); + if ret != FUSE_OK { + return ret; + } + } + _ => { + return libc::EBADF as i32; + } + }; + } else { + match self.find_inode(node_id) { + Some(i) => { + let ret = fchmod_at( + &self.proc_dir, + CString::new(format!("{}", &i.file.as_raw_fd())).unwrap(), + attr.mode, + ); + if ret != FUSE_OK { + return ret; + } + } + _ => { + return libc::EBADF as i32; + } + }; + } + } + + if attr.valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID) != 0 { + let uid = if attr.valid & FUSE_SET_ATTR_UID != 0 { + attr.uid + } else { + u32::MAX + }; + + let gid = if attr.valid & FUSE_SET_ATTR_GID != 0 { + attr.gid + } else { + u32::MAX + }; + + match self.find_inode(node_id) { + Some(i) => { + let ret = fchown_at( + &i.file, + CString::new("").unwrap(), + uid, + gid, + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + } + _ => { + return libc::EBADF as i32; + } + }; + } + + if attr.valid & FUSE_SET_ATTR_SIZE != 0 { + if attr.valid & FATTR_FH != 0 { + match self.file_map.get_value(attr.fh as usize) { + Some(file) => { + let ret = ftruncate(&file, attr.size); + if ret != FUSE_OK { + return ret; + } + } + _ => { + return libc::EBADF as i32; + } + }; + } else { + match self.find_inode(node_id) { + Some(i) => { + if i.file_type & libc::S_IFREG == 0 && i.file_type & libc::S_IFDIR == 0 { + return libc::EBADF as i32; + } + + let (file_opt, ret) = open_at( + &self.proc_dir, + CString::new(format!("{}", &i.file.as_raw_fd())).unwrap(), + libc::O_RDWR, + 0, + ); + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + let ret = ftruncate(&file, attr.size); + if ret != FUSE_OK { + return ret; + } + } + } + _ => { + return libc::EBADF as i32; + } + }; + } + } + + if attr.valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME) != 0 { + let (a_sec, a_nsec) = if attr.valid & FUSE_SET_ATTR_ATIME_NOW != 0 { + (0, libc::UTIME_NOW) + } else if attr.valid & FUSE_SET_ATTR_ATIME != 0 { + (attr.atime, attr.atimensec as i64) + } else { + (0, libc::UTIME_OMIT) + }; + + let (m_sec, m_nsec) = if attr.valid & FUSE_SET_ATTR_MTIME_NOW != 0 { + (0, libc::UTIME_NOW) + } else if attr.valid & FUSE_SET_ATTR_MTIME != 0 { + (attr.mtime, attr.mtimensec as i64) + } else { + (0, libc::UTIME_OMIT) + }; + + if attr.valid & FATTR_FH != 0 { + match self.file_map.get_value(attr.fh as usize) { + Some(file) => { + let ret = futimens(&file, a_sec, a_nsec, m_sec, m_nsec); + if ret != FUSE_OK { + return ret; + } + } + _ => { + return libc::EBADF as i32; + } + }; + } else { + match self.find_inode(node_id) { + Some(i) => { + let ret = utimensat( + &self.proc_dir, + CString::new(format!("{}", &i.file.as_raw_fd())).unwrap(), + a_sec, + a_nsec, + m_sec, + m_nsec, + 0, + ); + if ret != FUSE_OK { + return ret; + } + } + _ => { + return libc::EBADF as i32; + } + }; + } + } + + self.getattr(node_id, fuse_attr) + } + + /// Get the contexts of the symbolic link into the buffer. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to find the inode. + /// * `buff` - The buffer is saved by the contexts of the symbolic link. + pub fn readlink(&self, node_id: usize, buff: &mut Vec) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + let (buf_opt, ret) = readlinkat(&inode.file, CString::new("").unwrap()); + if ret != FUSE_OK { + return ret; + } + + if let Some(mut buf) = buf_opt { + buff.append(&mut buf); + } else { + return libc::EBADF as i32; + } + + FUSE_OK + } + + /// Get the contexts of the symbolic link into the buffer. + /// + /// # Arguments + /// + /// * `in_header` - The in_header of fuse message used to get uid and gid. + /// * `name` - The target link name used to create a symbolic link. + /// * `link_name` - The link name that will be created a symbolic link. + /// * `node_id` - The node id that is found by name. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn symlink( + &mut self, + in_header: &FuseInHeader, + name: CString, + link_name: CString, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let parent_inode = match self.find_inode(in_header.nodeid as usize) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + + let mut old_uid = 0_u32; + let mut old_gid = 0_u32; + let ret = change_uid_gid(in_header.uid, in_header.gid, &mut old_uid, &mut old_gid); + if ret != FUSE_OK { + return ret; + } + + let ret = symlinkat(&parent_inode.file, name.clone(), link_name); + + recover_uid_gid(old_uid, old_gid); + + if ret != FUSE_OK { + return ret; + } + + self.internal_lookup(&parent_inode, name, node_id, fuse_attr) + } + + /// Create a file system node(file, device special file or named pipe) by the path name + /// with the mode and dev in the mknod information. + /// + /// # Arguments + /// + /// * `in_header` - The in_header of fuse message used to get uid and gid. + /// * `mknod_in` - The information of mknod to get the permissions and dev. + /// * `name` - The path name used to create a file system node. + /// * `node_id` - The node id that is found by name. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn mknod( + &mut self, + in_header: &FuseInHeader, + mknod_in: &FuseMknodIn, + name: CString, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let parent_inode = match self.find_inode(in_header.nodeid as usize) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + + let mut old_uid = 0_u32; + let mut old_gid = 0_u32; + let ret = change_uid_gid(in_header.uid, in_header.gid, &mut old_uid, &mut old_gid); + if ret != FUSE_OK { + return ret; + } + + let ret = mknodat( + &parent_inode.file, + name.clone(), + mknod_in.mode & !mknod_in.umask, + mknod_in.rdev, + ); + + recover_uid_gid(old_uid, old_gid); + + if ret != FUSE_OK { + return ret; + } + + self.internal_lookup(&parent_inode, name, node_id, fuse_attr) + } + + /// Create a directory by the name with the permissions in the mkdir information. + /// + /// # Arguments + /// + /// * `in_header` - The in_header of fuse message used to get uid and gid. + /// * `mkdir_in` - The information of mkdir used to get permissions. + /// * `name` - The path name that will be created a directory. + /// * `node_id` - The node id that is found by the path name. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn mkdir( + &mut self, + in_header: &FuseInHeader, + mkdir_in: &FuseMkdirIn, + name: CString, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let parent_dir = match self.find_inode(in_header.nodeid as usize) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + + let mut old_uid = 0_u32; + let mut old_gid = 0_u32; + let ret = change_uid_gid(in_header.uid, in_header.gid, &mut old_uid, &mut old_gid); + if ret != FUSE_OK { + return ret; + } + + let ret = mkdir_at( + &parent_dir.file, + name.clone(), + mkdir_in.mode & !mkdir_in.umask, + ); + + recover_uid_gid(old_uid, old_gid); + + if ret != FUSE_OK { + return ret; + } + + self.internal_lookup(&parent_dir, name, node_id, fuse_attr) + } + + /// Delete a name from the host filesystem. + /// + /// # Arguments + /// + /// * `parent_nodeid` - The parent node id that is the starting directory to look up + /// in the management of filesystem. + /// * `name` - The name will be deleted. + pub fn unlink(&mut self, parent_nodeid: usize, name: CString) -> i32 { + let parent_inode = match self.find_inode(parent_nodeid) { + Some(i) => i.clone(), + None => return libc::EBADF as i32, + }; + + let (stat, ret) = fstat_at( + &parent_inode.file, + name.clone(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + + let key = StatKey { + ino: stat.st_ino, + dev: stat.st_dev, + }; + + match self.inodes.get(&key) { + Some(i) => i.clone(), + None => return libc::EIO as i32, + }; + + let ret = unlinkat(&parent_inode.file, name, 0); + if ret != FUSE_OK { + return ret; + } + + FUSE_OK + } + + /// Delete a directory from the host filesystem by the path name. + /// + /// # Arguments + /// + /// * `parent_nodeid` - The parent node id that is the starting directory to look up + /// in the management of filesystem. + /// * `name` - The path name of the directory will be deleted. + pub fn rmdir(&mut self, parent_nodeid: usize, name: CString) -> i32 { + let parent_inode = match self.find_inode(parent_nodeid) { + Some(i) => i.clone(), + None => return libc::EBADF as i32, + }; + + let (stat, ret) = fstat_at( + &parent_inode.file, + name.clone(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + + let key = StatKey { + ino: stat.st_ino, + dev: stat.st_dev, + }; + + match self.inodes.get(&key) { + Some(i) => i.clone(), + None => return libc::EIO as i32, + }; + + let ret = unlinkat(&parent_inode.file, name, libc::AT_REMOVEDIR); + if ret != FUSE_OK { + return ret; + } + + FUSE_OK + } + + /// Rename the old path name to the new path name in the host filesystem.. + /// + /// # Arguments + /// + /// * `parent_nodeid` - The parent node id that is the starting directory to look up + /// for old path name in the management of filesystem. + /// * `oldname` - The old path name that is relative to the directory of parent node. + /// * `newparent_nodeid` - The new parent node id that is the starting directory to + /// look up for new path name in the management of filesystem. + /// * `newname` - The new path name that is relative to the directory of new parent node. + pub fn rename( + &self, + parent_nodeid: usize, + oldname: CString, + newparent_nodeid: usize, + newname: CString, + ) -> i32 { + let parent_inode = match self.find_inode(parent_nodeid) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + let newparent_inode = match self.find_inode(newparent_nodeid) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + let (stat, ret) = fstat_at( + &parent_inode.file, + oldname.clone(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + + let key = StatKey { + ino: stat.st_ino, + dev: stat.st_dev, + }; + + match self.inodes.get(&key) { + Some(_) => {} + None => return libc::EIO as i32, + }; + + rename(&parent_inode.file, oldname, &newparent_inode.file, newname) + } + + /// Create a new link to an existing file for the host filesystem. + /// + /// # Arguments + /// + /// * `parent_nodeid` - The parent node id that is the starting directory to look up. + /// * `old_nodeid` - The old node id in the management of filesystem. + /// * `name` - The path name that is relative to the directory of parent node. + /// * `node_id` - The node id that is found by the path name in the management of filesystem. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn link( + &mut self, + parent_nodeid: usize, + old_nodeid: usize, + name: CString, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let proc_file = self.proc_dir.try_clone().unwrap(); + let parent_inode = match self.find_inode(parent_nodeid) { + Some(i) => i.clone(), + None => return libc::EBADF as i32, + }; + + let inode = match self.find_mut_inode(old_nodeid) { + Some(inode_) => inode_, + None => return libc::EBADF as i32, + }; + + let ret = linkat( + &proc_file, + CString::new(format!("{}", inode.as_raw_fd())).unwrap(), + &parent_inode.file, + name, + libc::AT_SYMLINK_FOLLOW, + ); + if ret != FUSE_OK { + return ret; + } + + let (stat, ret) = fstat_at( + &inode.file, + CString::new("").unwrap(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ); + + if ret != FUSE_OK { + return ret; + } + + *fuse_attr = FuseAttr::from_stat(stat); + *node_id = inode.node_id as u64; + inode.nlookup += 1; + + FUSE_OK + } + + /// Open the file with the node id in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode. + /// * `flags` - The flags used to open the file. + /// * `fh` - The file handler is returned in the management of filesystem. + pub fn open(&mut self, node_id: usize, flags: u32, fh: &mut u64) -> i32 { + let (inode_fd, file_type) = match self.find_inode(node_id) { + Some(i) => (i.as_raw_fd(), i.file_type), + None => { + return libc::EBADF as i32; + } + }; + + if file_type & libc::S_IFREG == 0 && file_type & libc::S_IFDIR == 0 { + return libc::EBADF as i32; + } + + let (file_opt, ret) = open_at( + &self.proc_dir, + CString::new(format!("{}", inode_fd)).unwrap(), + (flags as i32) & !libc::O_NOFOLLOW, + 0, + ); + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + *fh = self.file_map.get_map(file.try_clone().unwrap()) as u64; + } + + FUSE_OK + } + + /// Read the file descriptor by file hander in the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + /// * `fd` - The file descriptor in the host filesystem. + pub fn read(&mut self, fh: usize, fd: &mut RawFd) -> i32 { + match self.file_map.get_value(fh) { + Some(file) => { + *fd = file.as_raw_fd(); + } + _ => { + return libc::EBADF as i32; + } + } + + FUSE_OK + } + + /// write the file descriptor by file hander in the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + /// * `fd` - The file descriptor in the host filesystem. + pub fn write(&mut self, fh: usize, fd: &mut RawFd) -> i32 { + match self.file_map.get_value(fh) { + Some(file) => { + *fd = file.as_raw_fd(); + } + _ => { + return libc::EBADF as i32; + } + } + + FUSE_OK + } + + /// Get the information about a mounted filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode. + /// * `fuse_statfs` - The information about the mounted filesystem is + /// returned by the found inode. + pub fn statfs(&mut self, node_id: usize, fuse_statfs: &mut FuseStatfsOut) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + let (stat, ret) = fstat_vfs(&inode.file); + if ret != FUSE_OK { + return ret; + } + + *fuse_statfs = FuseStatfsOut::from_stat(stat); + + FUSE_OK + } + + /// Release the file with file handler in the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + pub fn release(&mut self, fh: usize) -> i32 { + self.file_map.put_map(fh); + + FUSE_OK + } + + /// Transfer the file data to the storage device with file handler in + /// the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + /// * `datasync` - The datasync indicates whether to use the fdatasync + /// or fsync interface. + pub fn fsyncfile(&self, fh: usize, datasync: bool) -> i32 { + let mut ret = FUSE_OK; + + if fh == u64::max_value() as usize { + let (inode_fd, file_type) = match self.find_inode(fh) { + Some(i) => (i.as_raw_fd(), i.file_type), + None => { + return libc::EBADF as i32; + } + }; + + if file_type & libc::S_IFREG == 0 && file_type & libc::S_IFDIR == 0 { + return libc::EBADF as i32; + } + + let (file_opt, ret_) = open_at( + &self.proc_dir, + CString::new(format!("{}", inode_fd)).unwrap(), + libc::O_RDWR, + 0, + ); + if ret_ != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + ret = fsync(&file, datasync); + } else { + return libc::EBADF as i32; + } + } else { + match self.file_map.get_value(fh) { + Some(file) => { + ret = fsync(file, datasync); + } + _ => { + return libc::EBADF as i32; + } + } + } + + ret + } + + /// Set an extended attribute identified by name and associated with the node id + /// in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode. + /// * `name` - The name associated with inode. + /// * `value` - The value of the extended attribute. + /// * `size` - The size of the value string. + /// * `flags` - The flags used to set an extended attribute. + pub fn setxattr( + &self, + node_id: usize, + name: CString, + value: CString, + size: u32, + flags: u32, + ) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + if inode.file_type & libc::S_IFREG != 0 || inode.file_type & libc::S_IFDIR != 0 { + let (file_opt, ret_) = open_at( + &self.proc_dir, + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + libc::O_RDONLY, + 0, + ); + if ret_ != FUSE_OK { + return ret_; + } + + if let Some(file) = file_opt { + fset_xattr(&file, name, value, size, flags) + } else { + libc::EBADF as i32 + } + } else { + if fchdir(&self.proc_dir) != FUSE_OK { + panic!("setxattr: failed to change process directoy"); + } + + let ret_ = set_xattr( + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + name, + value, + size, + flags, + ); + + if fchdir(&self.root_inode.file) != FUSE_OK { + panic!("setxattr: failed to change directoy of root inode"); + } + + ret_ + } + } + + /// Get an extended attribute identified by name and associated with the node id + /// in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode. + /// * `name` - The name associated with inode. + /// * `size` - The size of the buffer. + /// * `buff` - The buffer of the extended attribute. + pub fn getxattr(&self, node_id: usize, name: CString, size: u32, buff: &mut Vec) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + if inode.file_type & libc::S_IFREG != 0 || inode.file_type & libc::S_IFDIR != 0 { + let (file_opt, ret) = open_at( + &self.proc_dir, + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + libc::O_RDONLY, + 0, + ); + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + let (buf_opt, ret) = fget_xattr(&file, name, size as usize); + if ret != FUSE_OK { + return ret; + } + if let Some(mut buf) = buf_opt { + buff.append(&mut buf); + } + } else { + return libc::EBADF as i32; + } + } else { + if fchdir(&self.proc_dir) != FUSE_OK { + panic!("getxattr: failed to change process directoy"); + } + + let (buf_opt, ret) = get_xattr( + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + name, + size as usize, + ); + + if fchdir(&self.root_inode.file) != FUSE_OK { + panic!("getxattr: failed to change directoy of root inode"); + } + if ret != FUSE_OK { + return ret; + } + if let Some(mut buf) = buf_opt { + buff.append(&mut buf); + } + } + + FUSE_OK + } + + /// List extended attribute names associated with the node id + /// in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode. + /// * `size` - The size of the buffer. + /// * `buff` - The buffer of the extended attribute. + pub fn listxattr(&self, node_id: usize, size: u32, buff: &mut Vec) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + if inode.file_type & libc::S_IFREG != 0 || inode.file_type & libc::S_IFDIR != 0 { + let (file_opt, ret) = open_at( + &self.proc_dir, + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + libc::O_RDONLY, + 0, + ); + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + let (buf_opt, ret) = flist_xattr(&file, size as usize); + if ret != FUSE_OK { + return ret; + } + if let Some(mut buf) = buf_opt { + buff.append(&mut buf); + } + } else { + return libc::EBADF as i32; + } + } else { + if fchdir(&self.proc_dir) != FUSE_OK { + panic!("listxattr: failed to change process directoy"); + } + + let (buf_opt, ret) = list_xattr( + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + size as usize, + ); + + if fchdir(&self.root_inode.file) != FUSE_OK { + panic!("listxattr: failed to change directoy of root inode"); + } + if ret != FUSE_OK { + return ret; + } + if let Some(mut buf) = buf_opt { + buff.append(&mut buf); + } + } + + FUSE_OK + } + + /// Remove an extended attribute identified by name and associated with the node id + /// in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode in the management of filesystem. + /// * `name` - The name associated with inode. + pub fn removexattr(&self, node_id: usize, name: CString) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + if inode.file_type & libc::S_IFREG != 0 || inode.file_type & libc::S_IFDIR != 0 { + let (file_opt, ret) = open_at( + &self.proc_dir, + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + libc::O_RDONLY, + 0, + ); + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + let ret = fremove_xattr(&file, name); + if ret != FUSE_OK { + return ret; + } + } else { + return libc::EBADF as i32; + } + } else { + if fchdir(&self.proc_dir) != FUSE_OK { + panic!("removexattr: failed to change process directoy"); + } + + let ret = remove_xattr( + CString::new(format!("{}", &inode.file.as_raw_fd())).unwrap(), + name, + ); + + if fchdir(&self.root_inode.file) != FUSE_OK { + panic!("removexattr: failed to change directoy of root inode"); + } + if ret != FUSE_OK { + return ret; + } + } + + FUSE_OK + } + + /// Delete the file lock by the node id in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode in the management of filesystem. + /// * `owner` - The name associated with inode. + pub fn flush(&mut self, node_id: usize, owner: u64) -> i32 { + self.delete_file_lock(node_id, owner) + } + + /// Initialize fuse message for getting supported features in the process. + /// + /// # Arguments + /// + /// * `flags` - The supported features in StratoVirt. + /// * `support_flags` - The supported features in the process. + pub fn init(&self, flags: u32, support_flags: &mut u32) { + if flags & FUSE_MAX_PAGES != 0 { + *support_flags |= FUSE_MAX_PAGES; + } + if flags & FUSE_CAP_ASYNC_READ != 0 { + *support_flags |= FUSE_ASYNC_READ; + } + if flags & FUSE_CAP_PARALLEL_DIROPS != 0 { + *support_flags |= FUSE_PARALLEL_DIROPS; + } + if flags & FUSE_CAP_POSIX_LOCKS != 0 { + *support_flags |= FUSE_POSIX_LOCKS; + } + if flags & FUSE_CAP_ATOMIC_O_TRUNC != 0 { + *support_flags |= FUSE_ATOMIC_O_TRUNC; + } + if flags & FUSE_CAP_EXPORT_SUPPORT != 0 { + *support_flags |= FUSE_EXPORT_SUPPORT; + } + if flags & FUSE_CAP_DONT_MASK != 0 { + *support_flags |= FUSE_DONT_MASK; + } + if flags & FUSE_CAP_FLOCK_LOCKS != 0 { + *support_flags |= FUSE_FLOCK_LOCKS; + } + if flags & FUSE_CAP_AUTO_INVAL_DATA != 0 { + *support_flags |= FUSE_AUTO_INVAL_DATA; + } + if flags & FUSE_CAP_READDIRPLUS != 0 { + *support_flags |= FUSE_DO_READDIRPLUS; + } + if flags & FUSE_CAP_READDIRPLUS_AUTO != 0 { + *support_flags |= FUSE_READDIRPLUS_AUTO; + } + if flags & FUSE_CAP_ASYNC_DIO != 0 { + *support_flags |= FUSE_ASYNC_DIO; + } + + if flags & FUSE_CAP_POSIX_ACL != 0 { + *support_flags |= FUSE_POSIX_ACL; + } + + umask(0o000); + } + + /// Open a directory with the node id in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode in the management of filesystem. + /// * `dir_fh` - The directory handler is returned in the management of filesystem. + pub fn opendir(&mut self, node_id: usize, dir_fh: &mut u64) -> i32 { + let inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + let (file_opt, ret) = open_at(&inode.file, CString::new(".").unwrap(), libc::O_RDONLY, 0); + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + *dir_fh = self.file_map.get_map(file) as u64; + return FUSE_OK; + } + + libc::EBADF as i32 + } + + /// read a directory stream with the directory handler in the host filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode in the management of filesystem. + /// * `dh` - The directory handler in the management of filesystem. + /// * `size` - The size of the buffer. + /// * `offset` - The offset indicates it opens a directory stream with the offset. + /// * `plus` - The plus indicates it uses FuseDirentplus struct to the buffer. + /// * `buff` - The buffer of all FuseDirent structs or FuseDirentplus structs. + pub fn readdir( + &mut self, + node_id: usize, + dh: usize, + size: u32, + offset: u64, + plus: bool, + buff: &mut Vec, + ) -> i32 { + let dir_inode = match self.find_inode(node_id) { + Some(i) => i.clone(), + None => { + return libc::EBADF as i32; + } + }; + + let mut dirp = match self.file_map.get_value_mut(dh) { + Some(file) => { + let (dirp_opt, ret) = fdopen_dir(file.as_raw_fd()); + if ret != FUSE_OK { + return libc::EBADF as i32; + } + dirp_opt.unwrap() + } + _ => { + return libc::EBADF as i32; + } + }; + + seek_dir(&mut dirp, offset); + + let mut remain = size; + let mut son_nodeid = 0_u64; + loop { + let (dirent_opt, ret) = read_dir(&mut dirp); + if ret != FUSE_OK { + return ret; + } + let direntp = dirent_opt.unwrap(); + if direntp.is_null() { + break; + } + + // The above code has checked the validity of direntp, so it is safe for *direntp. + let dirent = unsafe { *direntp }; + + let (name_len, son_name) = match array_to_cstring(&dirent.d_name[..]) { + Ok(v) => v, + Err(_) => { + continue; + } + }; + + let only_entry_size = if plus { + mem::size_of::() + } else { + mem::size_of::() + }; + + let (entry_size, gap) = match round_up((only_entry_size + name_len) as u64, 8) { + Some(v) => (v as u32, v as usize - (only_entry_size + name_len)), + _ => { + return libc::EINVAL as i32; + } + }; + if entry_size > remain { + if son_nodeid != 0 { + self.forget(son_nodeid as usize, 1); + } + break; + } + + let mut fuse_dirent = FuseDirent { + ino: dirent.d_ino, + off: dirent.d_off as u64, + namelen: name_len as u32, + type_: dirent.d_type as u32 & (libc::S_IFMT >> 12), + name: [0u8; 0], + }; + + if dir_inode.node_id == self.root_inode.node_id && path_is_dotdot(&son_name) { + fuse_dirent.ino = self.root_inode.key.ino; + fuse_dirent.type_ = libc::DT_DIR as u32 & (libc::S_IFMT >> 12); + } + + if plus { + son_nodeid = 0; + let mut son_attr = FuseAttr::default(); + if !path_is_dot(&son_name) && !path_is_dotdot(&son_name) { + let ret = self.internal_lookup( + &dir_inode, + son_name.clone(), + &mut son_nodeid, + &mut son_attr, + ); + if ret != FUSE_OK { + return ret; + } + } + + buff.extend_from_slice( + FuseDirentplus { + entry_out: FuseEntryOut { + nodeid: son_nodeid as u64, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: son_attr, + }, + dirent: fuse_dirent, + } + .as_bytes(), + ); + } else { + buff.extend_from_slice(fuse_dirent.as_bytes()); + }; + + buff.extend_from_slice(son_name.as_bytes()); + if gap > 0 { + buff.append(&mut vec![0u8; gap]); + } + + remain -= entry_size; + } + + FUSE_OK + } + + /// Release a directory in the management of filesystem. + /// + /// # Arguments + /// + /// * `dir_fh` - The directory handler in the management of filesystem. + pub fn releasedir(&mut self, dir_fh: usize) -> i32 { + self.file_map.put_map(dir_fh); + + FUSE_OK + } + + /// Transfer the directory data to the storage device with directory handler in + /// the management of filesystem. + /// + /// # Arguments + /// + /// * `dir_fh` - The directory handler in the management of filesystem. + /// * `datasync` - The datasync indicates whether to use the fdatasync + /// or fsync interface. + pub fn fsyncdir(&self, dir_fh: usize, datasync: bool) -> i32 { + if let Some(file) = self.file_map.get_value(dir_fh) { + let ret = fsync(file, datasync); + if ret != FUSE_OK { + return ret; + } + } else { + return libc::EBADF as i32; + } + + FUSE_OK + } + + /// Create the POSIX file lock with the node id in the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode in the management of filesystem. + /// * `owner` - The unique index for file lock in the inode. + /// * `file_lock_in` - The information of file lock will be set. + /// * `file_lock_out` - The information of file lock will be returned. + pub fn getlk( + &mut self, + node_id: usize, + owner: u64, + file_lock_in: &FuseFileLock, + file_lock_out: &mut FuseFileLock, + ) -> i32 { + let (file_opt, ret) = self.create_file_lock(node_id, owner); + if ret != FUSE_OK { + return ret; + } + + let ret = fcntl_flock( + &file_opt.unwrap(), + libc::F_GETLK, + file_lock_in, + file_lock_out, + ); + if ret != FUSE_OK { + return ret; + } + + FUSE_OK + } + + /// Lock the file or unlock the file by POSIX lock with the node id in + /// the management of filesystem. + /// + /// # Arguments + /// + /// * `node_id` - The node id used to look up the inode in the management of filesystem. + /// * `owner` - The unique index for file lock in the inode. + /// * `is_blocking` - The is_blocking indicates whether to use a blocking lock. + /// * `file_lock_in` - The information of file lock will be set. + pub fn setlk( + &mut self, + node_id: usize, + owner: u64, + is_blocking: bool, + file_lock_in: &FuseFileLock, + ) -> i32 { + if is_blocking { + return libc::EOPNOTSUPP as i32; + } + + let (file_opt, ret) = self.create_file_lock(node_id, owner); + if ret != FUSE_OK { + return ret; + } + + let mut file_lock_out = FuseFileLock::default(); + let ret = fcntl_flock( + &file_opt.unwrap(), + libc::F_SETLK, + file_lock_in, + &mut file_lock_out, + ); + if ret != FUSE_OK { + return ret; + } + + FUSE_OK + } + + /// Lock the file or unlock the file by BSD lock with file handler in + /// the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + /// * `lock_type` - The lock type contains the type of read lock, write lock and unlocking. + /// * `is_blocking` - The is_blocking indicates whether to use a blocking lock. + pub fn flock(&self, fh: usize, lock_type: u32, is_blocking: bool) -> i32 { + let mut operation: i32 = 0; + + if lock_type == F_RDLCK { + operation = libc::LOCK_SH; + } else if lock_type == F_WDLCK { + operation = libc::LOCK_EX; + } else if lock_type == F_UNLCK { + operation = libc::LOCK_UN; + } + + if !is_blocking { + operation |= libc::LOCK_NB; + } + + if let Some(file) = self.file_map.get_value(fh) { + let ret = flock(file, operation); + if ret != FUSE_OK { + return ret; + } + } else { + return libc::EBADF as i32; + } + + FUSE_OK + } + + /// Create a file with name in the management of filesystem. + /// + /// # Arguments + /// + /// * `in_header` - The in_header of fuse message used to get uid and gid. + /// * `create_in` - The information of creating a file contains the flags, mode and umask. + /// * `name` - The string of name used to create a file. + /// * `fh` - The file handler is returned in the management of filesystem. + /// * `node_id` - The node id that is found by the name in the management of filesystem. + /// * `fuse_attr` - The attributes will be returned by the found inode. + pub fn create( + &mut self, + in_header: &FuseInHeader, + create_in: &FuseCreateIn, + name: CString, + fh: &mut u64, + node_id: &mut u64, + fuse_attr: &mut FuseAttr, + ) -> i32 { + let parent_dir = match self.find_inode(in_header.nodeid as usize) { + Some(i) => i.clone(), + _ => { + return libc::EBADF as i32; + } + }; + + let mut old_uid = 0_u32; + let mut old_gid = 0_u32; + let ret = change_uid_gid(in_header.uid, in_header.gid, &mut old_uid, &mut old_gid); + if ret != FUSE_OK { + return ret; + } + + let (file_opt, ret) = open_at( + &parent_dir.file, + name.clone(), + (create_in.flags as i32 | libc::O_CREAT) & !libc::O_NOFOLLOW, + create_in.mode & !(create_in.umask & 0o777), + ); + + recover_uid_gid(old_uid, old_gid); + + if ret != FUSE_OK { + return ret; + } + + if let Some(file) = file_opt { + *fh = self.file_map.get_map(file.try_clone().unwrap()) as u64; + } + + self.internal_lookup(&parent_dir, name, node_id, fuse_attr) + } + + /// Destroy the management of filesystem, except for the root inode. + pub fn destroy(&mut self) -> i32 { + let root_key = self.root_inode.key; + + self.inode_key_map.destroy_map(); + self.file_map.destroy_map(); + self.inodes = BTreeMap::new(); + + // Need to add root_inode back to the inode table and inode_key_map table for + // the filesystem function + let root_id = self.inode_key_map.get_map(root_key); + self.root_inode.node_id = root_id; + self.inodes.insert(root_key, self.root_inode.clone()); + + FUSE_OK + } + + /// Allocate the disk space with file handler in the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + /// * `mode` - The mode determines the operation to be performed on the given range. + /// * `offset` - The offset in the file. + /// * `length` - The length that needs to be allocated. + pub fn fallocate(&self, fh: usize, mode: u32, offset: u64, length: u64) -> i32 { + if let Some(file) = self.file_map.get_value(fh) { + let ret = fallocate(file, mode, offset, length); + if ret != FUSE_OK { + return ret; + } + } else { + return libc::EBADF as i32; + } + + FUSE_OK + } + + /// Reposition the file offset of the open file with file handler + /// in the management of filesystem. + /// + /// # Arguments + /// + /// * `fh` - The file handler in the management of filesystem. + /// * `offset` - The offset in the file used together with the whence. + /// * `whence` - The whence determines the operation to be performed in the file. + /// * `outoffset` - The offset from the beginning of the file is returned. + pub fn lseek(&self, fh: usize, offset: u64, whence: u32, outoffset: &mut u64) -> i32 { + if let Some(file) = self.file_map.get_value(fh) { + let (offset_tmp, ret) = lseek(file, offset, whence); + if ret != FUSE_OK { + return ret; + } + *outoffset = offset_tmp; + } else { + return libc::EBADF as i32; + } + + FUSE_OK + } +} diff --git a/vhost_user_fs/src/fs_ops.rs b/vhost_user_fs/src/fs_ops.rs new file mode 100644 index 0000000000000000000000000000000000000000..be3dc32d1964f4333b577db2966b6dbb49e5f6b1 --- /dev/null +++ b/vhost_user_fs/src/fs_ops.rs @@ -0,0 +1,859 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. +use std::ffi::CString; +use std::fs::File; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; + +use super::fuse_msg::*; + +/// The pointer to open a directory. +pub type DirPtr = *mut libc::DIR; +/// The pointer to a directory entry in the directory stream. +pub type DirentPtr = *mut libc::dirent; + +/// Get the information of a file with path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `flags` - The flags used to get the information of the file. +pub fn fstat_at(file: &File, name: CString, flags: i32) -> (libc::stat, i32) { + let mut stat: libc::stat = unsafe { std::mem::zeroed() }; + + errno::set_errno(errno::Errno(0)); + if unsafe { libc::fstatat(file.as_raw_fd(), name.as_ptr(), &mut stat, flags) } < 0 { + return (stat, errno::errno().0); + } + + (stat, FUSE_OK) +} + +/// Open a file with the path name. +/// +/// # Arguments +/// +/// * `name` - The path name in the host filesystem. +/// * `flags` - The flags used to open a file. +pub fn open(name: CString, flags: i32) -> (Option, i32) { + errno::set_errno(errno::Errno(0)); + let fd = unsafe { libc::open(name.as_ptr(), flags) }; + if fd < 0 { + return (None, errno::errno().0); + } + + let file = unsafe { File::from_raw_fd(fd) }; + + (Some(file), FUSE_OK) +} + +/// Open a file with path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `mode` - The mode used to open a file. +pub fn open_at(file: &File, name: CString, flags: i32, mode: u32) -> (Option, i32) { + errno::set_errno(errno::Errno(0)); + + let fd = unsafe { libc::openat(file.as_raw_fd(), name.as_ptr(), flags, mode) }; + if fd < 0 { + return (None, errno::errno().0); + } + + let file = unsafe { File::from_raw_fd(fd) }; + + (Some(file), FUSE_OK) +} + +/// Change permissions of a file. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `mode` - The mode indicates the permissions of the file will be set. +pub fn fchmod(file: &File, mode: u32) -> i32 { + errno::set_errno(errno::Errno(0)); + if unsafe { libc::fchmod(file.as_raw_fd(), mode) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Change permissions of a file with path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `mode` - The mode indicates the permissions of the file will be set. +pub fn fchmod_at(file: &File, name: CString, mode: u32) -> i32 { + errno::set_errno(errno::Errno(0)); + if unsafe { libc::fchmodat(file.as_raw_fd(), name.as_ptr(), mode, 0) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Change the owner and group of a file with path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `uid` - The user id will be set. +/// * `gid` - The group id will be set. +/// * `flags` - The flags indicates the action of file will be set. +pub fn fchown_at(file: &File, name: CString, uid: u32, gid: u32, flags: i32) -> i32 { + errno::set_errno(errno::Errno(0)); + if unsafe { libc::fchownat(file.as_raw_fd(), name.as_ptr(), uid, gid, flags) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Truncate file to specified length. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `size` - The size of truncating file. +pub fn ftruncate(file: &File, size: u64) -> i32 { + errno::set_errno(errno::Errno(0)); + if unsafe { libc::ftruncate(file.as_raw_fd(), size as i64) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Update the timestamps of a file with nanosecond precision. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `a_sec` - The second of last access time. +/// * `a_nsec` - The nanosecond of last access time. +/// * `m_sec` - The second of last modification time. +/// * `m_nsec` - The nanosecond of last modification time. +pub fn futimens(file: &File, a_sec: u64, a_nsec: i64, m_sec: u64, m_nsec: i64) -> i32 { + let tv = vec![ + libc::timespec { + tv_sec: a_sec as i64, + tv_nsec: a_nsec, + }, + libc::timespec { + tv_sec: m_sec as i64, + tv_nsec: m_nsec, + }, + ]; + + errno::set_errno(errno::Errno(0)); + if unsafe { libc::futimens(file.as_raw_fd(), tv.as_ptr()) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Update the timestamps with nanosecond precision by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `a_sec` - The second of last access time. +/// * `a_nsec` - The nanosecond of last access time. +/// * `m_sec` - The second of last modification time. +/// * `m_nsec` - The nanosecond of last modification time. +pub fn utimensat( + file: &File, + name: CString, + a_sec: u64, + a_nsec: i64, + m_sec: u64, + m_nsec: i64, + flags: i32, +) -> i32 { + let tv = vec![ + libc::timespec { + tv_sec: a_sec as i64, + tv_nsec: a_nsec, + }, + libc::timespec { + tv_sec: m_sec as i64, + tv_nsec: m_nsec, + }, + ]; + + errno::set_errno(errno::Errno(0)); + if unsafe { libc::utimensat(file.as_raw_fd(), name.as_ptr(), tv.as_ptr(), flags) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +const MAX_PATH_LEN: usize = 256; + +/// Read value of a symbolic link by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +pub fn readlinkat(file: &File, path: CString) -> (Option>, i32) { + let mut buf = vec![0; MAX_PATH_LEN + 1]; + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { + libc::readlinkat( + file.as_raw_fd(), + path.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_char, + buf.len(), + ) + }; + + if ret == -1 { + return (None, errno::errno().0 as i32); + } + + buf.resize(ret as usize, 0); + + (Some(buf), FUSE_OK) +} + +/// Creates a symbolic link to the target path name. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `link_name` - The link name is new path name for the target path name. +pub fn symlinkat(file: &File, name: CString, link_name: CString) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = unsafe { libc::symlinkat(link_name.as_ptr(), file.as_raw_fd(), name.as_ptr()) }; + + if ret == -1 { + return errno::errno().0 as i32; + } + + FUSE_OK +} + +/// Change user id and group id in the process. +/// +/// # Arguments +/// +/// * `new_uid` - The user id will be changed to the current user id. +/// * `new_gid` - The group id will be changed to the current group id. +/// * `old_uid` - The old user id will be returned. +/// * `old_gid` - The old group id will be returned. +pub fn change_uid_gid(new_uid: u32, new_gid: u32, old_uid: &mut u32, old_gid: &mut u32) -> i32 { + let current_uid = unsafe { libc::geteuid() }; + let current_gid = unsafe { libc::getegid() }; + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::syscall(libc::SYS_setresgid, -1, new_gid, -1) }; + if ret == -1 { + return errno::errno().0; + } + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::syscall(libc::SYS_setresuid, -1, new_uid, -1) }; + if ret == -1 { + unsafe { libc::syscall(libc::SYS_setresgid, -1, current_gid, -1) }; + + return errno::errno().0; + } + + *old_uid = current_uid; + *old_gid = current_gid; + FUSE_OK +} + +/// Recover user id and group id in the process. +/// +/// # Arguments +/// +/// * `old_uid` - The old user id will be recovered in the process. +/// * `old_gid` - The old group id will be recovered in the process. +pub fn recover_uid_gid(old_uid: u32, old_gid: u32) -> i32 { + let ret = unsafe { libc::syscall(libc::SYS_setresuid, -1, old_uid, -1) }; + if ret == -1 { + panic!("Failed to recover uid {} {}", old_uid, old_gid); + } + + let ret = unsafe { libc::syscall(libc::SYS_setresgid, -1, old_gid, -1) }; + if ret == -1 { + panic!("Failed to recover gid {} {}", old_uid, old_gid); + } + + FUSE_OK +} + +/// Create a special or ordinary file by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `mode` - The mode indicates both the file mode to use and the type of node to be created. +/// * `rdev` - The rdev indicates the major and minor numbers of the special file. +pub fn mknodat(file: &File, name: CString, mode: u32, rdev: u32) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = unsafe { libc::mknodat(file.as_raw_fd(), name.as_ptr(), mode, rdev as u64) }; + + if ret == -1 { + return errno::errno().0 as i32; + } + + FUSE_OK +} + +/// Create a directory by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `mode` - The mode indicates the permissions of the new directory. +pub fn mkdir_at(file: &File, name: CString, mode: u32) -> i32 { + errno::set_errno(errno::Errno(0)); + if unsafe { libc::mkdirat(file.as_raw_fd(), name.as_ptr(), mode) } < 0 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Delete a name in host filesystem by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the file descriptor of starting directory to look up for the +/// file. +/// * `name` - The name indicates the file path is relative to the starting directory. +/// * `flags` - The flags indicates the operation of deleting a name. +pub fn unlinkat(file: &File, name: CString, flags: i32) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = unsafe { libc::unlinkat(file.as_raw_fd(), name.as_ptr(), flags) }; + + if ret == -1 { + return errno::errno().0 as i32; + } + + FUSE_OK +} + +/// Modify a name in host filesystem by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `olddir` - The directory file handler saves the file descriptor of starting directory to look up for the +/// old file. +/// * `name` - The name indicates the file path is relative to the starting of old directory. +/// * `newdir` - The directory file handler saves the file descriptor of starting directory to look up for the +/// new file. +/// * `newname` - The name indicates the file path is relative to the starting of new directory. +pub fn rename(olddir: &File, name: CString, newdir: &File, newname: CString) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = unsafe { + libc::renameat( + olddir.as_raw_fd(), + name.as_ptr(), + newdir.as_raw_fd(), + newname.as_ptr(), + ) + }; + + if ret != FUSE_OK { + return errno::errno().0 as i32; + } + + FUSE_OK +} + +/// Change a name for file in host filesystem by path name that is relative to the starting directory. +/// +/// # Arguments +/// +/// * `old_file` - The file handler saves the file descriptor of starting old directory to look up for the +/// file. +/// * `old_name` - The name indicates the file path is relative to the starting of old directory. +/// * `new_file` - The file handler saves the file descriptor of starting new directory to look up for the +/// file. +/// * `new_name` - The name indicates the file path is relative to the starting of new directory. +/// * `flags` - The flags indicates the operation of change a name. +pub fn linkat( + old_file: &File, + old_name: CString, + new_file: &File, + new_name: CString, + flags: i32, +) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = unsafe { + libc::linkat( + old_file.as_raw_fd(), + old_name.as_ptr(), + new_file.as_raw_fd(), + new_name.as_ptr(), + flags, + ) + }; + + if ret == -1 { + return errno::errno().0 as i32; + } + + FUSE_OK +} + +/// Get the information about a mounted filesystem. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +pub fn fstat_vfs(file: &File) -> (libc::statvfs, i32) { + let mut stat: libc::statvfs = unsafe { std::mem::zeroed() }; + + errno::set_errno(errno::Errno(0)); + if unsafe { libc::fstatvfs(file.as_raw_fd(), &mut stat) } < 0 { + return (stat, errno::errno().0); + } + + (stat, FUSE_OK) +} + +/// Synchronize the data of file to storage device. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `datasync` - The datasync indicates whether to use the fdatasync +/// or fsync interface. +pub fn fsync(file: &File, datasync: bool) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = if datasync { + unsafe { libc::fdatasync(file.as_raw_fd()) } + } else { + unsafe { libc::fsync(file.as_raw_fd()) } + }; + + if ret != FUSE_OK { + return errno::errno().0; + } + + FUSE_OK +} + +/// Change current working directory. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open directory descriptor. +pub fn fchdir(file: &File) -> i32 { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::fchdir(file.as_raw_fd()) }; + if ret == -1 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Set an extended attribute value. +/// +/// # Arguments +/// +/// * `path` - The path in the host filesystem. +/// * `name` - The name of extended attribute. +/// * `value` - The value of extended attribute. +/// * `size` - The size of the string of value. +/// * `flags` - The flags indicates the attribute will be set. +pub fn set_xattr(path: CString, name: CString, value: CString, size: u32, flags: u32) -> i32 { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { + libc::setxattr( + path.as_ptr(), + name.as_ptr(), + value.as_ptr() as *const libc::c_void, + size as usize, + flags as i32, + ) + }; + + if ret == -1 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Set an extended attribute value by the open file file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `name` - The name of extended attribute. +/// * `value` - The value of extended attribute. +/// * `size` - The size of the string of value. +/// * `flags` - The flags indicates the attribute will be set. +pub fn fset_xattr(file: &File, name: CString, value: CString, size: u32, flags: u32) -> i32 { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { + libc::fsetxattr( + file.as_raw_fd(), + name.as_ptr(), + value.as_ptr() as *const libc::c_void, + size as usize, + flags as i32, + ) + }; + + if ret == -1 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Get an extended attribute value. +/// +/// # Arguments +/// +/// * `path` - The path in the host filesystem. +/// * `name` - The name of extended attribute. +/// * `size` - The size of the extended attribute value that needs to be get. +pub fn get_xattr(path: CString, name: CString, size: usize) -> (Option>, i32) { + let mut buf = vec![0; size]; + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { + libc::getxattr( + path.as_ptr(), + name.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_void, + size, + ) + }; + if ret == -1 { + return (None, errno::errno().0); + } + + buf.resize(ret as usize, 0); + + (Some(buf), FUSE_OK) +} + +/// Get an extended attribute value by the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `name` - The name of extended attribute. +/// * `size` - The size of the extended attribute value that needs to be get. +pub fn fget_xattr(file: &File, name: CString, size: usize) -> (Option>, i32) { + let mut buf = vec![0; size]; + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { + libc::fgetxattr( + file.as_raw_fd(), + name.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_void, + size, + ) + }; + if ret == -1 { + return (None, errno::errno().0); + } + + buf.resize(ret as usize, 0); + + (Some(buf), FUSE_OK) +} + +/// List extended attribute names. +/// +/// # Arguments +/// +/// * `path` - The path in the host filesystem. +/// * `size` - The size of the extended attribute names that needs to be get. +pub fn list_xattr(path: CString, size: usize) -> (Option>, i32) { + let mut buf = vec![0; size]; + + errno::set_errno(errno::Errno(0)); + let ret = + unsafe { libc::listxattr(path.as_ptr(), buf.as_mut_ptr() as *mut libc::c_char, size) }; + if ret == -1 { + return (None, errno::errno().0); + } + + buf.resize(ret as usize, 0); + + (Some(buf), FUSE_OK) +} + +/// List extended attribute names by the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `size` - The size of the extended attribute names that needs to be get. +pub fn flist_xattr(file: &File, size: usize) -> (Option>, i32) { + let mut buf = vec![0; size]; + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { + libc::flistxattr( + file.as_raw_fd(), + buf.as_mut_ptr() as *mut libc::c_char, + size, + ) + }; + if ret == -1 { + return (None, errno::errno().0); + } + + buf.resize(ret as usize, 0); + + (Some(buf), FUSE_OK) +} + +/// Remove an extended attribute value. +/// +/// # Arguments +/// +/// * `path` - The path in the host filesystem. +/// * `name` - The name of the extended attribute value. +pub fn remove_xattr(path: CString, name: CString) -> i32 { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::removexattr(path.as_ptr(), name.as_ptr()) }; + if ret == -1 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Remove an extended attribute value by the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `name` - The name of the extended attribute value. +pub fn fremove_xattr(file: &File, name: CString) -> i32 { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::fremovexattr(file.as_raw_fd(), name.as_ptr()) }; + if ret == -1 { + return errno::errno().0; + } + + FUSE_OK +} + +/// Set mask for file mode creation. +/// +/// # Arguments +/// +/// * `umask` - The umask indicates the permissions in the umask are turned off. +pub fn umask(umask: u32) { + unsafe { libc::umask(umask) }; +} + +/// Open a directory stream by the file descriptor. +/// +/// # Arguments +/// +/// * `fd` - The open file descriptor used to open a directory stream. +pub fn fdopen_dir(fd: RawFd) -> (Option, i32) { + errno::set_errno(errno::Errno(0)); + + let dirp = unsafe { libc::fdopendir(fd) }; + if errno::errno().0 != 0 { + return (None, errno::errno().0); + } + + (Some(dirp), FUSE_OK) +} + +/// Set the position of the next readdir() in the directory stream. +/// +/// # Arguments +/// +/// * `dirp` - The pointer to open a directory. +/// * `offset` - The position of the next readdir(). +pub fn seek_dir(dirp: &mut DirPtr, offset: u64) { + unsafe { + libc::seekdir(*dirp, offset as i64); + }; +} + +/// Read a directory entry in the directory stream. +/// +/// # Arguments +/// +/// * `dirp` - The pointer to open a directory. +pub fn read_dir(dirp: &mut DirPtr) -> (Option, i32) { + errno::set_errno(errno::Errno(0)); + + let direntp = unsafe { libc::readdir(*dirp) }; + if errno::errno().0 != 0 { + return (None, errno::errno().0); + } + + (Some(direntp), FUSE_OK) +} + +/// Lock the file or unlock the file by BSD lock by the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `operation` - The operation of lock type. +pub fn flock(file: &File, operation: i32) -> i32 { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::flock(file.as_raw_fd(), operation) }; + if ret == -1 { + return errno::errno().0; + } + + FUSE_OK +} + +const OFFSET_MAX: u64 = 0x7fffffffffffffff; +/// Lock the file or unlock the file by POSIX lock by the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `cmd` - The command for creating, lock and unlock in POSIX lock. +/// * `file_lock` - The information of file lock will be set. +/// * `file_lock_out` - The information of file lock will be returned. +pub fn fcntl_flock( + file: &File, + cmd: i32, + file_lock: &FuseFileLock, + file_lock_out: &mut FuseFileLock, +) -> i32 { + let mut flock: libc::flock = unsafe { std::mem::zeroed() }; + + flock.l_type = file_lock.lock_type as i16; + flock.l_whence = libc::SEEK_SET as i16; + flock.l_start = file_lock.start as i64; + flock.l_pid = file_lock.pid as i32; + + if file_lock.end == OFFSET_MAX { + flock.l_len = 0; + } else { + flock.l_len = file_lock.end as i64 - file_lock.start as i64 + 1; + } + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::fcntl(file.as_raw_fd(), cmd, &mut flock) }; + + if ret == -1 { + return errno::errno().0; + } + + file_lock_out.lock_type = flock.l_type as u32; + if flock.l_type != libc::F_ULOCK as i16 { + file_lock_out.start = flock.l_start as u64; + if flock.l_len == 0 { + file_lock_out.end = OFFSET_MAX; + } else { + file_lock_out.end = (flock.l_start + flock.l_len - 1) as u64; + } + } + + file_lock_out.pid = flock.l_pid as u32; + + FUSE_OK +} + +/// Allocate the disk space with the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `mode` - The mode determines the operation to be performed on the given range. +/// * `offset` - The offset in the file. +/// * `length` - The length that needs to be allocated. +pub fn fallocate(file: &File, mode: u32, offset: u64, length: u64) -> i32 { + errno::set_errno(errno::Errno(0)); + + let ret = + unsafe { libc::fallocate(file.as_raw_fd(), mode as i32, offset as i64, length as i64) }; + + if ret == -1 { + return errno::errno().0 as i32; + } + + FUSE_OK +} + +/// Reposition the file offset of the open file descriptor. +/// +/// # Arguments +/// +/// * `file` - The file handler saves the open file descriptor. +/// * `offset` - The offset in the file used together with the whence. +/// * `whence` - The length that needs to be allocated. +pub fn lseek(file: &File, offset: u64, whence: u32) -> (u64, i32) { + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::lseek(file.as_raw_fd(), offset as i64, whence as i32) }; + + if ret == -1 { + return (0, errno::errno().0 as i32); + } + + (ret as u64, FUSE_OK) +} + +/// Set file resource limits. +/// +/// # Arguments +/// +/// * `rlim_cur` - The soft limit of the file resource. +/// * `rlim_max` - The hard limit of the file resource. +pub fn set_rlimit(rlim_cur: u64, rlim_max: u64) -> i32 { + let limit = libc::rlimit { rlim_cur, rlim_max }; + + errno::set_errno(errno::Errno(0)); + let ret = unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &limit) }; + + if ret == -1 { + return errno::errno().0 as i32; + } + + FUSE_OK +} diff --git a/vhost_user_fs/src/fuse_msg.rs b/vhost_user_fs/src/fuse_msg.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee307c7624cb078a1703b1561452e305da12178c --- /dev/null +++ b/vhost_user_fs/src/fuse_msg.rs @@ -0,0 +1,1012 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::cmp; +use std::collections::VecDeque; +use std::ffi::CString; +use std::mem::size_of; +use std::sync::Arc; + +use error_chain::ChainedError; + +use address_space::AddressSpace; +use device_model::virtio::queue::ElemIovec; +use util::byte_code::ByteCode; + +use crate::errors::{Result, ResultExt}; + +/// Successfully process the fuse message +pub const FUSE_OK: i32 = 0; + +/// Get the buffers from the element of virtio queue to parse fuse message or +/// reply fuse message. +pub struct FuseBuffer { + /// Get the buffers to read or write from the element of virtio queue. + pub bufs: VecDeque, + // The total size of the buffers from the element of virtio queue. + btyes_total: usize, + // The processed bytes to read or write fuse message. + bytes_processed: usize, +} + +impl FuseBuffer { + /// Construct a fuse buffer to process fuse message. + /// + /// # Arguments + /// + /// * `elem_iovec` - The vectors of IO vector element from virtio queue. + pub fn new(elem_iovec: &[ElemIovec]) -> Self { + let mut btyes_total = 0; + let mut bufs = VecDeque::new(); + + for iov in elem_iovec { + btyes_total += iov.len as usize; + bufs.push_back(*iov); + } + + FuseBuffer { + bufs, + btyes_total, + bytes_processed: 0_usize, + } + } + + /// Read the CString ending with '\0' from the fuse buffers. + /// + /// # Arguments + /// + /// * `sys_mem` - Address space mapped with StratoVirt. + pub fn read_cstring(&mut self, sys_mem: &Arc) -> Result { + let bytes_remain = self.btyes_total - self.bytes_processed; + let mut buffer = vec![0; bytes_remain]; + + let mut offset = 0_usize; + for buf in &self.bufs { + let mut slice = &mut buffer[offset..]; + let read_count = cmp::min(slice.len(), buf.len as usize); + sys_mem + .read(&mut slice, buf.addr, read_count as u64) + .chain_err(|| "Failed to read buffer for fuse req")?; + offset += read_count; + } + + let pos = match buffer.iter().position(|c| *c == b'\0') { + Some(p) => p + 1, + None => bail!("It is not a string"), + }; + + let str_slice = buffer.as_slice(); + let cstring = unsafe { CString::from_vec_unchecked(str_slice[0..pos].to_vec()) }; + + // remove the processed bytes in self.bufs + let mut need_read_count = pos; + let bufs = self.bufs.clone(); + for buf in bufs { + let read_count = cmp::min(need_read_count, buf.len as usize); + self.bytes_processed += read_count; + + if let Some(buftmp) = self.bufs.pop_front() { + if read_count < buftmp.len as usize { + //Add the remain length to the head of self.bufs + let len = buftmp.len - read_count as u32; + let addr = buftmp.addr.unchecked_add(read_count as u64); + let remain = ElemIovec { addr, len }; + self.bufs.push_front(remain); + break; + } else { + need_read_count -= buftmp.len as usize; + } + } + } + + Ok(cstring) + } + + fn read_slice( + &mut self, + sys_mem: &Arc, + dst: &mut [u8], + count: usize, + ) -> Result<()> { + if dst.len() != count { + bail!( + "The length {} of dst slice is not equal to the count {}", + dst.len(), + count + ); + } + + let read_end = match self.bytes_processed.checked_add(count) { + Some(end_) => end_, + None => bail!("The read count {} {} overflow", count, self.bytes_processed), + }; + + if read_end > self.btyes_total { + bail!( + "The read count {} exceeds maximum {}", + read_end, + self.btyes_total + ); + } + + let bufs = self.bufs.clone(); + let mut offset = 0_usize; + for buf in bufs { + let mut slice = &mut dst[offset..]; + let read_count = cmp::min(slice.len(), buf.len as usize); + + sys_mem + .read(&mut slice, buf.addr, read_count as u64) + .chain_err(|| "Failed to read buffer for fuse req")?; + self.bytes_processed += read_count; + offset += read_count; + + // remove the processed bytes in self.bufs + if let Some(buftmp) = self.bufs.pop_front() { + if read_count < buftmp.len as usize { + //Add the remain length to the head of self.bufs + let len = buftmp.len - read_count as u32; + let addr = buftmp.addr.unchecked_add(read_count as u64); + let remain = ElemIovec { addr, len }; + self.bufs.push_front(remain); + break; + } + } + } + + Ok(()) + } + + /// read an object from the fuse buffers. + /// + /// # Arguments + /// + /// * `sys_mem` - Address space mapped with StratoVirt. + /// + /// # Note + /// To use this method, it is necessary to implement `ByteCode` trait for your object. + pub fn read_obj(&mut self, sys_mem: &Arc) -> Result { + let mut obj = T::default(); + self.read_slice(sys_mem, &mut obj.as_mut_bytes(), size_of::())?; + Ok(obj) + } + + fn write_slice(&mut self, sys_mem: &Arc, src: &[u8], count: usize) -> Result<()> { + if src.len() != count { + bail!( + "The length {} of src slice is not equal to the count {}", + src.len(), + count + ); + } + + let write_end = match self.bytes_processed.checked_add(count) { + Some(end_) => end_, + None => bail!("The read count {} {} overflow", count, self.bytes_processed), + }; + + if write_end > self.btyes_total { + bail!( + "The read count {} exceeds maximum {}", + write_end, + self.btyes_total + ); + } + + let bufs = self.bufs.clone(); + let mut offset = 0_usize; + for buf in bufs { + let mut slice = &src[offset..]; + let write_count = cmp::min(slice.len(), buf.len as usize); + + sys_mem + .write(&mut slice, buf.addr, write_count as u64) + .chain_err(|| "Failed to read buffer for fuse req")?; + self.bytes_processed += write_count; + offset += write_count; + + // remove the processed bytes in self.bufs + if let Some(buftmp) = self.bufs.pop_front() { + if write_count < buftmp.len as usize { + //Add the remain length to the head of self.bufs + let len = buftmp.len - write_count as u32; + let addr = buftmp.addr.unchecked_add(write_count as u64); + let remain = ElemIovec { addr, len }; + self.bufs.push_front(remain); + break; + } + } + } + + Ok(()) + } + + /// write an object to the fuse buffers. + /// + /// # Arguments + /// + /// * `sys_mem` - Address space mapped with StratoVirt. + /// * `data` - The object the will be written to the fuse buffers. + /// + /// # Note + /// To use this method, it is necessary to implement `ByteCode` trait for your object. + pub fn write_obj(&mut self, sys_mem: &Arc, data: &T) -> Result<()> { + self.write_slice(sys_mem, data.as_bytes(), size_of::()) + } + + /// Process the data for host file. if is_read is true, writing the data which is read from host + /// file to the fuse buffers. if is_read is false, writing the data which is read from the fuse + /// buffers to host file. + /// + /// # Arguments + /// + /// * `sys_mem` - Address space mapped with StratoVirt. + /// * `fd` - The file descriptor in host. + /// * `offset` - The offset which needs to be read and written in host file. + /// * `size` - The size which needs to be read and written in host file. + /// * `is_read` - If it is true, writing the data which is read from host file to the fuse buffers. + /// If it is false, writing the data which is read from the fuse buffers to host file. + pub fn access_file( + &mut self, + sys_mem: &Arc, + fd: i32, + offset: u64, + size: u32, + is_read: bool, + ) -> Result { + let mut remain_len = size; + let mut file_off = offset; + + let mut index = 0; + let mut bufs = self.bufs.clone(); + + loop { + if index >= bufs.len() { + bail!("{} out of bufs's index", index); + } + + let buf = if let Some(b) = bufs.get_mut(index) { + b + } else { + bail!("{} out of bufs's bound", index); + }; + + let len = if remain_len < buf.len { + remain_len + } else { + buf.len + }; + + let hva = if let Some(hva) = sys_mem.get_host_address(buf.addr) { + hva + } else { + bail!("read file error: get hva failed."); + }; + + let iov = vec![libc::iovec { + iov_base: hva as *mut libc::c_void, + iov_len: len as usize, + }]; + + let ret = unsafe { + if is_read { + libc::preadv(fd, iov.as_ptr(), iov.len() as i32, file_off as i64) + } else { + libc::pwritev(fd, iov.as_ptr(), iov.len() as i32, file_off as i64) + } + } as u32; + if ret == u32::MAX { + bail!("read file error"); + } + + remain_len -= ret; + file_off += ret as u64; + + self.bufs.pop_front(); + if ret < len { + buf.addr.0 += ret as u64; + buf.len -= ret; + + self.bufs.push_front(*buf); + } else { + index += 1; + } + + if ret == 0 || remain_len == 0 { + break; // finish + } + } + + Ok(size - remain_len) + } +} + +/// Save the address and the length for replying fuse message. +pub struct FuseIovec<'a> { + body: &'a [u8], + len: usize, +} + +impl<'a> FuseIovec<'a> { + /// Convert an object to the struct of FuseIovec for replying fuse message. + /// + /// # Arguments + /// + /// * `obj` - The object the will be converted to the struct of FuseIovec. + /// + /// # Note + /// To use this method, it is necessary to implement `ByteCode` trait for your object. + pub fn from_obj(obj: &'a T) -> Self { + let body = obj.as_bytes(); + FuseIovec { + body, + len: body.len(), + } + } + + /// Convert a slice to the struct of FuseIovec for replying fuse message. + /// + /// # Arguments + /// + /// * `obj` - The slice the will be converted to the struct of FuseIovec. + pub fn from_slice(body: &'a [u8]) -> Self { + FuseIovec { + body, + len: body.len(), + } + } +} + +/// Reply the fuse messages by writing the data to the writable fuse buffers. +/// +/// # Arguments +/// +/// * `writer` - The writable fuse buffers. +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `in_header` - The in_header reading from the read-only fuse buffers. +/// * `err` - The error number for processing the fuse message. If it is ok, set +/// error number to 0. If it is false, set error number from linux. +/// * `body_opt` - The body for replying the fuse message needs to be written +/// to fuse buffers. +/// * `body_len` - The length fo body for replying the fuse message. if the body +/// is none, set the length to 0. +pub fn reply_fuse_msg( + writer: &mut FuseBuffer, + sys_mem: &Arc, + in_header: &FuseInHeader, + err: i32, + body_opt: Option>, + body_len: usize, +) -> u32 { + let len = size_of::() + body_len; + let mut written_len = len as u32; + + let fuse_out_header = FuseOutHeader { + len: len as u32, + error: -err, + unique: in_header.unique, + }; + + if let Err(e) = writer.write_obj(sys_mem, &fuse_out_header) { + error!( + "Failed to write out_header of fuse msg {}, {}", + in_header.opcode, + e.display_chain(), + ); + written_len = 0_u32; + }; + + //write the body of fuse message in address space + if let Some(body) = body_opt { + for fuse_iov in body.iter() { + if let Err(e) = writer.write_slice(sys_mem, fuse_iov.body, fuse_iov.len) { + error!( + "Failed to write the body of fuse msg {}, {}", + in_header.opcode, + e.display_chain(), + ); + written_len = 0_u32; + } + } + } + + written_len +} + +pub const FUSE_LOOKUP: u32 = 1; +pub const FUSE_FORGET: u32 = 2; +pub const FUSE_GETATTR: u32 = 3; +pub const FUSE_SETATTR: u32 = 4; +pub const FUSE_READLINK: u32 = 5; +pub const FUSE_SYMLINK: u32 = 6; +pub const FUSE_MKNOD: u32 = 8; +pub const FUSE_MKDIR: u32 = 9; +pub const FUSE_UNLINK: u32 = 10; +pub const FUSE_RMDIR: u32 = 11; +pub const FUSE_RENAME: u32 = 12; +pub const FUSE_LINK: u32 = 13; +pub const FUSE_OPEN: u32 = 14; +pub const FUSE_READ: u32 = 15; +pub const FUSE_WRITE: u32 = 16; +pub const FUSE_STATFS: u32 = 17; +pub const FUSE_RELEASE: u32 = 18; +pub const FUSE_FSYNC: u32 = 20; +pub const FUSE_SETXATTR: u32 = 21; +pub const FUSE_GETXATTR: u32 = 22; +pub const FUSE_LISTXATTR: u32 = 23; +pub const FUSE_REMOVEXATTR: u32 = 24; +pub const FUSE_FLUSH: u32 = 25; +pub const FUSE_INIT: u32 = 26; +pub const FUSE_OPENDIR: u32 = 27; +pub const FUSE_READDIR: u32 = 28; +pub const FUSE_RELEASEDIR: u32 = 29; +pub const FUSE_FSYNCDIR: u32 = 30; +pub const FUSE_GETLK: u32 = 31; +pub const FUSE_SETLK: u32 = 32; +pub const FUSE_SETLKW: u32 = 33; +pub const FUSE_ACCESS: u32 = 34; +pub const FUSE_CREATE: u32 = 35; +pub const FUSE_INTERRUPT: u32 = 36; +pub const FUSE_BMAP: u32 = 37; +pub const FUSE_DESTROY: u32 = 38; +pub const FUSE_IOCTL: u32 = 39; +pub const FUSE_POLL: u32 = 40; +pub const FUSE_NOTIFY_REPLY: u32 = 41; +pub const FUSE_BATCH_FORGET: u32 = 42; +pub const FUSE_FALLOCATE: u32 = 43; +pub const FUSE_READDIRPLUS: u32 = 44; +pub const FUSE_RENAME2: u32 = 45; +pub const FUSE_LSEEK: u32 = 46; +pub const FUSE_COPY_FILE_RANGE: u32 = 47; +pub const FUSE_SETUPMAPPING: u32 = 48; +pub const FUSE_REMOVEMAPPING: u32 = 49; + +/// The kernel version which is supported by fuse messages. +pub const FUSE_KERNEL_VERSION: u32 = 7; +/// The minor version which is supported by fuse messages. +pub const FUSE_KERNEL_MINOR_VERSION: u32 = 32; + +/// The capability bit supports asynchronous read requests. +pub const FUSE_CAP_ASYNC_READ: u32 = 1 << 0; +/// The capability bit supports posix file locks. +pub const FUSE_CAP_POSIX_LOCKS: u32 = 1 << 1; +/// The capability bit supports the O_TRUNC open flag. +pub const FUSE_CAP_ATOMIC_O_TRUNC: u32 = 1 << 3; +/// The capability bit supports lookups of "." and "..". +pub const FUSE_CAP_EXPORT_SUPPORT: u32 = 1 << 4; +/// The capability bit don't apply umask to file mode on create operation. +pub const FUSE_CAP_DONT_MASK: u32 = 1 << 6; +/// The capability bit supports BSD file locks. +pub const FUSE_CAP_FLOCK_LOCKS: u32 = 1 << 10; +/// The capability bit automatically checks invalid cached file. +pub const FUSE_CAP_AUTO_INVAL_DATA: u32 = 1 << 12; +/// The capability bit supports readdirplus. +pub const FUSE_CAP_READDIRPLUS: u32 = 1 << 13; +/// The capability bit supports adaptive readdirplus. +pub const FUSE_CAP_READDIRPLUS_AUTO: u32 = 1 << 14; +/// The capability bit supports asynchronous direct I/O submission. +pub const FUSE_CAP_ASYNC_DIO: u32 = 1 << 15; +/// The capability bit supports for parallel directory operations. +pub const FUSE_CAP_PARALLEL_DIROPS: u32 = 1 << 18; +/// The capability bit supports POSIX ACLs. +pub const FUSE_CAP_POSIX_ACL: u32 = 1 << 19; + +/// The supported bit that supports asynchronous read requests. +pub const FUSE_ASYNC_READ: u32 = 1 << 0; +/// The supported bit that supports posix file locks. +pub const FUSE_POSIX_LOCKS: u32 = 1 << 1; +/// The supported bit that supports the O_TRUNC open flag. +pub const FUSE_ATOMIC_O_TRUNC: u32 = 1 << 3; +/// The supported bit that supports lookups of "." and "..". +pub const FUSE_EXPORT_SUPPORT: u32 = 1 << 4; +/// The supported bit that don't apply umask to file mode on create operation. +pub const FUSE_DONT_MASK: u32 = 1 << 6; +/// The supported bit that supports BSD file locks. +pub const FUSE_FLOCK_LOCKS: u32 = 1 << 10; +/// The supported bit that automatically checks invalid cached file. +pub const FUSE_AUTO_INVAL_DATA: u32 = 1 << 12; +/// The supported bit that supports readdirplus. +pub const FUSE_DO_READDIRPLUS: u32 = 1 << 13; +/// The supported bit that supports adaptive readdirplus. +pub const FUSE_READDIRPLUS_AUTO: u32 = 1 << 14; +/// The supported bit that supports asynchronous direct I/O submission. +pub const FUSE_ASYNC_DIO: u32 = 1 << 15; +/// The capability bit that supports for parallel directory operations. +pub const FUSE_PARALLEL_DIROPS: u32 = 1 << 18; +/// The capability bit that supports POSIX ACLs. +pub const FUSE_POSIX_ACL: u32 = 1 << 20; +/// The capability bit that needs to reply the max number of pages in init fuse message. +pub const FUSE_MAX_PAGES: u32 = 1 << 22; + +pub const FATTR_MODE: u32 = 1 << 0; +pub const FATTR_UID: u32 = 1 << 1; +pub const FATTR_GID: u32 = 1 << 2; +pub const FATTR_SIZE: u32 = 1 << 3; +pub const FATTR_ATIME: u32 = 1 << 4; +pub const FATTR_MTIME: u32 = 1 << 5; +pub const FATTR_FH: u32 = 1 << 6; +pub const FATTR_ATIME_NOW: u32 = 1 << 7; +pub const FATTR_MTIME_NOW: u32 = 1 << 8; +pub const FATTR_LOCKOWNER: u32 = 1 << 9; +pub const FATTR_CTIME: u32 = 1 << 10; + +pub const FUSE_SET_ATTR_MODE: u32 = 1 << 0; +pub const FUSE_SET_ATTR_UID: u32 = 1 << 1; +pub const FUSE_SET_ATTR_GID: u32 = 1 << 2; +pub const FUSE_SET_ATTR_SIZE: u32 = 1 << 3; +pub const FUSE_SET_ATTR_ATIME: u32 = 1 << 4; +pub const FUSE_SET_ATTR_MTIME: u32 = 1 << 5; +pub const FUSE_SET_ATTR_ATIME_NOW: u32 = 1 << 7; +pub const FUSE_SET_ATTR_MTIME_NOW: u32 = 1 << 8; +pub const FUSE_SET_ATTR_CTIME: u32 = 1 << 10; + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseInHeader { + pub len: u32, + pub opcode: u32, + pub unique: u64, + pub nodeid: u64, + pub uid: u32, + pub gid: u32, + pub pid: u32, + pub padding: u32, +} + +impl ByteCode for FuseInHeader {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseOutHeader { + pub len: u32, + pub error: i32, + pub unique: u64, +} + +impl ByteCode for FuseOutHeader {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseAttr { + pub ino: u64, + pub size: u64, + pub blocks: u64, + pub atime: u64, + pub mtime: u64, + pub ctime: u64, + pub atimensec: u32, + pub mtimensec: u32, + pub ctimensec: u32, + pub mode: u32, + pub nlink: u32, + pub uid: u32, + pub gid: u32, + pub rdev: u32, + pub blksize: u32, + pub flags: u32, +} + +impl ByteCode for FuseAttr {} + +impl FuseAttr { + pub fn from_stat(stat: libc::stat) -> Self { + FuseAttr { + ino: stat.st_ino, + size: stat.st_size as u64, + blocks: stat.st_blocks as u64, + atime: stat.st_atime as u64, + mtime: stat.st_mtime as u64, + ctime: stat.st_ctime as u64, + atimensec: stat.st_atime_nsec as u32, + mtimensec: stat.st_mtime_nsec as u32, + ctimensec: stat.st_ctime_nsec as u32, + mode: stat.st_mode, + nlink: stat.st_nlink as u32, + uid: stat.st_uid, + gid: stat.st_gid, + rdev: stat.st_rdev as u32, + blksize: stat.st_blksize as u32, + flags: 0, + } + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseEntryOut { + pub nodeid: u64, + pub generation: u64, + pub entry_valid: u64, + pub attr_valid: u64, + pub entry_valid_nsec: u32, + pub attr_valid_nsec: u32, + pub attr: FuseAttr, +} + +impl ByteCode for FuseEntryOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseForgetIn { + pub nlookup: u64, +} + +impl ByteCode for FuseForgetIn {} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseAttrOut { + pub attr_valid: u64, + pub attr_valid_nsec: u32, + pub dummy: u32, + pub attr: FuseAttr, +} + +impl ByteCode for FuseAttrOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseGetAttrIn { + pub getattr_flags: u32, + pub dummy: u32, + pub fh: u64, +} + +impl ByteCode for FuseGetAttrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseSetattrIn { + pub valid: u32, + pub padding: u32, + pub fh: u64, + pub size: u64, + pub lock_owner: u64, + pub atime: u64, + pub mtime: u64, + pub ctime: u64, + pub atimensec: u32, + pub mtimensec: u32, + pub ctimensec: u32, + pub mode: u32, + pub unused4: u32, + pub uid: u32, + pub gid: u32, + pub unused5: u32, +} + +impl ByteCode for FuseSetattrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseMknodIn { + pub mode: u32, + pub rdev: u32, + pub umask: u32, + pub padding: u32, +} + +impl ByteCode for FuseMknodIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseMkdirIn { + pub mode: u32, + pub umask: u32, +} + +impl ByteCode for FuseMkdirIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseRenameIn { + pub newdir: u64, +} + +impl ByteCode for FuseRenameIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseLinkIn { + pub oldnodeid: u64, +} + +impl ByteCode for FuseLinkIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseOpenIn { + pub flags: u32, + pub unused: u32, +} + +impl ByteCode for FuseOpenIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseOpenOut { + pub fh: u64, + pub open_flags: u32, + pub padding: u32, +} + +impl ByteCode for FuseOpenOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseReadIn { + pub fh: u64, + pub offset: u64, + pub size: u32, + pub read_flags: u32, + pub lock_owner: u64, + pub flags: u32, + pub padding: u32, +} + +impl ByteCode for FuseReadIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseWriteIn { + pub fh: u64, + pub offset: u64, + pub size: u32, + pub write_flags: u32, + pub lock_owner: u64, + pub flags: u32, + pub padding: u32, +} + +impl ByteCode for FuseWriteIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseWriteOut { + pub size: u32, + pub padding: u32, +} + +impl ByteCode for FuseWriteOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseKstatfs { + pub blocks: u64, + pub bfree: u64, + pub bavail: u64, + pub files: u64, + pub ffree: u64, + pub bsize: u32, + pub namelen: u32, + pub frsize: u32, + pub padding: u32, + pub spare: [u32; 6], +} + +impl ByteCode for FuseKstatfs {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseStatfsOut { + pub st: FuseKstatfs, +} + +impl ByteCode for FuseStatfsOut {} + +impl FuseStatfsOut { + pub fn from_stat(stat: libc::statvfs) -> Self { + let st = FuseKstatfs { + blocks: stat.f_blocks, + bfree: stat.f_bfree, + bavail: stat.f_bavail, + files: stat.f_files, + ffree: stat.f_ffree, + bsize: stat.f_bsize as u32, + namelen: stat.f_namemax as u32, + frsize: stat.f_frsize as u32, + ..Default::default() + }; + + FuseStatfsOut { st } + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseReleaseIn { + pub fh: u64, + pub flags: u32, + pub release_flags: u32, + pub lock_owner: u64, +} + +impl ByteCode for FuseReleaseIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseFsyncIn { + pub fh: u64, + pub fsync_flags: u32, + pub padding: u32, +} + +impl ByteCode for FuseFsyncIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseSetxattrIn { + pub size: u32, + pub flags: u32, +} + +impl ByteCode for FuseSetxattrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseGetxattrIn { + pub size: u32, + pub padding: u32, +} + +impl ByteCode for FuseGetxattrIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseInitIn { + pub major: u32, + pub minor: u32, + pub max_readahead: u32, + pub flags: u32, +} + +impl ByteCode for FuseInitIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseInitOut { + pub major: u32, + pub minor: u32, + pub max_readahead: u32, + pub flags: u32, + pub max_background: u16, + pub congestion_threshold: u16, + pub max_write: u32, + pub time_gran: u32, + pub max_pages: u16, + pub map_alignment: u16, + pub unused: [u32; 8], +} + +impl ByteCode for FuseInitOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseDirent { + pub ino: u64, + pub off: u64, + pub namelen: u32, + pub type_: u32, + pub name: [u8; 0], +} + +impl ByteCode for FuseDirent {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseDirentplus { + pub entry_out: FuseEntryOut, + pub dirent: FuseDirent, +} + +impl ByteCode for FuseDirentplus {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseFlushIn { + pub fh: u64, + pub unused: u32, + pub padding: u32, + pub lock_owner: u64, +} + +impl ByteCode for FuseFlushIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseFileLock { + pub start: u64, + pub end: u64, + pub lock_type: u32, + pub pid: u32, +} + +impl ByteCode for FuseFileLock {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseLkIn { + pub fh: u64, + pub owner: u64, + pub lk: FuseFileLock, + pub lk_flags: u32, + pub padding: u32, +} + +impl ByteCode for FuseLkIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseLkOut { + pub lk: FuseFileLock, +} + +impl ByteCode for FuseLkOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseCreateIn { + pub flags: u32, + pub mode: u32, + pub umask: u32, + pub padding: u32, +} + +impl ByteCode for FuseCreateIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseBatchForgetIn { + pub count: u32, + pub dummy: u32, +} + +impl ByteCode for FuseBatchForgetIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseForgetDataIn { + pub ino: u64, + pub nlookup: u64, +} + +impl ByteCode for FuseForgetDataIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseFallocateIn { + pub fh: u64, + pub offset: u64, + pub length: u64, + pub mode: u32, + pub padding: u32, +} + +impl ByteCode for FuseFallocateIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseLseekIn { + pub fh: u64, + pub offset: u64, + pub whence: u32, + pub padding: u32, +} + +impl ByteCode for FuseLseekIn {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FuseLseekOut { + pub offset: u64, +} + +impl ByteCode for FuseLseekOut {} diff --git a/vhost_user_fs/src/fuse_proc.rs b/vhost_user_fs/src/fuse_proc.rs new file mode 100644 index 0000000000000000000000000000000000000000..df3bbc9023b14c631b9bfcefaf0a819273b2db02 --- /dev/null +++ b/vhost_user_fs/src/fuse_proc.rs @@ -0,0 +1,1749 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::ffi::CString; +use std::mem; +use std::sync::{Arc, Mutex}; + +use error_chain::ChainedError; + +use address_space::AddressSpace; + +use super::fs::FileSystem; +use super::fuse_msg::*; + +fn is_safe_path(path: CString) -> bool { + let path_str = match path.into_string() { + Ok(str_) => str_, + Err(_e) => return false, + }; + + if path_str.find('/').is_some() { + return false; + } + + //Check if the path is "." or ".." + let bytes = path_str.as_bytes(); + if bytes[0] == 0x2e && (bytes[1] == 0x0 || (bytes[1] == 0x2e && bytes[2] == 0x0)) { + return false; + } + + true +} + +/// Process the fuse message of FUSE_LOOKUP. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_lookup( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for lookup, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut fuse_attr = FuseAttr::default(); + let mut node_id = 0_u64; + let ret = fs.lock().unwrap().lookup( + in_header.nodeid as usize, + name, + &mut node_id, + &mut fuse_attr, + ); + + if ret == FUSE_OK { + let entry_out = FuseEntryOut { + nodeid: node_id, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: fuse_attr, + }; + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&entry_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_FORGET. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_forget( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let forget_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for forget_in, {}", e.display_chain()); + return 0_u32; + } + }; + + fs.lock() + .unwrap() + .forget(in_header.nodeid as usize, forget_in.nlookup); + 0_u32 +} + +/// Process the fuse message of FUSE_GETATTR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_getattr( + sys_mem: &Arc, + fs: Arc>, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let mut fuse_attr = FuseAttr::default(); + let ret = fs + .lock() + .unwrap() + .getattr(in_header.nodeid as usize, &mut fuse_attr); + if ret == 0 { + let attr_out = FuseAttrOut { + attr_valid: 1, + attr_valid_nsec: 0, + dummy: 0, + attr: fuse_attr, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&attr_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_SETATTR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_setattr( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let setattr_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for setattr_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut fuse_attr = FuseAttr::default(); + let ret = fs + .lock() + .unwrap() + .setattr(in_header.nodeid as usize, &setattr_in, &mut fuse_attr); + if ret == FUSE_OK { + let attr_out = FuseAttrOut { + attr_valid: 1, + attr_valid_nsec: 0, + dummy: 0, + attr: fuse_attr, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&attr_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_READLINK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_readlink( + sys_mem: &Arc, + fs: Arc>, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let mut buff = Vec::new(); + let ret = fs + .lock() + .unwrap() + .readlink(in_header.nodeid as usize, &mut buff); + + if ret == FUSE_OK { + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_slice(buff.as_slice())]), + buff.len(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_SYMLINK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_symlink( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for symlink, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let link_name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!( + "Failed to read link name for symlink, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(link_name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let mut node_id = 0_u64; + let mut fuse_attr = FuseAttr::default(); + let ret = fs + .lock() + .unwrap() + .symlink(in_header, name, link_name, &mut node_id, &mut fuse_attr); + if ret == FUSE_OK { + let entry_out = FuseEntryOut { + nodeid: node_id, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: fuse_attr, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_obj(&entry_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_MKNOD. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_mknod( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let mknod_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for mknod_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for mknod, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let mut node_id = 0_u64; + let mut fuse_attr = FuseAttr::default(); + let ret = fs + .lock() + .unwrap() + .mknod(in_header, &mknod_in, name, &mut node_id, &mut fuse_attr); + if ret == FUSE_OK { + let entry_out = FuseEntryOut { + nodeid: node_id, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: fuse_attr, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_obj(&entry_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_MKDIR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_mkdir( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let mkdir_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for mkdir_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for mkdir, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let mut node_id = 0_u64; + let mut fuse_attr = FuseAttr::default(); + let ret = fs + .lock() + .unwrap() + .mkdir(in_header, &mkdir_in, name, &mut node_id, &mut fuse_attr); + if ret == FUSE_OK { + let entry_out = FuseEntryOut { + nodeid: node_id, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: fuse_attr, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_obj(&entry_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_UNLINK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_unlink( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for unlink, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let ret = fs.lock().unwrap().unlink(in_header.nodeid as usize, name); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_RMDIR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_rmdir( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for rmdir, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let ret = fs.lock().unwrap().rmdir(in_header.nodeid as usize, name); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_RENAME. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_rename( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let rename_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for rename_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let oldname = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read old name for rename, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let newname = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read new name for rename, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(oldname.clone()) || !is_safe_path(newname.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let ret = fs.lock().unwrap().rename( + in_header.nodeid as usize, + oldname, + rename_in.newdir as usize, + newname, + ); + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_LINK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_link( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let link_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for link_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for link, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let mut fuse_attr = FuseAttr::default(); + let mut node_id = 0_u64; + let ret = fs.lock().unwrap().link( + in_header.nodeid as usize, + link_in.oldnodeid as usize, + name, + &mut node_id, + &mut fuse_attr, + ); + + if ret == FUSE_OK { + let entry_out = FuseEntryOut { + nodeid: node_id, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: fuse_attr, + }; + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&entry_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_OPEN. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_open( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let open_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for open_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut fh = 0_u64; + let ret = fs + .lock() + .unwrap() + .open(in_header.nodeid as usize, open_in.flags, &mut fh); + if ret == FUSE_OK { + let open_out = FuseOpenOut { + fh, + open_flags: 0, + padding: 0, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&open_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_READ. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_read( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let read_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for read_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut fd = 0; + let ret = fs.lock().unwrap().read(read_in.fh as usize, &mut fd); + if ret == FUSE_OK { + let buf_header = match writer.bufs.get(0) { + Some(b) => *b, + None => { + error!("Failed to get the address of out_header"); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut f_ret = reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize); + + match writer.access_file(sys_mem, fd, read_in.offset, read_in.size, true) { + Ok(size) => { + f_ret += size; + // write size to FuseOutHeader.len + sys_mem.write_object(&f_ret, buf_header.addr).unwrap(); + } + Err(e) => { + error!("Failed to access file for reading, {}", e.display_chain()); + } + }; + + f_ret + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_WRITE. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_write( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let write_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for write_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut fd = 0; + let ret = fs.lock().unwrap().write(write_in.fh as usize, &mut fd); + if ret == FUSE_OK { + match reader.access_file(sys_mem, fd, write_in.offset, write_in.size, false) { + Ok(size) => { + let write_out = FuseWriteOut { size, padding: 0 }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&write_out)]), + mem::size_of::(), + ) + } + Err(e) => { + error!("Failed to access file for writing, {}", e.display_chain()); + reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize) + } + } + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_STATFS. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_statfs( + sys_mem: &Arc, + fs: Arc>, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let mut statfs = FuseStatfsOut::default(); + let ret = fs + .lock() + .unwrap() + .statfs(in_header.nodeid as usize, &mut statfs); + if ret == FUSE_OK { + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&statfs)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_RELEASE. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_release( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let release_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for release_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let ret = fs.lock().unwrap().release(release_in.fh as usize); + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_FSYNC. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_fsync( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let fsync_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read name for fsync_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let datasync = fsync_in.fsync_flags & 0x1 == 0x1; + + let ret = fs.lock().unwrap().fsyncfile(fsync_in.fh as usize, datasync); + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_SETXATTR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_setxattr( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let setxattr_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for setxattr_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for setxattr_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let value = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!( + "Failed to read value for setxattr_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let ret = fs.lock().unwrap().setxattr( + in_header.nodeid as usize, + name, + value, + setxattr_in.size, + setxattr_in.flags, + ); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_GETXATTR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_getxattr( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let getxattr_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for getxattr_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!("Failed to read name for getxattr_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut buff = Vec::new(); + let ret = + fs.lock() + .unwrap() + .getxattr(in_header.nodeid as usize, name, getxattr_in.size, &mut buff); + if ret == FUSE_OK { + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_slice(buff.as_slice())]), + buff.len(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_LISTXATTR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_listxattr( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let getxattr_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for listxattr_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut buff = Vec::new(); + let ret = fs + .lock() + .unwrap() + .listxattr(in_header.nodeid as usize, getxattr_in.size, &mut buff); + if ret == FUSE_OK { + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_slice(buff.as_slice())]), + buff.len(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_REMOVEXATTR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_removexattr( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let name = match reader.read_cstring(sys_mem) { + Ok(s) => s, + Err(e) => { + error!( + "Failed to read name for removexattr_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let ret = fs + .lock() + .unwrap() + .removexattr(in_header.nodeid as usize, name); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_FLUSH. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_flush( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let flush_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for flush_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let ret = fs + .lock() + .unwrap() + .flush(in_header.nodeid as usize, flush_in.lock_owner); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_INIT. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_init( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let init_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for init_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut support_flags = 0_u32; + fs.lock().unwrap().init(init_in.flags, &mut support_flags); + let init_out = FuseInitOut { + major: FUSE_KERNEL_VERSION, + minor: FUSE_KERNEL_MINOR_VERSION, + max_readahead: 131072, + flags: support_flags, + max_background: 0, + congestion_threshold: 0, + max_write: 1048576, + time_gran: 1, + max_pages: 256, + map_alignment: 0, + ..Default::default() + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_obj(&init_out)]), + mem::size_of::(), + ) +} + +/// Process the fuse message of FUSE_OPENDIR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_opendir( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let _open_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for opendir_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut dir_fh = 0_u64; + let ret = fs + .lock() + .unwrap() + .opendir(in_header.nodeid as usize, &mut dir_fh); + if ret == FUSE_OK { + let open_out = FuseOpenOut { + fh: dir_fh, + open_flags: 0, + padding: 0, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_obj(&open_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_READDIR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_readdir( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let read_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for readdir_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut buff = Vec::new(); + let ret = fs.lock().unwrap().readdir( + in_header.nodeid as usize, + read_in.fh as usize, + read_in.size, + read_in.offset, + false, + &mut buff, + ); + if ret == FUSE_OK { + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_slice(buff.as_slice())]), + buff.len(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_RELEASEDIR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_releasedir( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let release_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for releasedir_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let ret = fs.lock().unwrap().releasedir(release_in.fh as usize); + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_FSYNCDIR. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_fsyncdir( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let fsync_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for fsync_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let datasync = fsync_in.fsync_flags & 0x1 == 0x1; + let ret = fs.lock().unwrap().fsyncdir(fsync_in.fh as usize, datasync); + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_GETLK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_getlk( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let lk_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for get_lk_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut file_lock = FuseFileLock::default(); + let ret = fs.lock().unwrap().getlk( + in_header.nodeid as usize, + lk_in.owner, + &lk_in.lk, + &mut file_lock, + ); + + if ret == FUSE_OK { + let lk_out = FuseLkOut { lk: file_lock }; + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_obj(&lk_out)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +fn do_fuse_setlk_common( + sys_mem: &Arc, + fs: Arc>, + lk_in: &FuseLkIn, + is_blocking: bool, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let ret = fs.lock().unwrap().setlk( + in_header.nodeid as usize, + lk_in.owner, + is_blocking, + &lk_in.lk, + ); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +fn do_fuse_flock( + sys_mem: &Arc, + fs: Arc>, + lk_in: &FuseLkIn, + is_blocking: bool, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let ret = fs + .lock() + .unwrap() + .flock(lk_in.fh as usize, lk_in.lk.lock_type, is_blocking); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +const FUSE_LOCK_FLOCK: u32 = (1 << 0) as u32; + +/// Process the fuse message of FUSE_SETLK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_setlk( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let lk_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for set_lk_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if lk_in.lk_flags & FUSE_LOCK_FLOCK == FUSE_LOCK_FLOCK { + do_fuse_flock(sys_mem, fs, &lk_in, false, writer, in_header) + } else { + do_fuse_setlk_common(sys_mem, fs, &lk_in, false, writer, in_header) + } +} + +/// Process the fuse message of FUSE_SETLKW. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_setlkw( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let lk_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for setlkw_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if lk_in.lk_flags & FUSE_LOCK_FLOCK == FUSE_LOCK_FLOCK { + do_fuse_flock(sys_mem, fs, &lk_in, true, writer, in_header) + } else { + do_fuse_setlk_common(sys_mem, fs, &lk_in, true, writer, in_header) + } +} + +/// Process the fuse message of FUSE_CREATE. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_create( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let create_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for create_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let name = match reader.read_cstring(sys_mem) { + Ok(string) => string, + Err(e) => { + error!( + "Failed to read name for creating file, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + if !is_safe_path(name.clone()) { + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + + let mut fh = 0_u64; + let mut node_id = 0_u64; + let mut fuse_attr = FuseAttr::default(); + let ret = fs.lock().unwrap().create( + in_header, + &create_in, + name, + &mut fh, + &mut node_id, + &mut fuse_attr, + ); + if ret == FUSE_OK { + let entry_out = FuseEntryOut { + nodeid: node_id, + generation: 0, + entry_valid: 0, + entry_valid_nsec: 0, + attr_valid: 0, + attr_valid_nsec: 0, + attr: fuse_attr, + }; + + let open_out = FuseOpenOut { + fh: fh as u64, + open_flags: 0, + padding: 0, + }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![ + FuseIovec::from_obj(&entry_out), + FuseIovec::from_obj(&open_out), + ]), + mem::size_of::() + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_DESTROY. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_destroy( + sys_mem: &Arc, + fs: Arc>, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let ret = fs.lock().unwrap().destroy(); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_BATCH_FORGET. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +pub fn do_fuse_batch_forget( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, +) -> u32 { + let batch_forget_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for batch_forget_in, {}", + e.display_chain() + ); + return 0_u32; + } + }; + + for _i in 0..batch_forget_in.count as usize { + let forget_data_in = match reader.read_obj::(sys_mem) { + Ok(data) => data, + Err(e) => { + error!( + "Failed to read object for forget_date_in, {}", + e.display_chain() + ); + return 0; + } + }; + + fs.lock() + .unwrap() + .forget(forget_data_in.ino as usize, forget_data_in.nlookup); + } + + 0_u32 +} + +/// Process the fuse message of FUSE_FALLOCATE. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_fallocate( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let fallocate_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for fallocate_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let ret = fs.lock().unwrap().fallocate( + fallocate_in.fh as usize, + fallocate_in.mode, + fallocate_in.offset, + fallocate_in.length, + ); + + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) +} + +/// Process the fuse message of FUSE_READDIRPLUS. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_readdirplus( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let read_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!( + "Failed to read object for readdirplus_in, {}", + e.display_chain() + ); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut buff = Vec::new(); + let ret = fs.lock().unwrap().readdir( + in_header.nodeid as usize, + read_in.fh as usize, + read_in.size, + read_in.offset, + true, + &mut buff, + ); + if ret == FUSE_OK { + reply_fuse_msg( + writer, + sys_mem, + in_header, + FUSE_OK, + Some(vec![FuseIovec::from_slice(buff.as_slice())]), + buff.len(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} + +/// Process the fuse message of FUSE_LSEEK. +/// +/// # Arguments +/// +/// * `sys_mem` - Address space mapped with StratoVirt. +/// * `fs` - The management of userspace filesystem. +/// * `reader` - The read-only buffers parsed from the element of virtio queue. +/// * `writer` - The write-only buffers parsed from the element of virtio queue. +/// * `in_header` - The in_header reading from the read-only buffers. +pub fn do_fuse_lseek( + sys_mem: &Arc, + fs: Arc>, + reader: &mut FuseBuffer, + writer: &mut FuseBuffer, + in_header: &FuseInHeader, +) -> u32 { + let lseek_in = match reader.read_obj::(sys_mem) { + Ok(d) => d, + Err(e) => { + error!("Failed to read object for lseek_in, {}", e.display_chain()); + return reply_fuse_msg(writer, sys_mem, in_header, libc::EINVAL, None, 0_usize); + } + }; + + let mut outoffset = 0_u64; + let ret = fs.lock().unwrap().lseek( + lseek_in.fh as usize, + lseek_in.offset, + lseek_in.whence, + &mut outoffset, + ); + + if ret == FUSE_OK { + let lseekout = FuseLseekOut { offset: outoffset }; + + reply_fuse_msg( + writer, + sys_mem, + in_header, + ret, + Some(vec![FuseIovec::from_obj(&lseekout)]), + mem::size_of::(), + ) + } else { + reply_fuse_msg(writer, sys_mem, in_header, ret, None, 0_usize) + } +} diff --git a/vhost_user_fs/src/fuse_req.rs b/vhost_user_fs/src/fuse_req.rs new file mode 100644 index 0000000000000000000000000000000000000000..10bdd2f49f9de757b534865b04763886d1e95b6b --- /dev/null +++ b/vhost_user_fs/src/fuse_req.rs @@ -0,0 +1,438 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use error_chain::ChainedError; + +use address_space::AddressSpace; +use device_model::virtio::queue::Element; +use std::sync::{Arc, Mutex}; + +use super::fs::FileSystem; +use super::fuse_msg::*; +use super::fuse_proc::*; + +/// The request of fuse message parsed from virtio queue. +pub struct FuseReq { + desc_index: u16, + reader: FuseBuffer, + writer: FuseBuffer, +} + +impl FuseReq { + /// Construct a request of fuse message by the element from virtio queue. + /// + /// # Arguments + /// + /// * `elem` - The element parsed from virtio queue. + pub fn new(elem: &Element) -> Self { + FuseReq { + desc_index: elem.index, + reader: FuseBuffer::new(&elem.out_iovec), + writer: FuseBuffer::new(&elem.in_iovec), + } + } + + /// The function to deal with fuse message from the request. + /// + /// # Arguments + /// + /// * `sys_mem` - Address space mapped with StratoVirt. + /// * `fs` - The management of userspace filesystem. + pub fn execute( + &mut self, + sys_mem: &Arc, + fs: Arc>, + ) -> (u16, u32) { + let in_header = match self.reader.read_obj::(sys_mem) { + Ok(data) => data, + Err(err) => { + error!( + "Failed to read the header of fuse msg, {}", + err.display_chain(), + ); + return (self.desc_index, 0); + } + }; + + let written_len = match in_header.opcode { + FUSE_LOOKUP => { + do_fuse_lookup(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_FORGET => do_fuse_forget(sys_mem, fs, &mut self.reader, &in_header), + FUSE_GETATTR => do_fuse_getattr(sys_mem, fs, &mut self.writer, &in_header), + FUSE_SETATTR => { + do_fuse_setattr(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_READLINK => do_fuse_readlink(sys_mem, fs, &mut self.writer, &in_header), + FUSE_SYMLINK => { + do_fuse_symlink(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_MKNOD => { + do_fuse_mknod(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_MKDIR => { + do_fuse_mkdir(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_UNLINK => { + do_fuse_unlink(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_RMDIR => { + do_fuse_rmdir(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_RENAME => { + do_fuse_rename(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_LINK => do_fuse_link(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header), + FUSE_OPEN => do_fuse_open(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header), + FUSE_READ => do_fuse_read(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header), + FUSE_WRITE => { + do_fuse_write(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_STATFS => do_fuse_statfs(sys_mem, fs, &mut self.writer, &in_header), + FUSE_RELEASE => { + do_fuse_release(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_FSYNC => { + do_fuse_fsync(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_SETXATTR => { + do_fuse_setxattr(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_GETXATTR => { + do_fuse_getxattr(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_LISTXATTR => { + do_fuse_listxattr(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_REMOVEXATTR => { + do_fuse_removexattr(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_FLUSH => { + do_fuse_flush(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_INIT => do_fuse_init(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header), + FUSE_OPENDIR => { + do_fuse_opendir(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_READDIR => { + do_fuse_readdir(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_RELEASEDIR => { + do_fuse_releasedir(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_FSYNCDIR => { + do_fuse_fsyncdir(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_GETLK => { + do_fuse_getlk(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_SETLK => { + do_fuse_setlk(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_SETLKW => { + do_fuse_setlkw(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_CREATE => { + do_fuse_create(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_DESTROY => do_fuse_destroy(sys_mem, fs, &mut self.writer, &in_header), + FUSE_BATCH_FORGET => do_fuse_batch_forget(sys_mem, fs, &mut self.reader), + FUSE_FALLOCATE => { + do_fuse_fallocate(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_READDIRPLUS => { + do_fuse_readdirplus(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + FUSE_LSEEK => { + do_fuse_lseek(sys_mem, fs, &mut self.reader, &mut self.writer, &in_header) + } + _ => { + error!("The fuse msg {} is unsupported", in_header.opcode); + reply_fuse_msg( + &mut self.writer, + sys_mem, + &in_header, + libc::ENOSYS, + None, + 0_usize, + ) + } + }; + + // return the index of element and the length which is written + (self.desc_index, written_len) + } +} + +#[cfg(test)] +mod tests { + pub use super::super::*; + pub use super::*; + use address_space::{AddressSpace, GuestAddress, HostMemMapping, Region}; + use device_model::virtio::queue::*; + use std::env::temp_dir; + use std::fs::File; + use std::mem::size_of; + use util::byte_code::ByteCode; + + const SYSTEM_SPACE_SIZE: u64 = (1024 * 1024) as u64; + + #[repr(C)] + #[derive(Debug, Default, Copy, Clone)] + pub struct LookUp { + pub name: [u8; 9], + } + + impl ByteCode for LookUp {} + + fn address_space_init() -> Arc { + let root = Region::init_container_region(1 << 36); + let sys_space = AddressSpace::new(root).unwrap(); + let host_mmap = Arc::new( + HostMemMapping::new(GuestAddress(0), SYSTEM_SPACE_SIZE, None, false, false).unwrap(), + ); + sys_space + .root() + .add_subregion( + Region::init_ram_region(host_mmap.clone()), + host_mmap.start_address().raw_value(), + ) + .unwrap(); + sys_space + } + + // Testcase call FUSE_LOOKUP operation, if the file metadata is written to mem_space, it will + // return the written len. And fuse out header error code equals to zero. + #[test] + fn test_lookup_file_success() { + // create a file named vhostreq + let source_dir = temp_dir(); + let file_path = source_dir.join("vhostreq"); + let _file = File::create(&file_path).unwrap(); + + let mem_space = address_space_init(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + + // reader buffer: FuseInHeader + name + let mut elem = Element::new(0); + elem.desc_num = 3; + let in_header_len = (size_of::() + size_of::()) as u32; + let iovec = ElemIovec { + addr: GuestAddress(0x00), + len: in_header_len, + }; + elem.out_iovec.push(iovec); + + // write buffers: FuseOutHeader buffer, FuseIovec.body buffer + let out_header_len = size_of::() as u32; + let iovec = ElemIovec { + addr: GuestAddress(0x100), + len: out_header_len, + }; + elem.in_iovec.push(iovec); + let iovec = ElemIovec { + addr: GuestAddress(0x200), + len: 128, + }; + elem.in_iovec.push(iovec); + let mut req = FuseReq::new(&elem); + + let head_len = size_of::() as u32; + let in_header = FuseInHeader { + len: head_len, + opcode: FUSE_LOOKUP, + unique: 42, + nodeid: 1, + uid: 0, + gid: 0, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x00)) + .unwrap(); + + // file name: vhostreq + let file_name = LookUp { + name: [118, 104, 111, 115, 116, 114, 101, 113, 0], + }; + let name_offset = std::mem::size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x00).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + let (_, written_len) = req.execute(&mem_space, filesystem.clone()); + // Confirm the FuseEntryOut data is written to mem_space + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x100)) + .unwrap(); + let reply_msg_len = (size_of::() + size_of::()) as u32; + assert_eq!(fuse_out_header.error, 0); + assert_eq!(fuse_out_header.len, reply_msg_len); + assert_eq!(fuse_out_header.unique, in_header.unique); + assert_eq!(written_len, reply_msg_len); + } + + // If reader buffer address is out of bounds, the fuse_out_header error code will be set to EINVAL + #[test] + fn test_lookup_addr_out_of_bounds() { + // create a file named vhostrea + let source_dir = temp_dir(); + let file_path = source_dir.join("vhostrea"); + let _file = File::create(&file_path).unwrap(); + + let mem_space = address_space_init(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + + // Address plus len is out of bounds + let mut elem = Element::new(0); + elem.desc_num = 3; + let in_header_len = (size_of::() + size_of::()) as u32; + let iovec = ElemIovec { + addr: GuestAddress(0x000f_ffd0), // invalid + len: in_header_len, + }; + elem.out_iovec.push(iovec); + + let out_header_len = size_of::() as u32; + let iovec = ElemIovec { + addr: GuestAddress(0x100), + len: out_header_len, + }; + elem.in_iovec.push(iovec); + let iovec = ElemIovec { + addr: GuestAddress(0x200), + len: 128, + }; + elem.in_iovec.push(iovec); + let mut req = FuseReq::new(&elem); + + let head_len = size_of::() as u32; + let in_header = FuseInHeader { + len: head_len, + opcode: FUSE_LOOKUP, + unique: 42, + nodeid: 1, + uid: 0, + gid: 0, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x000f_ffd0)) + .unwrap(); + + // file name: vhostrea + let file_name = LookUp { + name: [118, 104, 111, 115, 116, 114, 101, 97, 0], + }; + let name_offset = std::mem::size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x00).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + let (_, written_len) = req.execute(&mem_space, filesystem.clone()); + // Confirm the FuseEntryOut data is written to mem_space + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x100)) + .unwrap(); + let reply_msg_len = size_of::() as u32; + assert_eq!(-fuse_out_header.error, libc::EINVAL); + assert_eq!(fuse_out_header.unique, in_header.unique); + assert_eq!(fuse_out_header.len, reply_msg_len); + assert_eq!(written_len, reply_msg_len); + } + + // If reader buffer len is invalid, the fuse_out_header error code will be set to EINVAL + #[test] + fn test_lookup_invalid_elem_length() { + // create a file named vhostreb + let source_dir = temp_dir(); + let file_path = source_dir.join("vhostreb"); + let _file = File::create(&file_path).unwrap(); + + let mem_space = address_space_init(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + + // ElemIovec len is less than required. + let mut elem = Element::new(0); + elem.desc_num = 3; + let iovec = ElemIovec { + addr: GuestAddress(0x00), + len: 45, // Invalid length + }; + elem.out_iovec.push(iovec); + + let out_header_len = size_of::() as u32; + let iovec = ElemIovec { + addr: GuestAddress(0x100), + len: out_header_len, + }; + elem.in_iovec.push(iovec); + let iovec = ElemIovec { + addr: GuestAddress(0x200), + len: 128, + }; + elem.in_iovec.push(iovec); + let mut req = FuseReq::new(&elem); + + let head_len = size_of::() as u32; + let in_header = FuseInHeader { + len: head_len, + opcode: FUSE_LOOKUP, + unique: 42, + nodeid: 1, + uid: 0, + gid: 0, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x00)) + .unwrap(); + + // file name: vhostreb + let file_name = LookUp { + name: [118, 104, 111, 115, 116, 114, 101, 98, 0], + }; + let name_offset = std::mem::size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x00).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + let (_, written_len) = req.execute(&mem_space, filesystem.clone()); + // Confirm the FuseEntryOut data is written to mem_space + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x100)) + .unwrap(); + let reply_msg_len = size_of::() as u32; + assert_eq!(-fuse_out_header.error, libc::EINVAL); + assert_eq!(fuse_out_header.unique, in_header.unique); + assert_eq!(fuse_out_header.len, reply_msg_len); + assert_eq!(written_len, reply_msg_len); + } +} diff --git a/vhost_user_fs/src/lib.rs b/vhost_user_fs/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..273ea80714d5e4a231f041ef31fe86b7f7c8c4b7 --- /dev/null +++ b/vhost_user_fs/src/lib.rs @@ -0,0 +1,46 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate log; +extern crate vmm_sys_util; + +extern crate address_space; +extern crate device_model; +extern crate machine_manager; +extern crate util; + +pub mod cmdline; +pub mod fs; +pub mod fs_ops; +pub mod fuse_msg; +pub mod fuse_proc; +pub mod fuse_req; +pub mod vhost_user_fs; +pub mod vhost_user_server; +pub mod virtio_fs; + +pub mod errors { + error_chain! { + links { + Util(util::errors::Error, util::errors::ErrorKind); + DeviceModel(device_model::errors::Error, device_model::errors::ErrorKind); + Virtio(device_model::virtio::errors::Error, device_model::virtio::errors::ErrorKind); + AddressSpace(address_space::errors::Error, address_space::errors::ErrorKind); + } + foreign_links { + Io(std::io::Error); + } + } +} diff --git a/vhost_user_fs/src/vhost_user_fs.rs b/vhost_user_fs/src/vhost_user_fs.rs new file mode 100644 index 0000000000000000000000000000000000000000..9e6d7b8c2c8c2171d06d54a869aff3ab13c6ea09 --- /dev/null +++ b/vhost_user_fs/src/vhost_user_fs.rs @@ -0,0 +1,174 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::os::unix::io::RawFd; +use std::sync::{Arc, Mutex}; + +use error_chain::ChainedError; +use vmm_sys_util::epoll::EventSet; + +use machine_manager::event_loop::EventLoop; +use util::loop_context::{ + EventLoopManager, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation, +}; + +use super::cmdline::FsConfig; +use super::fs::set_rlimit_nofile; +use super::vhost_user_server::VhostUserServerHandler; +use super::virtio_fs::VirtioFs; + +use crate::errors::{Result, ResultExt}; + +/// The vhost-user filesystem device contains virtio fs device and the vhost-user +/// server which can be connected with the vhost-user client in StratoVirt. +#[derive(Clone)] +pub struct VhostUserFs { + server_handler: VhostUserServerHandler, +} + +trait CreateEventNotifier { + fn create_event_notifier( + &mut self, + server_handler: Arc>, + ) -> Option>; +} + +impl CreateEventNotifier for VhostUserServerHandler { + fn create_event_notifier( + &mut self, + server_handler: Arc>, + ) -> Option> { + let mut notifiers = Vec::new(); + if self.sock.is_server_accept() { + if let Err(e) = self.sock.server_connection_refuse() { + error!( + "Failed to refuse socket for vhost user server, {}", + e.display_chain() + ); + } + return None; + } else if let Err(e) = self.sock.server_accept() { + error!( + "Failed to accept the socket for vhost user server, {}", + e.display_chain() + ); + return None; + } + + let mut handlers = Vec::new(); + let handler: Box = Box::new(move |event, _| { + if event == EventSet::IN { + let mut lock_server_handler = server_handler.lock().unwrap(); + if let Err(e) = lock_server_handler.handle_request() { + error!( + "Failed to handle request for vhost user server, {}", + e.display_chain() + ); + } + } + + if event & EventSet::HANG_UP == EventSet::HANG_UP { + panic!("Receive the event of HANG_UP from stratovirt"); + } else { + None + } + }); + + handlers.push(Arc::new(Mutex::new(handler))); + + let notifier = EventNotifier::new( + NotifierOperation::AddShared, + self.sock.get_stream_raw_fd(), + None, + EventSet::IN | EventSet::HANG_UP, + handlers, + ); + + notifiers.push(notifier); + Some(notifiers) + } +} + +impl EventNotifierHelper for VhostUserServerHandler { + fn internal_notifiers(server_handler: Arc>) -> Vec { + let mut notifiers = Vec::new(); + + let server_handler_clone = server_handler.clone(); + let mut handlers = Vec::new(); + let handler: Box Option>> = + Box::new(move |_, _| { + server_handler_clone + .lock() + .unwrap() + .create_event_notifier(server_handler_clone.clone()) + }); + + handlers.push(Arc::new(Mutex::new(handler))); + + let notifier = EventNotifier::new( + NotifierOperation::AddShared, + server_handler.lock().unwrap().sock.get_listener_raw_fd(), + None, + EventSet::IN, + handlers, + ); + + notifiers.push(notifier); + + notifiers + } +} + +impl VhostUserFs { + /// Create a new vhost-user filesystem device. + /// + /// # Arguments + /// + /// * `fs_config` - Configuration of the vhost-user filesystem device. + pub fn new(fs_config: FsConfig) -> Result { + if let Some(limit) = fs_config.rlimit_nofile { + set_rlimit_nofile(limit) + .chain_err(|| format!("Failed to set rlimit nofile {}", limit))?; + } + + let virtio_fs = Arc::new(Mutex::new( + VirtioFs::new(&fs_config.source_dir) + .chain_err(|| format!("Failed to create virtio fs {}", fs_config.source_dir))?, + )); + + let server_handler = VhostUserServerHandler::new(&fs_config.sock_path, virtio_fs) + .chain_err(|| format!("Failed to create vhost user server {}", fs_config.sock_path))?; + Ok(VhostUserFs { server_handler }) + } + + /// Add events to epoll handler for the vhost-user filesystem device. + pub fn add_event_notifier(&self) -> Result<()> { + EventLoop::update_event( + EventNotifierHelper::internal_notifiers(Arc::new(Mutex::new( + self.server_handler.clone(), + ))), + None, + )?; + + Ok(()) + } +} + +impl EventLoopManager for VhostUserFs { + fn loop_should_exit(&self) -> bool { + false + } + + fn loop_cleanup(&self) -> util::errors::Result<()> { + Ok(()) + } +} diff --git a/vhost_user_fs/src/vhost_user_server.rs b/vhost_user_fs/src/vhost_user_server.rs new file mode 100644 index 0000000000000000000000000000000000000000..f6cf0872346a2d5522f8760675326dca7a7311a2 --- /dev/null +++ b/vhost_user_fs/src/vhost_user_server.rs @@ -0,0 +1,790 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::mem::size_of; +use std::os::unix::io::RawFd; +use std::slice; +use std::sync::{Arc, Mutex}; + +use device_model::virtio::vhost::user::vhost_user_msg::*; +use device_model::virtio::vhost::user::vhost_user_sock::{UnixDomainSock, MAX_ATTACHED_FD_ENTRIES}; +use util::unix::limit_permission; + +use crate::errors::{Result, ResultExt}; + +///the trait for dealing with vhost-user request in the server. +pub trait VhostUserReqHandler: Send + Sync { + /// Set the current process as the owner of this file descriptor. + fn set_owner(&mut self) -> Result<()>; + + /// Get a bitmask of supported virtio/vhost features. + fn get_features(&self) -> Result; + + /// Inform the vhost subsystem which features to enable. This should be a subset of + /// supported features from VHOST_GET_FEATURES. + /// + /// # Arguments + /// + /// * `features` - The features from the vhost-user client in StratoVirt. + fn set_features(&mut self, features: u64) -> Result<()>; + + /// Set the guest memory mappings for vhost to use. + /// + /// # Arguments + /// + /// * `regions` - The slice of memory region information for the message of memory table. + /// * `fds` - The files descriptors are used to map shared memory for the process and + /// StratoVirt. + fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegion], fds: &[RawFd]) -> Result<()>; + + /// Set the size of descriptors in the virtio queue. + /// + /// # Arguments + /// + /// * `queue_index` - The index of virtio queue. + /// * `num` - The total size of virtio queue. + fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>; + + /// Set the addresses for a given virtio queue. + /// + /// # Arguments + /// + /// * `queue_index` - The index of virtio queue. + /// * `flags` - Option flags. + /// * `desc_table` - The start address of descriptor table. + /// * `used_ring` - The start address of used ring. + /// * `avail_ring` - The start address of avail ring. + /// * `log` - The start address of log. + fn set_vring_addr( + &mut self, + queue_index: usize, + flags: u32, + desc_table: u64, + used_ring: u64, + avail_ring: u64, + log: u64, + ) -> Result<()>; + + /// Set the first index to look for available descriptors. + /// + /// # Arguments + /// + /// * `queue_index` - The index of virtio queue. + /// * `num` - the first index to look for available descriptors. + fn set_vring_base(&mut self, queue_index: usize, num: u16) -> Result<()>; + + /// Set the eventfd to trigger when buffers need to be processed + /// by the guest. + /// + /// # Arguments + /// + /// * `queue_index` - The index of virtio queue. + /// * `fd` - The files descriptor used to notify the guest. + fn set_vring_call(&mut self, queue_index: usize, fd: RawFd) -> Result<()>; + + /// Set the eventfd that will be signaled by the guest when buffers + /// need to be processed by the host. + /// + /// # Arguments + /// + /// * `queue_index` - The index of virtio queue. + /// * `fd` - The files descriptor used to notify the host. + fn set_vring_kick(&mut self, queue_index: usize, fd: RawFd) -> Result<()>; + + /// set the status of virtio queue + /// + /// # Arguments + /// + /// * `queue_index` - The index of virtio queue. + /// * `status` - The status of virtio queue. + fn set_vring_enable(&mut self, queue_index: usize, status: u32) -> Result<()>; +} + +/// The vhost-user server handler can communicate with StratoVirt and set the data of requests +/// to the backend +#[derive(Clone)] +pub struct VhostUserServerHandler { + /// The information of socket used to communicate with StratoVirt + pub sock: UnixDomainSock, + // The backend used to save the data of requests from StratoVirt + backend: Arc>, +} + +fn close_fds(fds: Vec) { + for fd in fds { + let _ = unsafe { libc::close(fd) }; + } +} + +fn is_invalid_fds(hdr: &mut VhostUserMsgHeader, rfds: Option>) -> Result<()> { + match VhostUserMsgReq::from(hdr.request) { + VhostUserMsgReq::SetMemTable => Ok(()), + VhostUserMsgReq::SetVringCall => Ok(()), + VhostUserMsgReq::SetVringKick => Ok(()), + VhostUserMsgReq::SetSlaveReqFd => Ok(()), + _ => { + if rfds.is_some() { + if let Some(fds) = rfds { + close_fds(fds); + } + bail!("The fds is invalid, request: {}", hdr.request); + } else { + Ok(()) + } + } + } +} + +impl VhostUserServerHandler { + /// Construct a vhost-user server handler + /// + /// # Arguments + /// + /// * `path` - The path of unix socket file which communicates with StratoVirt. + /// * `backend` - The trait of backend used to save the data of requests from StratoVirt. + pub fn new(path: &str, backend: Arc>) -> Result { + let mut server = UnixDomainSock::new(path); + server + .server_bind(true) + .chain_err(|| format!("Failed to bind for vhost user server {}", path))?; + limit_permission(path).chain_err(|| format!("Failed to limit permission {}", path))?; + + Ok(VhostUserServerHandler { + sock: server, + backend, + }) + } + + fn recv_hdr_and_fds(&mut self) -> Result<(VhostUserMsgHeader, Option>)> { + let mut hdr = VhostUserMsgHeader::default(); + let body_opt: Option<&mut u32> = None; + let payload_opt: Option<&mut [u8]> = None; + let mut fds = vec![0; MAX_ATTACHED_FD_ENTRIES]; + + let (rcv_len, fds_num) = self + .sock + .vhostuser_msgrecv(Some(&mut hdr), body_opt, payload_opt, &mut fds) + .chain_err(|| "Failed to recv hdr and fds")?; + + if rcv_len != size_of::() { + bail!( + "The length {} {} of header is invalid", + rcv_len, + size_of::() + ); + } else if hdr.is_invalid() { + bail!( + "The header of vhost user msg is invalid, request: {}, size: {}, flags: {}", + hdr.request, + hdr.size, + hdr.flags + ); + } + + let rfds = match fds_num { + 0 => None, + n => { + let mut fds_temp = Vec::with_capacity(n); + fds_temp.extend_from_slice(&fds[0..n]); + Some(fds_temp) + } + }; + + is_invalid_fds(&mut hdr, rfds.clone())?; + + Ok((hdr, rfds)) + } + + fn recv_body(&mut self, len: usize) -> Result<(usize, Vec)> { + let mut rbuf = vec![0u8; len]; + let body_opt: Option<&mut u32> = None; + let hdr_opt: Option<&mut VhostUserMsgHeader> = None; + + let (rcv_len, _) = self + .sock + .vhostuser_msgrecv(hdr_opt, body_opt, Some(&mut rbuf), &mut []) + .chain_err(|| "Failed to recv msg body")?; + + if rcv_len != len { + bail!("The length of msg body is invalid {} {}", rcv_len, len); + } + + Ok((rcv_len, rbuf)) + } + + fn get_msg_body<'a, D: Sized>( + &self, + hdr: &VhostUserMsgHeader, + buf: &'a [u8], + len: usize, + ) -> Result<&'a D> { + if (hdr.size as usize) != len || size_of::() != len { + bail!( + "Failed to get msg body {} {} {}", + len, + hdr.size, + size_of::() + ); + } + + let body = unsafe { &*(buf.as_ptr() as *const D) }; + Ok(body) + } + + fn send_ack_msg(&mut self, request: u32, res: D, fds: &[RawFd]) -> Result<()> { + let hdr = VhostUserMsgHeader::new( + request, + VhostUserHeaderFlag::Reply as u32, + size_of::() as u32, + ); + let payload_opt: Option<&[u8]> = None; + + self.sock + .vhostuser_msgsend(Some(&hdr), Some(&res), payload_opt, fds) + .chain_err(|| "Failed to send ack msg")?; + + Ok(()) + } + + #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] + fn set_msg_mem_table( + &mut self, + hdr: &VhostUserMsgHeader, + buf: &[u8], + len: usize, + fds_opt: Option>, + ) -> Result<()> { + if (hdr.size as usize) != len || (hdr.size as usize) < size_of::() { + if let Some(fds) = fds_opt { + close_fds(fds); + } + bail!( + "The header length of mem table is invalid {} {} {}", + len, + hdr.size, + size_of::() + ); + } + + let memhdrsize = size_of::(); + // The length of buf has been judged above to ensure no buffer overflow + let memhdr = unsafe { &*(buf.as_ptr() as *const VhostUserMemoryHdr) }; + let total_size = + (memhdr.nregion as usize * size_of::()) + memhdrsize; + if (hdr.size as usize) != total_size { + if let Some(fds) = fds_opt { + close_fds(fds); + } + bail!( + "The body length of mem table is invalid {} {}", + hdr.size, + total_size + ); + } + + // The length of buf has been judged above to ensure no buffer overflow + let regions = unsafe { + slice::from_raw_parts( + buf.as_ptr().add(memhdrsize) as *const VhostUserMemoryRegion, + memhdr.nregion as usize, + ) + }; + + if let Some(fds) = fds_opt { + let fds_len = fds.len(); + if fds_len != (memhdr.nregion as usize) { + close_fds(fds); + bail!( + "The length {} {} of fds for mem table is invalid", + fds_len, + memhdr.nregion + ); + } + self.backend.lock().unwrap().set_mem_table(regions, &fds)?; + } else { + bail!("The fds of mem table is null"); + } + + Ok(()) + } + + fn process_request( + &mut self, + hdr: &VhostUserMsgHeader, + buf: &[u8], + len: usize, + rfds: Option>, + ) -> Result<()> { + match VhostUserMsgReq::from(hdr.request) { + VhostUserMsgReq::GetFeatures => { + if len != 0 { + bail!("The length {} of getting features is invalid", len); + } + + let features = self.backend.lock().unwrap().get_features()?; + if hdr.is_need_reply() { + self.send_ack_msg(VhostUserMsgReq::GetFeatures as u32, features, &[]) + .chain_err(|| "Failed to send ack msg for getting features")?; + } + } + VhostUserMsgReq::SetFeatures => { + let features = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting features")?; + self.backend.lock().unwrap().set_features(*features)?; + } + VhostUserMsgReq::SetOwner => { + if len != 0 { + bail!("The length {} of setting owner is invalid", len); + } + self.backend.lock().unwrap().set_owner()?; + } + VhostUserMsgReq::SetMemTable => { + let ret = match self.set_msg_mem_table(&hdr, buf, len, rfds) { + Err(ref e) => { + error!( + "Failed to set mem table {}", + error_chain::ChainedError::display_chain(e) + ); + 1u64 + } + Ok(_) => 0u64, + }; + if hdr.is_need_reply() { + self.send_ack_msg(VhostUserMsgReq::SetMemTable as u32, ret, &[]) + .chain_err(|| "Failed to send ack msg for setting mem table")?; + } + } + VhostUserMsgReq::SetVringNum => { + let vringstate = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting vring num")?; + self.backend + .lock() + .unwrap() + .set_vring_num(vringstate.index as usize, vringstate.value as u16)?; + } + VhostUserMsgReq::SetVringAddr => { + let vringaddr = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting vring addr")?; + self.backend.lock().unwrap().set_vring_addr( + vringaddr.index as usize, + vringaddr.flags, + vringaddr.desc_user_addr, + vringaddr.used_user_addr, + vringaddr.avail_user_addr, + vringaddr.log_guest_addr, + )?; + } + VhostUserMsgReq::SetVringBase => { + let vringstate = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting vring base")?; + self.backend + .lock() + .unwrap() + .set_vring_base(vringstate.index as usize, vringstate.value as u16)?; + } + VhostUserMsgReq::SetVringEnable => { + let vringstate = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting vring enable")?; + self.backend + .lock() + .unwrap() + .set_vring_enable(vringstate.index as usize, vringstate.value)?; + } + VhostUserMsgReq::SetVringKick => { + let index = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting vring kick")?; + if let Some(fds) = rfds { + let fds_len = fds.len(); + if fds_len != 1 { + close_fds(fds); + bail!("The length {} of fds for kicking is invalid", fds_len); + } + self.backend + .lock() + .unwrap() + .set_vring_kick(*index as usize, fds[0])?; + } else { + bail!("The length {} of fds for kicking is null"); + } + } + VhostUserMsgReq::SetVringCall => { + let index = self + .get_msg_body::(&hdr, buf, len) + .chain_err(|| "Failed to get msg body for setting vring call")?; + if let Some(fds) = rfds { + let fds_len = fds.len(); + if fds_len != 1 { + close_fds(fds); + bail!("The length {} of fds for calling is invalid", fds_len); + } + self.backend + .lock() + .unwrap() + .set_vring_call(*index as usize, fds[0])?; + } else { + bail!("The length {} of fds for calling is null"); + } + } + _ => { + bail!("The request {} is unknown", hdr.request); + } + }; + + Ok(()) + } + + /// The function used to process requests from StratoVirt + pub fn handle_request(&mut self) -> Result<()> { + let (hdr, rfds) = self + .recv_hdr_and_fds() + .chain_err(|| "Failed to recv header and fds")?; + + let (len, buf) = match hdr.size { + 0 => (0, vec![0u8; 0]), + _ => { + let (rcv_len, rbuf) = self + .recv_body(hdr.size as usize) + .chain_err(|| "Failed to recv msg body")?; + (rcv_len, rbuf) + } + }; + + self.process_request(&hdr, &buf, len, rfds) + .chain_err(|| format!("Failed to process the request {}", hdr.request))?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + extern crate address_space; + extern crate device_model; + + use std::env::temp_dir; + use std::os::unix::io::{FromRawFd, RawFd}; + use std::sync::{Arc, Mutex}; + use std::{thread, time}; + + use vmm_sys_util::eventfd::EventFd; + + use address_space::{create_host_mmaps, AddressSpace, GuestAddress, Region}; + use device_model::virtio::queue::QueueConfig; + use device_model::virtio::vhost::user::vhost_user_client::VhostUserClient; + use device_model::virtio::vhost::VhostOps; + use machine_manager::config::MachineMemConfig; + use vhost_user_server::VhostUserReqHandler; + + use super::super::errors::Result; + use super::*; + + const SYSTEM_SPACE_SIZE: u64 = (1024 * 1024) as u64; + const QUEUE_SIZE: u16 = 256; + const LAST_AVAIL_IDX: u16 = 100; + fn address_space_init() -> Arc { + let root = Region::init_container_region(1 << 36); + let sys_space = AddressSpace::new(root).unwrap(); + let addr_ranges = [(0x0, SYSTEM_SPACE_SIZE)]; + let mem_config = MachineMemConfig { + mem_size: SYSTEM_SPACE_SIZE, + mem_path: None, + dump_guest_core: false, + mem_share: true, + }; + let host_mmap_vec = create_host_mmaps(&addr_ranges, &mem_config).unwrap(); + for host_mmap in host_mmap_vec { + sys_space + .root() + .add_subregion( + Region::init_ram_region(host_mmap.clone()), + host_mmap.start_address().raw_value(), + ) + .unwrap(); + } + sys_space + } + + struct VhostUserBackend { + owner: bool, + features: u64, + } + + impl VhostUserBackend { + fn new() -> Self { + VhostUserBackend { + owner: false, + features: 0, + } + } + } + + impl VhostUserReqHandler for VhostUserBackend { + fn set_owner(&mut self) -> Result<()> { + println!("set owner"); + self.owner = true; + Ok(()) + } + + fn get_features(&self) -> Result { + println!("get features: {}", self.features); + Ok(self.features) + } + + fn set_features(&mut self, features: u64) -> Result<()> { + println!("set features: {}", self.features); + self.features = features; + Ok(()) + } + + fn set_mem_table( + &mut self, + regions: &[VhostUserMemoryRegion], + _fds: &[RawFd], + ) -> Result<()> { + for region in regions { + println!("guest_phys_addr 0x{:X}, memory_size: {}, userspace_addr: 0x{:X}, mmap_offset: {}", + region.guest_phys_addr, region.memory_size, + region.userspace_addr, region.mmap_offset, + ); + assert_eq!(region.guest_phys_addr, 0x0); + assert_eq!(region.memory_size, SYSTEM_SPACE_SIZE); + assert_eq!(region.mmap_offset, 0); + } + + Ok(()) + } + + fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> { + println!("set vring num, index: {}, num: {}", queue_index, num); + assert_eq!(queue_index, 2); + assert_eq!(num, QUEUE_SIZE); + Ok(()) + } + + fn set_vring_addr( + &mut self, + queue_index: usize, + flags: u32, + desc_table: u64, + used_ring: u64, + avail_ring: u64, + log: u64, + ) -> Result<()> { + println!( + "queue_index: {}, table: 0x{:x}, avail: 0x{:x}, used: 0x{:x}, flags: {}, log: {}", + queue_index, desc_table, avail_ring, used_ring, flags, log + ); + assert_eq!(queue_index, 4); + assert_eq!(desc_table, 0x1); + assert_eq!(avail_ring, 0x2); + assert_eq!(used_ring, 0x3); + assert_eq!(log, 0); + assert_eq!(flags, 0x1); + Ok(()) + } + + fn set_vring_base(&mut self, queue_index: usize, num: u16) -> Result<()> { + println!("queue_index: {}, num: {}", queue_index, num); + assert_eq!(queue_index, 1); + assert_eq!(num, LAST_AVAIL_IDX); + Ok(()) + } + + fn set_vring_call(&mut self, queue_index: usize, fd: RawFd) -> Result<()> { + println!("queue_index: {}, call_fd: {}", queue_index, fd); + assert_eq!(queue_index, 6); + let file = unsafe { EventFd::from_raw_fd(fd) }; + file.write(200).expect("Failed to write call eventfd"); + Ok(()) + } + + fn set_vring_kick(&mut self, queue_index: usize, fd: RawFd) -> Result<()> { + println!("queue_index: {}, kick_fd: {}", queue_index, fd); + assert_eq!(queue_index, 7); + let file = unsafe { EventFd::from_raw_fd(fd) }; + file.write(300).expect("Failed to write kick eventfd"); + Ok(()) + } + + fn set_vring_enable(&mut self, queue_index: usize, status: u32) -> Result<()> { + println!("queue_index: {}, status: {}", queue_index, status); + assert_eq!(queue_index, 3); + assert_eq!(status, 1); + Ok(()) + } + } + + // It is ok to send the message of SetOwner, SetFeatures and GetFeatures from client to server. + #[test] + fn vhost_user_test_01() { + let exit_server_thread = Arc::new(Mutex::new(false)); + let test_path = temp_dir().join("test1"); + + let exit_thread_clone = exit_server_thread.clone(); + let test_path_clone = test_path.clone(); + let _handle = thread::Builder::new() + .spawn(move || { + let mut server = VhostUserServerHandler::new( + test_path_clone.to_str().unwrap(), + Arc::new(Mutex::new(VhostUserBackend::new())), + ) + .unwrap(); + + if server.sock.server_accept().is_err() { + return; + } + + loop { + let _ = server.handle_request(); + if *exit_thread_clone.lock().unwrap() == true { + break; + } + } + }) + .expect("thread spawn failed"); + + thread::sleep(time::Duration::from_micros(1000)); + + let mem_space = address_space_init(); + let queue_num = 255; + let client = + VhostUserClient::new(&mem_space, test_path.to_str().unwrap(), queue_num).unwrap(); + assert!(client.set_owner().is_ok()); + + assert!(client.set_features(1000).is_ok()); + let features = match client.get_features() { + Err(_e) => 0_u64, + Ok(ret) => ret, + }; + assert_eq!(features, 1000); + + *exit_server_thread.lock().unwrap() = true; + thread::sleep(time::Duration::from_micros(1000)); + } + + // It is ok to send the message of SetMemTable, SetVringEnable, SetVringAddr and + // SetVringNum from client to server. + #[test] + fn vhost_user_test_02() { + let exit_server_thread = Arc::new(Mutex::new(false)); + let test_path = temp_dir().join("test2"); + + let exit_thread_clone = exit_server_thread.clone(); + let test_path_clone = test_path.clone(); + let _handle = thread::Builder::new() + .spawn(move || { + let mut server = VhostUserServerHandler::new( + test_path_clone.to_str().unwrap(), + Arc::new(Mutex::new(VhostUserBackend::new())), + ) + .unwrap(); + + if server.sock.server_accept().is_err() { + return; + } + + loop { + let _ = server.handle_request(); + if *exit_thread_clone.lock().unwrap() == true { + break; + } + } + }) + .expect("thread spawn failed"); + + thread::sleep(time::Duration::from_micros(1000)); + + let mem_space = address_space_init(); + let queue_num = 255; + let client = + VhostUserClient::new(&mem_space, test_path.to_str().unwrap(), queue_num).unwrap(); + assert!(client.set_mem_table().is_ok()); + + let queue_index: usize = 2; + assert!(client.set_vring_num(queue_index, QUEUE_SIZE).is_ok()); + + let queue_index: usize = 1; + assert!(client.set_vring_base(queue_index, LAST_AVAIL_IDX).is_ok()); + + let queue_index: usize = 3; + assert!(client.set_vring_enable(queue_index, true).is_ok()); + + let queue_index: usize = 4; + let config = QueueConfig { + desc_table: GuestAddress(0x1), + avail_ring: GuestAddress(0x2), + used_ring: GuestAddress(0x3), + max_size: 300, + size: 200, + ready: true, + }; + assert!(client.set_vring_addr(&config, queue_index, 0x1).is_ok()); + + *exit_server_thread.lock().unwrap() = true; + thread::sleep(time::Duration::from_micros(1000)); + } + + // It is ok to send the message of SetVringCall and SetVringKick from client to server. + #[test] + fn vhost_user_test_03() { + let exit_server_thread = Arc::new(Mutex::new(false)); + let test_path = temp_dir().join("test3"); + + let exit_thread_clone = exit_server_thread.clone(); + let test_path_clone = test_path.clone(); + let _handle = thread::Builder::new() + .spawn(move || { + let mut server = VhostUserServerHandler::new( + test_path_clone.to_str().unwrap(), + Arc::new(Mutex::new(VhostUserBackend::new())), + ) + .unwrap(); + + if server.sock.server_accept().is_err() { + return; + } + + loop { + let _ = server.handle_request(); + if *exit_thread_clone.lock().unwrap() == true { + break; + } + } + }) + .expect("thread spawn failed"); + + thread::sleep(time::Duration::from_micros(1000)); + + let mem_space = address_space_init(); + let queue_num = 255; + let client = + VhostUserClient::new(&mem_space, test_path.to_str().unwrap(), queue_num).unwrap(); + + let call_evt = EventFd::new(0).expect("Failed to create eventfd for calling"); + let queue_index = 6; + assert!(client.set_vring_call(queue_index, &call_evt).is_ok()); + assert_eq!(call_evt.read().expect("Failed to read call eventfd"), 200); + + let kick_evt = EventFd::new(0).expect("Failed to create eventfd for calling"); + let queue_index = 7; + assert!(client.set_vring_kick(queue_index, &kick_evt).is_ok()); + assert_eq!(kick_evt.read().expect("Failed to read kick eventfd"), 300); + + *exit_server_thread.lock().unwrap() = true; + thread::sleep(time::Duration::from_micros(1000)); + } +} diff --git a/vhost_user_fs/src/virtio_fs.rs b/vhost_user_fs/src/virtio_fs.rs new file mode 100644 index 0000000000000000000000000000000000000000..402a75a804bd262c32de27d0b1e2e1fb2bbda443 --- /dev/null +++ b/vhost_user_fs/src/virtio_fs.rs @@ -0,0 +1,1376 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::fs::File; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::sync::{Arc, Mutex}; + +use error_chain::ChainedError; +use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; + +use address_space::{AddressSpace, FileBackend, GuestAddress, HostMemMapping, Region}; +use device_model::virtio::{ + queue::{Queue, QueueConfig, QUEUE_TYPE_SPLIT_VRING}, + vhost::user::vhost_user_msg::*, + VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC, VIRTIO_F_VERSION_1, +}; +use machine_manager::event_loop::EventLoop; +use util::loop_context::{read_fd, EventNotifier, EventNotifierHelper, NotifierOperation}; + +use super::fs::FileSystem; +use super::fuse_req::FuseReq; +use super::vhost_user_server::VhostUserReqHandler; + +use crate::errors::{Result, ResultExt}; + +// The num of high priority queue +const VIRIOT_FS_HIGH_PRIO_QUEUE_NUM: u64 = 1; +// The num of request queue +const VIRTIO_FS_REQ_QUEUES_NUM: u64 = 1; +const VIRTIO_FS_MAX_QUEUE_SIZE: u16 = 1024; + +struct FsIoHandler { + queue: Queue, + kick_evt: EventFd, + call_evt: EventFd, + mem_space: Arc, + driver_features: u64, + fs: Arc>, +} + +impl FsIoHandler { + fn new( + queue_config: QueueConfig, + kick_evt: &EventFd, + call_evt: &EventFd, + mem_space: &Arc, + driver_features: u64, + fs: Arc>, + ) -> Result { + let queue = Queue::new(queue_config, QUEUE_TYPE_SPLIT_VRING) + .chain_err(|| "Failed to create virtual queue")?; + if !queue.is_valid(mem_space) { + bail!("Invalid queue for fs handler"); + } + + Ok(FsIoHandler { + queue, + kick_evt: kick_evt.try_clone().unwrap(), + call_evt: call_evt.try_clone().unwrap(), + mem_space: mem_space.clone(), + driver_features, + fs, + }) + } + + fn process_queue(&mut self) -> Result<()> { + while let Ok(elem) = self + .queue + .vring + .pop_avail(&self.mem_space, self.driver_features) + { + let mut req = FuseReq::new(&elem); + let (index, len) = req.execute(&self.mem_space, self.fs.clone()); + self.queue.vring.add_used(&self.mem_space, index, len)?; + } + + if self + .queue + .vring + .should_notify(&self.mem_space, self.driver_features) + { + self.call_evt + .write(1) + .chain_err(|| "Failed to write call fd")?; + } + + Ok(()) + } + + fn delete_notifiers(&self) -> Vec { + let mut notifiers = Vec::new(); + notifiers.push(EventNotifier::new( + NotifierOperation::Delete, + self.kick_evt.as_raw_fd(), + None, + EventSet::IN, + Vec::new(), + )); + + notifiers + } +} + +impl EventNotifierHelper for FsIoHandler { + fn internal_notifiers(fs_handler: Arc>) -> Vec { + let mut notifiers = Vec::new(); + + let fs_handler_clone = fs_handler.clone(); + let handler = Box::new(move |_, fd: RawFd| { + read_fd(fd); + + if let Err(e) = fs_handler_clone.lock().unwrap().process_queue() { + error!("Failed to process fuse msg, {}", e.display_chain()); + } + + None + }); + + notifiers.push(EventNotifier::new( + NotifierOperation::AddShared, + fs_handler.lock().unwrap().kick_evt.as_raw_fd(), + None, + EventSet::IN, + vec![Arc::new(Mutex::new(handler))], + )); + + notifiers + } +} + +struct QueueInfo { + config: QueueConfig, + kick_evt: Option, + call_evt: Option, +} + +impl QueueInfo { + fn new(queue_size: u16) -> Self { + QueueInfo { + config: QueueConfig::new(queue_size), + kick_evt: None, + call_evt: None, + } + } +} + +struct VirtioFsConfig { + device_features: u64, + driver_features: u64, + queues_info: Vec, + mem_regions: Vec, +} + +impl VirtioFsConfig { + fn new() -> Self { + let device_features = 1_u64 << VIRTIO_F_VERSION_1 + | 1_u64 << VIRTIO_F_RING_INDIRECT_DESC + | 1_u64 << VIRTIO_F_RING_EVENT_IDX; + + let mut queues_info = Vec::new(); + for _i in 0..(VIRIOT_FS_HIGH_PRIO_QUEUE_NUM + VIRTIO_FS_REQ_QUEUES_NUM) { + queues_info.push(QueueInfo::new(VIRTIO_FS_MAX_QUEUE_SIZE)); + } + + VirtioFsConfig { + device_features, + driver_features: 0_u64, + queues_info, + mem_regions: Vec::new(), + } + } + + fn get_mut_queue_config(&mut self, queue_index: usize) -> Result<&mut QueueInfo> { + self.queues_info + .get_mut(queue_index) + .ok_or_else(|| format!("The select index of queue {} overflows", queue_index).into()) + } +} + +/// The virtio fs device contains the configuration of virtio fs, the management of +/// userspace filesystem and the handler used to process requests in virtio queue +/// from the guest. +pub struct VirtioFs { + config: VirtioFsConfig, + fs_handlers: Vec>>>, + sys_mem: Arc, + fs: Arc>, +} + +impl VirtioFs { + /// Construct a virtio fs device by the path of source directory shared in host. + /// + /// # Arguments + /// + /// * `source_dir` - The path of source directory shared in host. + pub fn new(source_dir: &str) -> Result { + let sys_mem = AddressSpace::new(Region::init_container_region(u64::max_value())) + .chain_err(|| "Failed to create address space")?; + + let mut fs_handlers = Vec::new(); + for _i in 0..(VIRIOT_FS_HIGH_PRIO_QUEUE_NUM + VIRTIO_FS_REQ_QUEUES_NUM) { + fs_handlers.push(None); + } + + let fs = Arc::new(Mutex::new(FileSystem::new(source_dir).chain_err(|| { + format!("Failed to create file system, source dir: {}", source_dir) + })?)); + + Ok(VirtioFs { + config: VirtioFsConfig::new(), + fs_handlers, + sys_mem, + fs, + }) + } +} + +impl VhostUserReqHandler for VirtioFs { + fn set_owner(&mut self) -> Result<()> { + Ok(()) + } + + fn get_features(&self) -> Result { + Ok(self.config.device_features) + } + + fn set_features(&mut self, features: u64) -> Result<()> { + self.config.driver_features = features; + Ok(()) + } + + fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegion], fds: &[RawFd]) -> Result<()> { + if !self.config.mem_regions.is_empty() { + for region in &self.config.mem_regions { + if let Err(e) = self.sys_mem.root().delete_subregion(region) { + error!( + "Failed to delete subregion for setting mem table, {}", + e.display_chain() + ); + } + } + self.config.mem_regions = Vec::new(); + } + + for (index, region_config) in regions.iter().enumerate() { + let file = unsafe { File::from_raw_fd(fds[index]) }; + let fileback = FileBackend { + file: Arc::new(file), + offset: region_config.mmap_offset, + page_size: 0_u64, + }; + + let mmap = Arc::new( + HostMemMapping::new( + GuestAddress(region_config.guest_phys_addr), + region_config.memory_size, + Some(fileback), + false, + true, + ) + .chain_err(|| + format!("Failed to create the mapping of host memory for setting mem table, addr: 0x{:X}, size: {}, offset: {}", + region_config.guest_phys_addr, region_config.memory_size, region_config.mmap_offset, + ) + )? + ); + + let region = Region::init_ram_region(mmap.clone()); + self.sys_mem + .root() + .add_subregion(region.clone(), mmap.start_address().raw_value()) + .chain_err(|| "Failed to add subregion for setting mem table")?; + + self.config.mem_regions.push(region); + } + + Ok(()) + } + + fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> { + self.config + .get_mut_queue_config(queue_index) + .map(|queue_info| { + queue_info.config.size = num; + }) + .chain_err(|| { + format!( + "Failed to set vring num, index: {}, num: {}", + queue_index, num, + ) + })?; + Ok(()) + } + + fn set_vring_addr( + &mut self, + queue_index: usize, + _flags: u32, + desc_table: u64, + used_ring: u64, + avail_ring: u64, + _log: u64, + ) -> Result<()> { + self.config + .get_mut_queue_config(queue_index as usize) + .map(|queue_info| { + queue_info.config.desc_table = GuestAddress(desc_table); + queue_info.config.avail_ring = GuestAddress(avail_ring); + queue_info.config.used_ring = GuestAddress(used_ring); + }).chain_err(|| + format!("Failed to set vring addr, index: {}, desc: 0x{:X}, avail: 0x{:X}, used: 0x{:X}", + queue_index, desc_table, avail_ring, used_ring, + ) + )?; + + Ok(()) + } + + fn set_vring_base(&mut self, _queue_index: usize, _num: u16) -> Result<()> { + Ok(()) + } + + fn set_vring_call(&mut self, queue_index: usize, fd: RawFd) -> Result<()> { + self.config + .get_mut_queue_config(queue_index) + .map(|queue_info| { + let call_evt = unsafe { EventFd::from_raw_fd(fd) }; + queue_info.call_evt = Some(call_evt); + }) + .chain_err(|| format!("Failed to set vring call, index: {}", queue_index))?; + Ok(()) + } + + fn set_vring_kick(&mut self, queue_index: usize, fd: RawFd) -> Result<()> { + self.config + .get_mut_queue_config(queue_index) + .map(|queue_info| { + let kick_evt = unsafe { EventFd::from_raw_fd(fd) }; + queue_info.kick_evt = Some(kick_evt); + }) + .chain_err(|| format!("Failed to set vring kick, index: {}", queue_index))?; + Ok(()) + } + + fn set_vring_enable(&mut self, queue_index: usize, status: u32) -> Result<()> { + let driver_features = self.config.driver_features; + + let mut queue_info = self.config.get_mut_queue_config(queue_index)?; + queue_info.config.ready = status == 1; + + if status == 1 { + if queue_info.kick_evt.is_none() || queue_info.call_evt.is_none() { + bail!( + "The event for kicking {} or calling {} is none", + queue_info.kick_evt.is_none(), + queue_info.call_evt.is_none(), + ); + } + + let fs_handler = Arc::new(Mutex::new( + FsIoHandler::new( + queue_info.config, + queue_info.kick_evt.as_ref().unwrap(), + queue_info.call_evt.as_ref().unwrap(), + &self.sys_mem, + driver_features, + self.fs.clone(), + ) + .chain_err(|| "Failed to create fs handler")?, + )); + + self.fs_handlers[queue_index] = Some(fs_handler.clone()); + EventLoop::update_event(EventNotifierHelper::internal_notifiers(fs_handler), None) + .chain_err(|| "Failed to update event for queue status which is ready")?; + } else if let Some(fs_handler) = self.fs_handlers.get_mut(queue_index).unwrap().take() { + EventLoop::update_event(fs_handler.lock().unwrap().delete_notifiers(), None) + .chain_err(|| "Failed to update event for queue status which is not ready")?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use address_space::{AddressSpace, GuestAddress, Region}; + use device_model::virtio::queue::QueueConfig; + use device_model::virtio::SplitVringDesc; + use fuse_msg::{FuseCreateIn, FuseInHeader, FuseOutHeader, FUSE_CREATE}; + use std::env::temp_dir; + use std::mem::size_of; + use std::path::Path; + use std::sync::Arc; + use util::byte_code::ByteCode; + use vmm_sys_util::eventfd::EventFd; + + const SYSTEM_SPACE_SIZE: u64 = (1024 * 1024) as u64; + const VIRTQ_DESC_F_NEXT: u16 = 0x1; + const VIRTQ_DESC_F_WRITE: u16 = 0x2; + + #[repr(C)] + #[derive(Debug, Default, Copy, Clone)] + struct CreateName { + name: [u8; 9], + } + + impl ByteCode for CreateName {} + + fn address_space_init() -> Arc { + let root = Region::init_container_region(1 << 36); + let sys_space = AddressSpace::new(root).unwrap(); + let host_mmap = Arc::new( + HostMemMapping::new(GuestAddress(0), SYSTEM_SPACE_SIZE, None, false, false).unwrap(), + ); + sys_space + .root() + .add_subregion( + Region::init_ram_region(host_mmap.clone()), + host_mmap.start_address().raw_value(), + ) + .unwrap(); + sys_space + } + + // Imitate fuse protocol to create FuseCreateIn request. the vhost_user_fs + // will create a file named virtiofs in temp directory. + #[test] + fn test_valid_request() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem, + ); + + // Setting the first desc. + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + // Expected to call function do_fuse_create + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc + let create_len = (size_of::() + size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x200), + len: create_len, + flags: 0, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + // Expect file mode: 0644 + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + // file name: virtiofs + let file_name = CreateName { + name: [118, 105, 114, 116, 105, 111, 102, 115, 0], + }; + mem_space + .write_object::(&create_node, GuestAddress(0x200)) + .unwrap(); + let name_offset = size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x200).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Get used_ring data + let idx = mem_space + .read_object::(GuestAddress(queue_config.used_ring.0 + 2 as u64)) + .unwrap(); + assert_eq!(idx, 1); + // Open virtiofs file success. + let file_path = source_dir.join("virtiofs"); + assert_eq!(Path::new(file_path.to_str().unwrap()).exists(), true); + } + + // While creating a file for fuse protocol, must provide a name field after FuseCreateIn + // structure. + #[test] + fn test_missing_name_field_request() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem, + ); + + // Missing name field data. + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc + let create_len = (size_of::() + size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x200), + len: create_len, + flags: VIRTQ_DESC_F_NEXT, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + mem_space + .write_object::(&create_node, GuestAddress(0x200)) + .unwrap(); + + // Setting the third desc + let out_header_len = (size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x300), + len: out_header_len, + flags: VIRTQ_DESC_F_WRITE, + next: 3, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 32 as u64), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&3, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Confirm the FuseOutHeader error code + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x300)) + .unwrap(); + assert_eq!(-fuse_out_header.error, libc::ENOENT); + } + + // Creating a file, the request only has FuseInHeader structure. + #[test] + fn test_only_inheader() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem, + ); + + // Send only one reader desc. + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = size_of::() as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc + let out_header_len = (size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x300), + len: out_header_len, + flags: VIRTQ_DESC_F_WRITE, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Confirm the FuseOutHeader error code + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x300)) + .unwrap(); + assert_eq!(-fuse_out_header.error, libc::EINVAL); + // Get used_ring data. + let idx = mem_space + .read_object::(GuestAddress(queue_config.used_ring.0 + 2 as u64)) + .unwrap(); + assert_eq!(idx, 1); + } + + // There are tow desc tables, but the second flag is VIRTQ_DESC_F_WRITE. For creating a + // file, two desc tables are required. + #[test] + fn test_invalid_desc_flag() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem, + ); + + // The second SplitVringDesc flag is invalid. + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc. + let create_len = (size_of::() + size_of::()) as u32; + // Invalid flags in desc table. + let desc = SplitVringDesc { + addr: GuestAddress(0x200), + len: create_len, + flags: VIRTQ_DESC_F_WRITE, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + + // file name: virtiofa + let file_name = CreateName { + name: [118, 105, 114, 116, 105, 111, 102, 97, 0], + }; + mem_space + .write_object::(&create_node, GuestAddress(0x200)) + .unwrap(); + let name_offset = size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x200).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Open the file failed. + let file_path = source_dir.join("virtiofa"); + assert_eq!(Path::new(file_path.to_str().unwrap()).exists(), false); + } + + // Construct an unsupported opcode. + #[test] + fn test_unsupported_opcode() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem, + ); + + // Unsupported opcode + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + // invalid operation code: 64 + let in_header = FuseInHeader { + len: in_header_len, + opcode: 64, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc + let create_len = (size_of::() + size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x200), + len: create_len, + flags: 0, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + + // file name: virtiofe + let file_name = CreateName { + name: [118, 105, 114, 116, 105, 111, 102, 101, 0], + }; + mem_space + .write_object::(&create_node, GuestAddress(0x200)) + .unwrap(); + let name_offset = size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x200).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Open the file failed. + let file_path = source_dir.join("virtiofe"); + assert_eq!(Path::new(file_path.to_str().unwrap()).exists(), false); + } + + // The desc table length parameter must match FuseInHeader structure. And it will not work if + // the address of FuseInHeader is out of bounds. + #[test] + fn test_wrong_param_for_desc_01() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem.clone(), + ); + + // Setting the first desc. + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // The value of addr and len are out of mem_space range, set invalid address in desc table. + let create_len = (size_of::() + size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x000f_ffe6), + len: create_len, + flags: VIRTQ_DESC_F_NEXT, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + + let file_name = CreateName { + name: [118, 105, 114, 116, 105, 111, 102, 115, 0], + }; + mem_space + .write_object::(&create_node, GuestAddress(0x000f_ffe6)) + .unwrap(); + let name_offset = size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x200).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + // Setting the third desc + let out_header_len = (size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x300), + len: out_header_len, + flags: VIRTQ_DESC_F_WRITE, + next: 3, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 32 as u64), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Confirm the FuseOutHeader error code + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x300)) + .unwrap(); + assert_eq!(-fuse_out_header.error, libc::ENOENT); + } + + #[test] + fn test_wrong_param_for_desc_02() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem.clone(), + ); + + // SplitVringDesc len not match FuseInHeader, len is less than sizeof:: + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: 20, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc + let create_len = (size_of::() + size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x200), + len: create_len, + flags: 0, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + + // file name: virtiofk + let file_name = CreateName { + name: [118, 105, 114, 116, 105, 111, 102, 107, 0], + }; + mem_space + .write_object::(&create_node, GuestAddress(0x200)) + .unwrap(); + let name_offset = size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x200).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Open the file failed. + let file_path = source_dir.join("virtiofk"); + assert_eq!(Path::new(file_path.to_str().unwrap()).exists(), false); + } + + // Input invalid source path and name path, it will return Err or error code. + #[test] + fn test_invalid_path() { + let mem_space = address_space_init(); + let kick_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let call_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut queue_config = QueueConfig::new(256); + queue_config.desc_table = GuestAddress(0); + queue_config.avail_ring = GuestAddress(4096); + queue_config.used_ring = GuestAddress(8192); + queue_config.ready = true; + queue_config.size = 256; + + { + // Invalid path: `/aaa/` path is not exist. + let source_dir = "/aaa/"; + assert!(FileSystem::new(source_dir).is_err()); + // Access parent path is allowed. + let source_dir = temp_dir(); + assert!(FileSystem::new(source_dir.to_str().unwrap()).is_ok()); + let source_dir = "HDMriseqSfQLIGEUHnoiexNLyWALwnatXspd \ + YWYYzeQZrhVqvlHpgkMekdolTTUTXqIsWfPg \ + acdJdsUhfocpLQatpAAtUKoogwcaCByegcWE \ + BpWzNpbWLFCUcvdNBsyiBJ/"; + assert!(FileSystem::new(source_dir).is_err()); + } + + let source_dir = temp_dir(); + let filesystem = Arc::new(Mutex::new( + FileSystem::new(source_dir.to_str().unwrap()).unwrap(), + )); + let driver_features = 1000; + + let fs_handler = FsIoHandler::new( + queue_config, + &kick_evt, + &call_evt, + &mem_space, + driver_features, + filesystem, + ); + + // Setting the first desc. + let head_len = size_of::() as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x100), + len: head_len, + flags: VIRTQ_DESC_F_NEXT, + next: 1, + }; + mem_space + .write_object::(&desc, GuestAddress(queue_config.desc_table.0)) + .unwrap(); + + let in_header_len = (size_of::() + + size_of::() + + size_of::()) as u32; + let uid = unsafe { libc::geteuid() }; + let gid = unsafe { libc::getegid() }; + let in_header = FuseInHeader { + len: in_header_len, + opcode: FUSE_CREATE, + unique: 42, + nodeid: 1, + uid, + gid, + pid: 13579, + padding: 0, + }; + mem_space + .write_object::(&in_header, GuestAddress(0x100)) + .unwrap(); + + // Setting the second desc + let create_len = (size_of::() + size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x200), + len: create_len, + flags: VIRTQ_DESC_F_NEXT, + next: 2, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 16 as u64), + ) + .unwrap(); + + let create_node = FuseCreateIn { + flags: 34881, + mode: 0o100666, + umask: 0o00022, + padding: 0, + }; + // invalid file name: .`\0`rtiofm + let file_name = CreateName { + name: [46, 0, 114, 116, 105, 111, 102, 109, 0], + }; + mem_space + .write_object::(&create_node, GuestAddress(0x200)) + .unwrap(); + let name_offset = size_of::() as u64; + mem_space + .write_object( + &file_name, + GuestAddress(0x200).checked_add(name_offset).unwrap(), + ) + .unwrap(); + + // Setting the third desc + let out_header_len = (size_of::()) as u32; + let desc = SplitVringDesc { + addr: GuestAddress(0x300), + len: out_header_len, + flags: VIRTQ_DESC_F_WRITE, + next: 3, + }; + mem_space + .write_object::( + &desc, + GuestAddress(queue_config.desc_table.0 + 32 as u64), + ) + .unwrap(); + + // Setting avail_ring ring id + mem_space + .write_object::(&0, GuestAddress(queue_config.avail_ring.0 + 4 as u64)) + .unwrap(); + // Setting avail_ring idx + mem_space + .write_object::(&1, GuestAddress(queue_config.avail_ring.0 + 2 as u64)) + .unwrap(); + + assert!(fs_handler.unwrap().process_queue().is_ok()); + // Confirm the FuseOutHeader error code + let fuse_out_header = mem_space + .read_object::(GuestAddress(0x300)) + .unwrap(); + assert_eq!(-fuse_out_header.error, libc::EINVAL); + // Open the file failed. + let file_path = source_dir.join("virtiofm"); + assert_eq!(Path::new(file_path.to_str().unwrap()).exists(), false); + } +}