From 5c3522b994d6f631be464ee13c1bba759485ef45 Mon Sep 17 00:00:00 2001 From: Teoh Han Hui Date: Thu, 25 Apr 2024 21:04:50 +0800 Subject: [PATCH] Rewrite it in Rust --- .editorconfig | 44 ++ .gitattributes | 9 + .gitignore | 1 + Cargo.lock | 616 ++++++++++++++++++++++++ Cargo.toml | 22 + crates/krun-guest/Cargo.toml | 23 + crates/krun-guest/src/bin/krun-guest.rs | 38 ++ crates/krun-guest/src/cli_options.rs | 54 +++ crates/krun-guest/src/fex.rs | 37 ++ crates/krun-guest/src/lib.rs | 6 + crates/krun-guest/src/mount.rs | 55 +++ crates/krun-guest/src/net.rs | 40 ++ crates/krun-guest/src/sommelier.rs | 28 ++ crates/krun-guest/src/user.rs | 64 +++ crates/krun/Cargo.toml | 24 + crates/krun/src/bin/krun.rs | 316 ++++++++++++ crates/krun/src/cli_options.rs | 59 +++ crates/krun/src/lib.rs | 3 + crates/krun/src/net.rs | 113 +++++ crates/krun/src/utils.rs | 22 + crates/libkrun/Cargo.toml | 11 + crates/libkrun/build.rs | 16 + crates/libkrun/src/lib.rs | 5 + crates/libkrun/wrapper.h | 1 + crates/utils/Cargo.toml | 15 + crates/utils/src/env.rs | 34 ++ crates/utils/src/lib.rs | 2 + crates/utils/src/tracing.rs | 44 ++ rustfmt.toml | 18 + 29 files changed, 1720 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 crates/krun-guest/Cargo.toml create mode 100644 crates/krun-guest/src/bin/krun-guest.rs create mode 100644 crates/krun-guest/src/cli_options.rs create mode 100644 crates/krun-guest/src/fex.rs create mode 100644 crates/krun-guest/src/lib.rs create mode 100644 crates/krun-guest/src/mount.rs create mode 100644 crates/krun-guest/src/net.rs create mode 100644 crates/krun-guest/src/sommelier.rs create mode 100644 crates/krun-guest/src/user.rs create mode 100644 crates/krun/Cargo.toml create mode 100644 crates/krun/src/bin/krun.rs create mode 100644 crates/krun/src/cli_options.rs create mode 100644 crates/krun/src/lib.rs create mode 100644 crates/krun/src/net.rs create mode 100644 crates/krun/src/utils.rs create mode 100644 crates/libkrun/Cargo.toml create mode 100644 crates/libkrun/build.rs create mode 100644 crates/libkrun/src/lib.rs create mode 100644 crates/libkrun/wrapper.h create mode 100644 crates/utils/Cargo.toml create mode 100644 crates/utils/src/env.rs create mode 100644 crates/utils/src/lib.rs create mode 100644 crates/utils/src/tracing.rs create mode 100644 rustfmt.toml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d62e5ad --- /dev/null +++ b/.editorconfig @@ -0,0 +1,44 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.rs] +indent_style = space +indent_size = 4 + +[*.sh] +indent_style = tab +indent_size = 4 + +[*.toml] +indent_style = space +indent_size = 4 + +[*.{yaml,yml}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false + +[.github/{actions,workflows}/**/*.yml] +indent_style = space +indent_size = 2 + +[Cargo.toml] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2ff72ae --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text=auto eol=lf + +*.md text eol=lf +*.rs text eol=lf +*.sh text eol=lf +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +Cargo.lock text eol=lf merge=binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a1f9fb2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,616 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bpaf" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567fc5f0a754100df11b167b2a247b2366fc1ac18e9b776a07659be00878f681" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "krun" +version = "0.1.0" +dependencies = [ + "anyhow", + "arrayvec", + "bpaf", + "libkrun", + "nix", + "rustix", + "tracing", + "tracing-subscriber", + "utils", +] + +[[package]] +name = "krun-guest" +version = "0.1.0" +dependencies = [ + "anyhow", + "bpaf", + "nix", + "rustix", + "tempfile", + "tracing", + "tracing-subscriber", + "utils", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libkrun" +version = "1.8.1" +dependencies = [ + "bindgen", +] + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "prettyplease" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utils" +version = "0.0.0" +dependencies = [ + "tracing", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[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" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..145e4db --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.package] +version = "0.1.0" +authors = ["Sergio Lopez ", "Teoh Han Hui "] +edition = "2021" +rust-version = "1.77.0" +description = "Run programs from your system in a microVM" +repository = "https://github.com/slp/krun" +license = "MIT" + +[workspace.dependencies] +anyhow = "1.0.82" +bindgen = "0.69.4" +bpaf = "0.9.11" +nix = "0.28.0" +rustix = "0.38.34" +tempfile = "3.10.1" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" diff --git a/crates/krun-guest/Cargo.toml b/crates/krun-guest/Cargo.toml new file mode 100644 index 0000000..f1527af --- /dev/null +++ b/crates/krun-guest/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "krun-guest" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +description = { workspace = true } +repository = { workspace = true } +license = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +bpaf = { workspace = true } +nix = { workspace = true, features = ["user"] } +rustix = { workspace = true, features = ["fs", "mount", "system"] } +tempfile = { workspace = true } +tracing = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true } +utils = { path = "../utils" } + +[features] +default = ["tracing"] +tracing = ["dep:tracing", "dep:tracing-subscriber", "utils/tracing"] diff --git a/crates/krun-guest/src/bin/krun-guest.rs b/crates/krun-guest/src/bin/krun-guest.rs new file mode 100644 index 0000000..2e976a8 --- /dev/null +++ b/crates/krun-guest/src/bin/krun-guest.rs @@ -0,0 +1,38 @@ +use std::{os::unix::process::CommandExt as _, process::Command}; + +use anyhow::{Context, Result}; +use krun_guest::{ + cli_options::options, fex::setup_fex, mount::mount_filesystems, net::configure_network, + sommelier::exec_sommelier, user::setup_user, +}; +use utils::tracing::debug; + +fn main() -> Result<()> { + #[cfg(feature = "tracing")] + tracing_subscriber::fmt::init(); + + let options = options().run(); + + if let Err(err) = mount_filesystems() { + return Err(err).context("Couldn't mount filesystems, bailing out"); + } + + setup_fex()?; + + configure_network()?; + + if let Err(err) = setup_user(options.username, options.uid, options.gid) { + return Err(err).context("Couldn't set up user, bailing out"); + } + + // Will not return if successful. + exec_sommelier(&options.command, &options.command_args) + .context("Failed to execute sommelier")?; + + // Fallback option if sommelier is not present. + debug!(command = options.command, command_args = ?options.command_args, "exec"); + let err = Command::new(&options.command) + .args(options.command_args) + .exec(); + Err(err).with_context(|| format!("Failed to exec `{}`", options.command))? +} diff --git a/crates/krun-guest/src/cli_options.rs b/crates/krun-guest/src/cli_options.rs new file mode 100644 index 0000000..122fbb4 --- /dev/null +++ b/crates/krun-guest/src/cli_options.rs @@ -0,0 +1,54 @@ +use anyhow::Context; +use bpaf::{any, construct, positional, OptionParser, Parser}; +use nix::{ + libc::{gid_t, uid_t}, + unistd::{Gid, Uid}, +}; + +#[derive(Clone, Debug)] +pub struct Options { + pub username: String, + pub uid: Uid, + pub gid: Gid, + pub command: String, + pub command_args: Vec, +} + +pub fn options() -> OptionParser { + let username = positional("USER"); + let uid = positional::("UID").parse(|s| { + s.parse::() + .context("Failed to parse UID") + .map(|uid| uid.into()) + }); + let gid = positional::("GID").parse(|s| { + s.parse::() + .context("Failed to parse GID") + .map(|gid| gid.into()) + }); + let command = positional("COMMAND"); + let command_args = any::("COMMAND_ARGS", |arg| { + (!["--help", "-h"].contains(&&*arg)).then_some(arg) + }) + .many(); + + construct!(Options { + // positionals + username, + uid, + gid, + command, + command_args, + }) + .to_options() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_options() { + options().check_invariants(false) + } +} diff --git a/crates/krun-guest/src/fex.rs b/crates/krun-guest/src/fex.rs new file mode 100644 index 0000000..114c362 --- /dev/null +++ b/crates/krun-guest/src/fex.rs @@ -0,0 +1,37 @@ +use std::{fs::OpenOptions, io::Write}; + +use anyhow::{Context, Result}; +use utils::env::find_in_path; + +const FEX_X86_BINFMT_MISC_RULE: &str = ":FEX-x86:M:0:\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x03\\x00:\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:${FEX_INTERPRETER}:POCF"; +const FEX_X86_64_BINFMT_MISC_RULE: &str = ":FEX-x86_64:M:0:\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x3e\\x00:\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:${FEX_INTERPRETER}:POCF"; + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn setup_fex() -> Result<()> { + let fex_interpreter_path = + find_in_path("FEXInterpreter").context("Failed to check existence of `FEXInterpreter`")?; + let Some(fex_interpreter_path) = fex_interpreter_path else { + return Ok(()); + }; + let fex_interpreter_path = fex_interpreter_path + .to_str() + .context("Failed to process `FEXInterpreter` path as it contains invalid UTF-8")?; + + let mut file = OpenOptions::new() + .write(true) + .open("/proc/sys/fs/binfmt_misc/register") + .context("Failed to open binfmt_misc/register for writing")?; + + { + let rule = FEX_X86_BINFMT_MISC_RULE.replace("${FEX_INTERPRETER}", fex_interpreter_path); + file.write_all(rule.as_bytes()) + .context("Failed to register `FEX-x86` binfmt_misc rule")?; + } + { + let rule = FEX_X86_64_BINFMT_MISC_RULE.replace("${FEX_INTERPRETER}", fex_interpreter_path); + file.write_all(rule.as_bytes()) + .context("Failed to register `FEX-x86_64` binfmt_misc rule")?; + } + + Ok(()) +} diff --git a/crates/krun-guest/src/lib.rs b/crates/krun-guest/src/lib.rs new file mode 100644 index 0000000..4452084 --- /dev/null +++ b/crates/krun-guest/src/lib.rs @@ -0,0 +1,6 @@ +pub mod cli_options; +pub mod fex; +pub mod mount; +pub mod net; +pub mod sommelier; +pub mod user; diff --git a/crates/krun-guest/src/mount.rs b/crates/krun-guest/src/mount.rs new file mode 100644 index 0000000..e32f7fd --- /dev/null +++ b/crates/krun-guest/src/mount.rs @@ -0,0 +1,55 @@ +use std::{fs::OpenOptions, os::fd::AsFd}; + +use anyhow::{Context, Result}; +use rustix::{ + fs::CWD, + mount::{mount2, move_mount, open_tree, MountFlags, MoveMountFlags, OpenTreeFlags}, +}; + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn mount_filesystems() -> Result<()> { + mount2( + Some("tmpfs"), + "/var/run", + Some("tmpfs"), + MountFlags::NOEXEC | MountFlags::NOSUID | MountFlags::RELATIME, + None, + ) + .context("Failed to mount `/var/run`")?; + + let _ = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open("/tmp/resolv.conf") + .context("Failed to create `/tmp/resolv.conf`")?; + + { + let fd = open_tree( + CWD, + "/tmp/resolv.conf", + OpenTreeFlags::OPEN_TREE_CLONE | OpenTreeFlags::OPEN_TREE_CLOEXEC, + ) + .context("Failed to open_tree `/tmp/resolv.conf`")?; + + move_mount( + fd.as_fd(), + "", + CWD, + "/etc/resolv.conf", + MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH, + ) + .context("Failed to move_mount `/etc/resolv.conf`")?; + } + + mount2( + Some("binfmt_misc"), + "/proc/sys/fs/binfmt_misc", + Some("binfmt_misc"), + MountFlags::NOEXEC | MountFlags::NOSUID | MountFlags::RELATIME, + None, + ) + .context("Failed to mount `binfmt_misc`")?; + + Ok(()) +} diff --git a/crates/krun-guest/src/net.rs b/crates/krun-guest/src/net.rs new file mode 100644 index 0000000..2728486 --- /dev/null +++ b/crates/krun-guest/src/net.rs @@ -0,0 +1,40 @@ +use std::{fs, os::unix::process::ExitStatusExt, process::Command}; + +use anyhow::{anyhow, Context, Result}; +use rustix::system::sethostname; +use utils::tracing::debug; + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn configure_network() -> Result<()> { + { + let hostname = + fs::read_to_string("/etc/hostname").context("Failed to read `/etc/hostname`")?; + let hostname = if let Some((hostname, _)) = hostname.split_once('\n') { + hostname.to_owned() + } else { + hostname + }; + sethostname(hostname.as_bytes()).context("Failed to set hostname")?; + } + + let output = Command::new("/sbin/dhclient") + .output() + .context("Failed to execute `dhclient` as child process")?; + debug!(?output, "dhclient output"); + if !output.status.success() { + let err = if let Some(code) = output.status.code() { + anyhow!("`dhclient` process exited with status code: {code}") + } else { + anyhow!( + "`dhclient` process terminated by signal: {}", + output + .status + .signal() + .expect("either one of status code or signal should be set") + ) + }; + Err(err)?; + } + + Ok(()) +} diff --git a/crates/krun-guest/src/sommelier.rs b/crates/krun-guest/src/sommelier.rs new file mode 100644 index 0000000..05cf5e4 --- /dev/null +++ b/crates/krun-guest/src/sommelier.rs @@ -0,0 +1,28 @@ +use std::{os::unix::process::CommandExt as _, process::Command}; + +use anyhow::{Context, Result}; +use utils::{env::find_in_path, tracing::debug}; + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn exec_sommelier(command: &String, command_args: &[String]) -> Result<()> { + let sommelier_path = + find_in_path("sommelier").context("Failed to check existence of `sommelier`")?; + let Some(sommelier_path) = sommelier_path else { + return Ok(()); + }; + + let gl_env = std::env::var("LIBGL_DRIVERS_PATH").ok(); + + let mut cmd = Command::new(sommelier_path); + cmd.args(["--virtgpu-channel", "-X", "--glamor"]); + + if let Some(gl_env) = gl_env { + cmd.arg(format!("--xwayland-gl-driver-path={}", gl_env)); + } + + cmd.arg(command).args(command_args); + + debug!("exec"); + let err = cmd.exec(); + Err(err).context("Failed to exec `sommelier`")? +} diff --git a/crates/krun-guest/src/user.rs b/crates/krun-guest/src/user.rs new file mode 100644 index 0000000..a150c93 --- /dev/null +++ b/crates/krun-guest/src/user.rs @@ -0,0 +1,64 @@ +use std::{ + fs::{read_dir, Permissions}, + os::unix::fs::{chown, PermissionsExt as _}, +}; + +use anyhow::{anyhow, Context, Result}; +use nix::unistd::{setgid, setuid, Gid, Uid, User}; + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn setup_user(username: String, uid: Uid, gid: Gid) -> Result<()> { + setup_directories(uid, gid)?; + + setgid(gid).context("Failed to setgid")?; + setuid(uid).context("Failed to setuid")?; + + { + let path = tempfile::Builder::new() + .prefix(&format!("krun-run-{uid}-")) + .permissions(Permissions::from_mode(0o755)) + .tempdir() + .context("Failed to create temp dir for `XDG_RUNTIME_DIR`")? + .into_path(); + // SAFETY: safe if and only if `krun-guest` program is not multithreaded + // See https://doc.rust-lang.org/std/env/fn.set_var.html#safety + std::env::set_var("XDG_RUNTIME_DIR", path); + } + + let user = User::from_name(&username); + let Ok(Some(user)) = user else { + let err = if let Err(err) = user { + err.into() + } else { + anyhow!("requested entry not found") + }; + return Err(err) + .with_context(|| format!("Failed to get user `{username}` from user database")); + }; + + { + // SAFETY: safe if and only if `krun-guest` program is not multithreaded + // See https://doc.rust-lang.org/std/env/fn.set_var.html#safety + std::env::set_var("HOME", user.dir); + } + + Ok(()) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +fn setup_directories(uid: Uid, gid: Gid) -> Result<()> { + for dir in ["/dev/dri", "/dev/snd"] { + let dir_iter = + read_dir(dir).with_context(|| format!("Failed to read directory `{dir}`"))?; + + for entry in dir_iter { + let path = entry + .with_context(|| format!("Failed to read directory entry in `{dir}`"))? + .path(); + chown(&path, Some(uid.into()), Some(gid.into())) + .with_context(|| format!("Failed to chown `{path:?}`"))?; + } + } + + Ok(()) +} diff --git a/crates/krun/Cargo.toml b/crates/krun/Cargo.toml new file mode 100644 index 0000000..1f623fc --- /dev/null +++ b/crates/krun/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "krun" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +description = { workspace = true } +repository = { workspace = true } +license = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +arrayvec = "0.7.4" +bpaf = { workspace = true } +libkrun = { path = "../libkrun" } +nix = { workspace = true, features = ["process", "user"] } +rustix = { workspace = true, features = ["net", "process", "stdio"] } +tracing = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true } +utils = { path = "../utils" } + +[features] +default = ["tracing"] +tracing = ["dep:tracing", "dep:tracing-subscriber", "utils/tracing"] diff --git a/crates/krun/src/bin/krun.rs b/crates/krun/src/bin/krun.rs new file mode 100644 index 0000000..60ada31 --- /dev/null +++ b/crates/krun/src/bin/krun.rs @@ -0,0 +1,316 @@ +use std::{ + ffi::{c_char, CString}, + os::{ + fd::{IntoRawFd, OwnedFd}, + unix::process::ExitStatusExt as _, + }, + process::Command, +}; + +use anyhow::{anyhow, Context, Result}; +use krun::{ + cli_options::options, + net::{connect_to_passt, start_passt, NetMode}, +}; +use libkrun::{ + krun_create_ctx, krun_set_exec, krun_set_gpu_options, krun_set_log_level, krun_set_passt_fd, + krun_set_root, krun_set_vm_config, krun_set_workdir, krun_start_enter, VIRGLRENDERER_DRM, + VIRGLRENDERER_THREAD_SYNC, VIRGLRENDERER_USE_ASYNC_FENCE_CB, VIRGLRENDERER_USE_EGL, +}; +use nix::unistd::User; +use rustix::{ + io::Errno, + process::{geteuid, getgid, getrlimit, getuid, setrlimit, Resource}, +}; +use utils::{env::find_in_path, tracing::debug}; + +fn main() -> Result<()> { + #[cfg(feature = "tracing")] + tracing_subscriber::fmt::init(); + + if getuid().as_raw() == 0 || geteuid().as_raw() == 0 { + println!("Running as root is not supported as it may break your system"); + return Err(anyhow!("real user ID or effective user ID is 0")); + } + + let options = options().fallback_to_usage().run(); + + { + // Set the log level to "off". + // + // SAFETY: safe as no pointers involved + let err = unsafe { krun_set_log_level(0) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to configure log level"); + } + } + + let ctx_id = { + // Create the configuration context. + // + // SAFETY: safe as no pointers involved + let ctx_id = unsafe { krun_create_ctx() }; + if ctx_id < 0 { + let err = Errno::from_raw_os_error(-ctx_id); + return Err(err).context("Failed to create configuration context"); + } + ctx_id as u32 + }; + + { + // Configure the number of vCPUs (4) and the amount of RAM (4096 MiB). + // + // SAFETY: safe as no pointers involved + let err = unsafe { krun_set_vm_config(ctx_id, 4, 4096) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err) + .context("Failed to configure the number of vCPUs and/or the amount of RAM"); + } + } + + { + // Raise RLIMIT_NOFILE to the maximum allowed to create some room for virtio-fs + let mut rlim = getrlimit(Resource::Nofile); + rlim.current = rlim.maximum; + setrlimit(Resource::Nofile, rlim).context("Failed to raise `RLIMIT_NOFILE`")?; + } + + { + // SAFETY: safe as `root_path` is a pointer to a C-string literal + let err = unsafe { krun_set_root(ctx_id, c"/".as_ptr()) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to configure root path"); + } + } + + { + let virgl_flags = VIRGLRENDERER_USE_EGL + | VIRGLRENDERER_DRM + | VIRGLRENDERER_THREAD_SYNC + | VIRGLRENDERER_USE_ASYNC_FENCE_CB; + // SAFETY: safe as no pointers involved + let err = unsafe { krun_set_gpu_options(ctx_id, virgl_flags) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to configure gpu"); + } + } + + if options.net == NetMode::PASST { + let passt_fd: OwnedFd = if let Some(passt_socket) = options.passt_socket { + connect_to_passt(passt_socket) + .context("Failed to connect to `passt`")? + .into() + } else { + start_passt().context("Failed to start `passt`")? + }; + // SAFETY: safe as `passt_fd` is an `OwnedFd` and consumed to prevent closing on drop + // See https://doc.rust-lang.org/std/io/index.html#io-safety + let err = unsafe { krun_set_passt_fd(ctx_id, passt_fd.into_raw_fd()) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to configure net mode"); + } + } + + let username = std::env::var("USER").context("Failed to get username from environment")?; + let user = User::from_name(&username); + let Ok(Some(user)) = user else { + let err = if let Err(err) = user { + err.into() + } else { + anyhow!("requested entry not found") + }; + return Err(err) + .with_context(|| format!("Failed to get user `{username}` from user database")); + }; + let workdir_path = CString::new( + user.dir + .to_str() + .expect("workdir_path should not contain invalid UTF-8"), + ) + .expect("workdir_path should not contain NUL character"); + + { + // Set the working directory to the user's home directory, just for the sake of + // completeness. + // + // SAFETY: safe as `workdir_path` is a pointer to a `CString` with long enough lifetime + let err = unsafe { krun_set_workdir(ctx_id, workdir_path.as_ptr()) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).with_context(|| { + format!( + "Failed to configure `{}` as working directory", + workdir_path + .into_string() + .expect("workdir_path should not contain invalid UTF-8") + ) + }); + } + } + + let krun_guest_path = + find_in_path("krun-guest").context("Failed to check existence of `krun-guest`")?; + let krun_guest_path = if let Some(krun_guest_path) = krun_guest_path { + krun_guest_path + } else { + let krun_path = std::env::current_exe().and_then(|p| p.canonicalize()); + let krun_path = krun_path.context("Failed to get path of current running executable")?; + krun_path.with_file_name(format!( + "{}-guest", + krun_path + .file_name() + .expect("krun_path should end with a file name") + .to_str() + .context("Failed to process `krun` file name as it contains invalid UTF-8")? + )) + }; + let krun_guest_path = CString::new( + krun_guest_path + .to_str() + .context("Failed to process `krun-guest` path as it contains invalid UTF-8")?, + ) + .context("Failed to process `krun-guest` path as it contains NUL character")?; + + let mut krun_guest_args: Vec = vec![ + CString::new(username).expect("username should not contain NUL character"), + CString::new(format!("{}", getuid().as_raw())) + .expect("uid should not contain NUL character"), + CString::new(format!("{}", getgid().as_raw())) + .expect("gid should not contain NUL character"), + ]; + krun_guest_args.push( + CString::new(options.command) + .context("Failed to process command as it contains NUL character")?, + ); + let command_argc = options.command_args.len(); + for arg in options.command_args { + let s = CString::new(arg) + .context("Failed to process command arg as it contains NUL character")?; + krun_guest_args.push(s); + } + let krun_guest_args: Vec<*const c_char> = { + const KRUN_GUEST_ARGS_FIXED: usize = 4; + // SAFETY: all pointers must be stored in the same allocation + // See https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety + let mut vec = Vec::with_capacity(KRUN_GUEST_ARGS_FIXED + command_argc + 1); + for s in &krun_guest_args { + vec.push(s.as_ptr()); + } + vec.push(std::ptr::null()); + vec + }; + + let mut env: Vec = vec![]; + + let [ld_env, gl_env] = ["LD_LIBRARY_PATH", "LIBGL_DRIVERS_PATH"] + .map(|key| std::env::var(key).map_or(None, |value| Some((key, value)))); + if let (Some(ld_env), Some(gl_env)) = (ld_env, gl_env) { + for (key, value) in [ld_env, gl_env] { + let s = CString::new(format!("{key}={value}")).with_context(|| { + format!("Failed to process `{key}` env var as it contains NUL character") + })?; + env.push(s); + } + } + + if let Some(eglinfo_path) = + find_in_path("eglinfo").context("Failed to check existence of `eglinfo`")? + { + let output = Command::new(eglinfo_path) + .args(["-p", "wayland"]) + .env("LANG", "C") + .output() + .context("Failed to execute `eglinfo` as child process")?; + debug!(?output, "eglinfo output"); + if !output.status.success() { + let err = if let Some(code) = output.status.code() { + anyhow!("`eglinfo` process exited with status code: {code}") + } else { + anyhow!( + "`eglinfo` process terminated by signal: {}", + output + .status + .signal() + .expect("either one of status code or signal should be set") + ) + }; + Err(err)?; + } + + let is_asahi = std::str::from_utf8(&output.stdout) + .context("Failed to process stdout from `eglinfo` as it contains invalid UTF-8")? + .contains("EGL driver name: asahi"); + if is_asahi { + let s = CString::from(c"MESA_LOADER_DRIVER_OVERRIDE=asahi"); + env.push(s); + } + } + + if let Ok(rust_log_env) = std::env::var("RUST_LOG") { + let s = CString::new(format!("RUST_LOG={rust_log_env}")) + .context("Failed to process `RUST_LOG` env var as it contains NUL character")?; + env.push(s); + } + + { + // `krun-guest` program needs PATH + let path_env = std::env::var("PATH").context("Failed to get `PATH` env var")?; + let s = CString::new(format!("PATH={path_env}")) + .context("Failed to process `PATH` env var as it contains NUL character")?; + env.push(s); + } + + let env: Vec<*const c_char> = { + // SAFETY: all pointers must be stored in the same allocation + // See https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety + let mut vec = Vec::with_capacity(env.len() + 1); + for s in &env { + vec.push(s.as_ptr()); + } + vec.push(std::ptr::null()); + vec + }; + + { + // Specify the path of the binary to be executed in the isolated context, relative to + // the root path. + // + // SAFETY: safe as + // * `krun_guest_path` is a pointer to a `CString` with long enough lifetime + // * `krun_guest_args` is a pointer to a `Vec` of pointers to `CString`s all with long + // enough lifetime + // * `env` is a pointer to a `Vec` of pointers to `CString`s all with long enough lifetime + let err = unsafe { + krun_set_exec( + ctx_id, + krun_guest_path.as_ptr(), + krun_guest_args.as_ptr(), + env.as_ptr(), + ) + }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err) + .context("Failed to configure the parameters for the executable to be run"); + } + } + + { + // Start and enter the microVM. Unless there is some error while creating the microVM + // this function never returns. + // + // SAFETY: safe as no pointers involved + let err = unsafe { krun_start_enter(ctx_id) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to create the microVM"); + } + } + + unreachable!("`krun_start_enter` should never return"); +} diff --git a/crates/krun/src/cli_options.rs b/crates/krun/src/cli_options.rs new file mode 100644 index 0000000..8ec676d --- /dev/null +++ b/crates/krun/src/cli_options.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; + +use anyhow::anyhow; +use bpaf::{any, construct, long, positional, OptionParser, Parser}; + +use crate::net::NetMode; + +#[derive(Clone, Debug)] +pub struct Options { + pub net: NetMode, + pub passt_socket: Option, + pub command: String, + pub command_args: Vec, +} + +pub fn options() -> OptionParser { + let net = long("net") + .help( + "Set network mode + NET_MODE can be either TSI (default) or PASST", + ) + .argument::("NET_MODE") + .fallback("TSI".to_owned()) + .display_fallback() + .parse(|s| match &*s.to_ascii_uppercase() { + "PASST" => Ok(NetMode::PASST), + "TSI" => Ok(NetMode::TSI), + _ => Err(anyhow!("unknown mode {s}")), + }); + let passt_socket = long("passt-socket") + .help("Instead of starting passt, connect to passt socket at PATH") + .argument("PATH") + .optional(); + let command = positional("COMMAND").help("the command you want to execute in the vm"); + let command_args = any::("COMMAND_ARGS", |arg| { + (!["--help", "-h"].contains(&&*arg)).then_some(arg) + }) + .help("arguments of COMMAND") + .many(); + + construct!(Options { + net, + passt_socket, + // positionals + command, + command_args, + }) + .to_options() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_options() { + options().check_invariants(false) + } +} diff --git a/crates/krun/src/lib.rs b/crates/krun/src/lib.rs new file mode 100644 index 0000000..7e4db97 --- /dev/null +++ b/crates/krun/src/lib.rs @@ -0,0 +1,3 @@ +pub mod cli_options; +pub mod net; +pub mod utils; diff --git a/crates/krun/src/net.rs b/crates/krun/src/net.rs new file mode 100644 index 0000000..d412195 --- /dev/null +++ b/crates/krun/src/net.rs @@ -0,0 +1,113 @@ +use std::{ + ffi::CString, + fmt::{self, Write}, + os::{ + fd::{AsRawFd, OwnedFd}, + unix::{fs::PermissionsExt as _, net::UnixStream}, + }, + path::{Path, PathBuf}, + process::exit, +}; + +use anyhow::{Context, Result}; +use arrayvec::ArrayString; +use nix::unistd::{execv, fork, write, ForkResult}; +use rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType}; + +use crate::utils::print_error; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum NetMode { + PASST = 0, + TSI, +} + +/// Connects to an existing `passt` socket. +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn connect_to_passt

(passt_socket_path: P) -> Result +where + P: AsRef + fmt::Debug, +{ + Ok(UnixStream::connect(passt_socket_path)?) +} + +/// Starts `passt`. +/// +/// This does [fork(2)] and [exec(3)]. +/// +/// [exec(3)]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html +/// [fork(2)]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] +pub fn start_passt() -> Result { + let (parent_fd, child_fd) = socketpair( + AddressFamily::UNIX, + SocketType::STREAM, + SocketFlags::empty(), + None, + )?; + + let mut passt_path = PathBuf::from("passt"); + // Search for passt in PATH. + if let Ok(p) = std::env::var("PATH") { + for search_path in std::env::split_paths(&p) { + let pb = search_path.join("passt"); + if !pb.is_file() { + continue; + } + let Ok(metadata) = std::fs::metadata(&pb) else { + continue; + }; + if metadata.permissions().mode() & 0o111 != 0 { + passt_path = pb.canonicalize()?; + } + } + } + let passt_path = CString::new( + passt_path + .to_str() + .expect("passt_path should not contain invalid UTF-8"), + ) + .expect("passt_path should not contain NUL character"); + + // SAFETY: safe as `child_fd` is an `OwnedFd` and never dropped (as `execv` never returns) + // See https://doc.rust-lang.org/std/io/index.html#io-safety + let child_fd_str = CString::new(format!("{}", child_fd.as_raw_fd())) + .expect("child_fd string should not contain NUL character"); + + match unsafe { fork() } { + Ok(ForkResult::Parent { child: _, .. }) => { + drop(child_fd); + Ok(parent_fd) + }, + Ok(ForkResult::Child) => { + // SAFETY: child must only call async-signal-safe functions + // See https://docs.rs/nix/latest/nix/unistd/fn.fork.html#safety + + drop(parent_fd); + + { + const MAX_DIGITS: usize = 10; + const MAX_REST: usize = 20; + let mut s = ArrayString::<{ MAX_DIGITS + MAX_REST + 1 }>::new(); + // SAFETY: unsafe to use `print!` (or `unwrap`) here + // `write!` is safe here as `fmt::Write` impl for `ArrayString` does not allocate + writeln!(&mut s, "passing fd {} to passt", child_fd.as_raw_fd()).ok(); + write(std::io::stdout(), s.as_bytes()).ok(); + } + + // SAFETY: safe if and only if the `krun` program is not multithreaded + // See https://github.com/nix-rust/nix/issues/555#issuecomment-294422144 + if let Err(err) = execv( + &passt_path, + &[&*passt_path, c"-q", c"-f", c"--fd", &child_fd_str], + ) { + print_error(err); + // SAFETY: unsafe to `return` + exit(-1); + } + + unreachable!("`exec` should never return"); + }, + Err(err) => Err(err).context("Failed to fork child process"), + } +} diff --git a/crates/krun/src/utils.rs b/crates/krun/src/utils.rs new file mode 100644 index 0000000..491c873 --- /dev/null +++ b/crates/krun/src/utils.rs @@ -0,0 +1,22 @@ +use std::fmt::Write; + +use arrayvec::ArrayString; +use nix::{errno::Errno, unistd::write}; + +/// Prints an error in an [async-signal-safe] way. +/// +/// The output should be similar to [perror(3)]. +/// +/// [async-signal-safe]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03 +/// [perror(3)]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/perror.html +pub fn print_error(err: Errno) { + const MAX_CHARS: usize = 15; + let mut s = ArrayString::::new(); + // SAFETY: unsafe to use `print!` (or `unwrap`) here + // `write!` is safe here as `fmt::Write` impl for `ArrayString` does not allocate + write!(&mut s, "{err:?}").ok(); + write(std::io::stderr(), s.as_bytes()).ok(); + write(std::io::stderr(), ": ".as_bytes()).ok(); + write(std::io::stderr(), err.desc().as_bytes()).ok(); + write(std::io::stderr(), "\n".as_bytes()).ok(); +} diff --git a/crates/libkrun/Cargo.toml b/crates/libkrun/Cargo.toml new file mode 100644 index 0000000..b171295 --- /dev/null +++ b/crates/libkrun/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "libkrun" +version = "1.8.1" +edition = { workspace = true } +rust-version = { workspace = true } +repository = "https://github.com/containers/libkrun" +license = "Apache-2.0" +publish = false + +[build-dependencies] +bindgen = { workspace = true } diff --git a/crates/libkrun/build.rs b/crates/libkrun/build.rs new file mode 100644 index 0000000..c670f2b --- /dev/null +++ b/crates/libkrun/build.rs @@ -0,0 +1,16 @@ +use std::{env, path::PathBuf}; + +fn main() { + println!("cargo::rerun-if-changed=wrapper.h"); + println!("cargo::rustc-link-lib=krun"); + + let bindings = bindgen::Builder::default() + .header("wrapper.h") + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/crates/libkrun/src/lib.rs b/crates/libkrun/src/lib.rs new file mode 100644 index 0000000..32aa412 --- /dev/null +++ b/crates/libkrun/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/crates/libkrun/wrapper.h b/crates/libkrun/wrapper.h new file mode 100644 index 0000000..fba0181 --- /dev/null +++ b/crates/libkrun/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml new file mode 100644 index 0000000..f64a4c5 --- /dev/null +++ b/crates/utils/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "utils" +authors = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +publish = false + +[dependencies] +tracing = { workspace = true, optional = true } + +[features] +default = [] +tracing = ["dep:tracing"] diff --git a/crates/utils/src/env.rs b/crates/utils/src/env.rs new file mode 100644 index 0000000..31cc0cd --- /dev/null +++ b/crates/utils/src/env.rs @@ -0,0 +1,34 @@ +use std::{ + env, fs, io, + os::unix::fs::PermissionsExt as _, + path::{Path, PathBuf}, +}; + +pub fn find_in_path

(program: P) -> io::Result> +where + P: AsRef, +{ + let program = program.as_ref(); + // only accept a relative path with one component + if program.parent() != Some(Path::new("")) { + return Ok(None); + }; + + let Ok(p) = env::var("PATH") else { + return Ok(None); + }; + for search_path in env::split_paths(&p) { + let pb = search_path.join(program); + if !pb.is_file() { + continue; + } + let Ok(metadata) = fs::metadata(&pb) else { + continue; + }; + if metadata.permissions().mode() & 0o111 != 0 { + return pb.canonicalize().map(Some); + } + } + + Ok(None) +} diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs new file mode 100644 index 0000000..18436ee --- /dev/null +++ b/crates/utils/src/lib.rs @@ -0,0 +1,2 @@ +pub mod env; +pub mod tracing; diff --git a/crates/utils/src/tracing.rs b/crates/utils/src/tracing.rs new file mode 100644 index 0000000..9841b22 --- /dev/null +++ b/crates/utils/src/tracing.rs @@ -0,0 +1,44 @@ +#[macro_export] +macro_rules! trace { + ($($arg:tt)+) => { + #[cfg(feature = "tracing")] + ::tracing::trace!($($arg)+); + }; +} +pub use crate::trace; + +#[macro_export] +macro_rules! debug { + ($($arg:tt)+) => { + #[cfg(feature = "tracing")] + ::tracing::debug!($($arg)+); + }; +} +pub use crate::debug; + +#[macro_export] +macro_rules! info { + ($($arg:tt)+) => { + #[cfg(feature = "tracing")] + ::tracing::info!($($arg)+); + }; +} +pub use crate::info; + +#[macro_export] +macro_rules! warn { + ($($arg:tt)+) => { + #[cfg(feature = "tracing")] + ::tracing::warn!($($arg)+); + }; +} +pub use crate::warn; + +#[macro_export] +macro_rules! error { + ($($arg:tt)+) => { + #[cfg(feature = "tracing")] + ::tracing::error!($($arg)+); + }; +} +pub use crate::error; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..bba0995 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,18 @@ +edition = "2021" + +# empty_item_single_line = false +# error_on_line_overflow = true +# format_code_in_doc_comments = true +# format_strings = true +# group_imports = "StdExternalCrate" +# imports_granularity = "Crate" +# imports_layout = "HorizontalVertical" +match_block_trailing_comma = true +newline_style = "Unix" +# normalize_comments = true +# normalize_doc_attributes = true +# overflow_delimited_expr = true +# reorder_impl_items = true +use_field_init_shorthand = true +use_try_shorthand = true +# wrap_comments = true