Compare commits
21 commits
__assets__
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c42eaef554 | ||
|
|
eedf1f889d | ||
|
|
52a0ccee0d | ||
|
|
95bc64076d | ||
|
|
2626130659 | ||
|
|
2561342e0c | ||
|
|
762dd2dd84 | ||
|
|
d145ee2b5c | ||
|
|
1315858592 | ||
|
|
c9095421c6 | ||
|
|
2456dc0ea5 | ||
|
|
df832a5141 | ||
|
|
1d931ae191 | ||
|
|
52c3ea7cd3 | ||
|
|
ea34b7b08c | ||
|
|
fa0bf056d0 | ||
|
|
52ad012e13 | ||
|
|
73c92e3e69 | ||
|
|
4fd76692db | ||
|
|
250c459866 | ||
|
|
50ea609880 |
18 changed files with 902 additions and 301 deletions
94
Cargo.lock
generated
94
Cargo.lock
generated
|
|
@ -142,8 +142,8 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "busd"
|
name = "busd"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
source = "git+https://github.com/valpackett/busd?branch=val%2Fmsksqvsqqrxm#25736c855284b13371f0be2ef38f16af8f73bda1"
|
source = "git+https://github.com/valpackett/busd?branch=val%2Fmsksqvsqqrxm#7084025107e02600043856c56fd178730526daad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -151,8 +151,8 @@ dependencies = [
|
||||||
"event-listener",
|
"event-listener",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"nix 0.30.1",
|
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -635,15 +635,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.174"
|
version = "0.2.178"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.9.4"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "listenfd"
|
name = "listenfd"
|
||||||
|
|
@ -726,19 +726,6 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nix"
|
|
||||||
version = "0.30.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"cfg_aliases",
|
|
||||||
"libc",
|
|
||||||
"memoffset",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
|
@ -871,9 +858,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.0"
|
version = "0.39.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
|
checksum = "f2e3bf4aa9d243beeb01a7b3bc30b77cfe2c44e24ec02d751a7104a53c2c49a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -940,15 +927,15 @@ checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.8"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1026,6 +1013,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"eyre",
|
"eyre",
|
||||||
"futures",
|
"futures",
|
||||||
|
"libc",
|
||||||
"rand",
|
"rand",
|
||||||
"rustix",
|
"rustix",
|
||||||
"sidebus-common",
|
"sidebus-common",
|
||||||
|
|
@ -1321,7 +1309,9 @@ version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1338,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e8b4d00e672f147fc86a09738fadb1445bd1c0a40542378dfb82909deeee688"
|
checksum = "4e8b4d00e672f147fc86a09738fadb1445bd1c0a40542378dfb82909deeee688"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"nix 0.29.0",
|
"nix",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1436,6 +1426,12 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
@ -1463,6 +1459,15 @@ dependencies = [
|
||||||
"windows-targets 0.53.2",
|
"windows-targets 0.53.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -1651,8 +1656,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "5.9.0"
|
version = "5.14.0"
|
||||||
source = "git+https://github.com/dbus2/zbus#6da6b1b5f528fe2d14b0f25ae1dca1a9fd31575c"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
|
|
@ -1662,16 +1668,17 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"hex",
|
"hex",
|
||||||
"nix 0.30.1",
|
"libc",
|
||||||
"ordered-stream",
|
"ordered-stream",
|
||||||
"rand",
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-vsock",
|
"tokio-vsock",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uds_windows",
|
"uds_windows",
|
||||||
"windows-sys 0.60.2",
|
"uuid",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
"winnow",
|
"winnow",
|
||||||
"zbus_macros",
|
"zbus_macros",
|
||||||
"zbus_names",
|
"zbus_names",
|
||||||
|
|
@ -1680,8 +1687,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_macros"
|
name = "zbus_macros"
|
||||||
version = "5.9.0"
|
version = "5.14.0"
|
||||||
source = "git+https://github.com/dbus2/zbus#6da6b1b5f528fe2d14b0f25ae1dca1a9fd31575c"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1694,8 +1702,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_names"
|
name = "zbus_names"
|
||||||
version = "4.2.0"
|
version = "4.3.1"
|
||||||
source = "git+https://github.com/dbus2/zbus#6da6b1b5f528fe2d14b0f25ae1dca1a9fd31575c"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"winnow",
|
"winnow",
|
||||||
|
|
@ -1778,8 +1787,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.6.0"
|
version = "5.10.0"
|
||||||
source = "git+https://github.com/dbus2/zbus#6da6b1b5f528fe2d14b0f25ae1dca1a9fd31575c"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"endi",
|
"endi",
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
|
|
@ -1791,8 +1801,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant_derive"
|
name = "zvariant_derive"
|
||||||
version = "5.6.0"
|
version = "5.10.0"
|
||||||
source = "git+https://github.com/dbus2/zbus#6da6b1b5f528fe2d14b0f25ae1dca1a9fd31575c"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1803,8 +1814,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant_utils"
|
name = "zvariant_utils"
|
||||||
version = "3.2.0"
|
version = "3.3.0"
|
||||||
source = "git+https://github.com/dbus2/zbus#6da6b1b5f528fe2d14b0f25ae1dca1a9fd31575c"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,4 @@ members = [
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
sidebus-common = { path = "sidebus-common" }
|
sidebus-common = { path = "sidebus-common" }
|
||||||
busd = { git = "https://github.com/valpackett/busd", branch = "val/msksqvsqqrxm", default-features = false }
|
busd = { git = "https://github.com/valpackett/busd", branch = "val/msksqvsqqrxm", default-features = false }
|
||||||
zbus = { git = "https://github.com/dbus2/zbus", default-features = false, features = ["tokio", "tokio-vsock", "bus-impl", "p2p"] }
|
zbus = { version = "5.0", default-features = false, features = ["tokio", "tokio-vsock", "bus-impl", "p2p"] }
|
||||||
# zbus git to match busd git
|
|
||||||
|
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
(it.. runs as a "sidecar" to a VM.. but manages D-*Bus*.. get it?)
|
(it.. runs as a "sidecar" to a VM.. but manages D-*Bus*.. get it?)
|
||||||
|
|
||||||
|
<video controls muted src="https://git.clan.lol/valpackett/sidebus/releases/download/__assets__/vm-filechooser-portal-2.webm">
|
||||||
|
<p><a href="https://git.clan.lol/valpackett/sidebus/releases/download/__assets__/vm-filechooser-portal-2.webm">Watch demo →</a></p>
|
||||||
|
</video>
|
||||||
|
|
||||||
A cross-domain smart D-Bus proxying system that makes (some) [XDG Desktop Portals] work across virtual machines.
|
A cross-domain smart D-Bus proxying system that makes (some) [XDG Desktop Portals] work across virtual machines.
|
||||||
|
|
||||||
[XDG Desktop Portals]: https://flatpak.github.io/xdg-desktop-portal/docs/index.html
|
[XDG Desktop Portals]: https://flatpak.github.io/xdg-desktop-portal/docs/index.html
|
||||||
|
|
@ -10,9 +14,8 @@ A cross-domain smart D-Bus proxying system that makes (some) [XDG Desktop Portal
|
||||||
|
|
||||||
- `sidebus-broker` host process:
|
- `sidebus-broker` host process:
|
||||||
- to be launched alongside the VMM
|
- to be launched alongside the VMM
|
||||||
- hosts D-Bus servers in-process, based on [busd](https://github.com/dbus2/busd):
|
- hosts a "private" bus for VM-instance-specific daemons such as permission-store and document-portal
|
||||||
- a "private" bus for VM-instance-specific daemons such as permission-store and document-portal
|
- listens on vsock (or on a unix socket that muvm would proxy as vsock) and connects to the VM bus as a client when the agent connects
|
||||||
- a "VM" bus, the one actually exposed to the guest over vsock
|
|
||||||
- orchestrates the lifecycle of the aforementioned daemons + virtiofsd
|
- orchestrates the lifecycle of the aforementioned daemons + virtiofsd
|
||||||
- (we are sharing the directory *provided by* the document-portal FUSE filesystem!)
|
- (we are sharing the directory *provided by* the document-portal FUSE filesystem!)
|
||||||
- provides portal front-end interfaces like `org.freedesktop.portal.FileChooser` on the VM bus
|
- provides portal front-end interfaces like `org.freedesktop.portal.FileChooser` on the VM bus
|
||||||
|
|
@ -20,11 +23,10 @@ A cross-domain smart D-Bus proxying system that makes (some) [XDG Desktop Portal
|
||||||
- (not talking directly to impls: don't want to reimplement per-DE portal selection; also 1:1 mapping is nicer to code)
|
- (not talking directly to impls: don't want to reimplement per-DE portal selection; also 1:1 mapping is nicer to code)
|
||||||
- but with extra hooks like exposing files to the guest using our private (per-VM) document-portal!
|
- but with extra hooks like exposing files to the guest using our private (per-VM) document-portal!
|
||||||
- `sidebus-agent` guest process:
|
- `sidebus-agent` guest process:
|
||||||
- listens on a guest unix socket, proxies D-Bus messages to a vsock
|
- connects to the broker over vsock and splices the connection into the VM (session) bus
|
||||||
- spawned on-demand by systemd via socket activation
|
- can be spawned spawned on-demand by D-Bus
|
||||||
- uses systemd credentials for config args like vsock port
|
- uses systemd credentials for config args like vsock port
|
||||||
- (very convenient to pass via the VMM, e.g. qemu: `-smbios type=11,value=io.systemd.credential:sidebus.port=1337`)
|
- (very convenient to pass via the VMM, e.g. qemu: `-smbios type=11,value=io.systemd.credential:sidebus.port=1337`)
|
||||||
- guest NixOS configuration exposed via the flake
|
|
||||||
|
|
||||||
## Development Notes
|
## Development Notes
|
||||||
|
|
||||||
|
|
|
||||||
66
flake.lock
generated
66
flake.lock
generated
|
|
@ -1,30 +1,32 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"nixpkgs-lib": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1768135262,
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||||
"owner": "numtide",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-parts",
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-parts",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751271578,
|
"lastModified": 1762977756,
|
||||||
"narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=",
|
"narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df",
|
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -36,44 +38,8 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-parts": "flake-parts",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"rust-overlay": "rust-overlay"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-overlay": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1751510438,
|
|
||||||
"narHash": "sha256-m8PjOoyyCR4nhqtHEBP1tB/jF+gJYYguSZmUmVTEAQE=",
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"rev": "7f415261f298656f8164bd636c0dc05af4e95b6b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
114
flake.nix
114
flake.nix
|
|
@ -1,80 +1,60 @@
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
rust-overlay = {
|
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||||
url = "github:oxalica/rust-overlay";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {self, nixpkgs, flake-utils, rust-overlay}:
|
outputs =
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
inputs@{ flake-parts, ... }:
|
||||||
let
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
overlays = [ (import rust-overlay) ];
|
systems = [
|
||||||
pkgs = import nixpkgs {
|
"x86_64-linux"
|
||||||
inherit system overlays;
|
"aarch64-linux"
|
||||||
};
|
];
|
||||||
|
|
||||||
buildEnvVars = {
|
perSystem =
|
||||||
BIN_XDG_PERMISSION_STORE = "${pkgs.xdg-desktop-portal}/libexec/xdg-permission-store";
|
{ pkgs, ... }:
|
||||||
BIN_XDG_DOCUMENT_PORTAL = "${pkgs.xdg-desktop-portal}/libexec/xdg-document-portal";
|
let
|
||||||
BIN_VIRTIOFSD = "${pkgs.virtiofsd}/bin/virtiofsd";
|
buildEnvVars = {
|
||||||
};
|
BIN_XDG_PERMISSION_STORE = "${pkgs.xdg-desktop-portal}/libexec/xdg-permission-store";
|
||||||
|
BIN_XDG_DOCUMENT_PORTAL = "${pkgs.xdg-desktop-portal}/libexec/xdg-document-portal";
|
||||||
|
BIN_VIRTIOFSD = "${pkgs.virtiofsd}/bin/virtiofsd";
|
||||||
|
};
|
||||||
|
|
||||||
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
rustPackage =
|
||||||
rustPlatform = pkgs.makeRustPlatform {
|
crate:
|
||||||
cargo = rustToolchain;
|
let
|
||||||
rustc = rustToolchain;
|
cargoToml = builtins.fromTOML (builtins.readFile ./${crate}/Cargo.toml);
|
||||||
};
|
in
|
||||||
rustPackage = crate:
|
pkgs.rustPlatform.buildRustPackage {
|
||||||
let cargoToml = builtins.fromTOML (builtins.readFile ./${crate}/Cargo.toml);
|
inherit (cargoToml.package) name version;
|
||||||
in rustPlatform.buildRustPackage {
|
src = ./.;
|
||||||
inherit (cargoToml.package) name version;
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
src = ./.;
|
cargoLock.outputHashes = {
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
"busd-0.5.0" = "sha256-IZZ2MeEmUbzRrH6SUz0pnecMH4f8Mh54WdhI4q44YfI=";
|
||||||
cargoLock.outputHashes = {
|
};
|
||||||
"zbus-5.9.0" = "sha256-3xaKbf+JmO5yVwPbvA3z9dHvqICh7yCeKk1SIX8zhJA=";
|
buildAndTestSubdir = crate;
|
||||||
"busd-0.4.0" = "sha256-UzTclEJ8lRMmiuLJJi+gsm7vkx+MLfnDdi5s9OVT1HE=";
|
env = buildEnvVars;
|
||||||
};
|
};
|
||||||
buildAndTestSubdir = crate;
|
in
|
||||||
|
{
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
cargo
|
||||||
|
rustc
|
||||||
|
rust-analyzer
|
||||||
|
clippy
|
||||||
|
];
|
||||||
env = buildEnvVars;
|
env = buildEnvVars;
|
||||||
};
|
};
|
||||||
in
|
|
||||||
{
|
packages.sidebus-agent = rustPackage "sidebus-agent";
|
||||||
devShells.default = pkgs.mkShell {
|
packages.sidebus-broker = rustPackage "sidebus-broker";
|
||||||
buildInputs = [ rustToolchain ];
|
|
||||||
env = buildEnvVars;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
packages.sidebus-agent = rustPackage "sidebus-agent";
|
flake = {
|
||||||
packages.sidebus-broker = rustPackage "sidebus-broker";
|
nixosModules.sidebus-vm = ./nixosModules/sidebus-vm.nix;
|
||||||
|
};
|
||||||
nixosModules.sidebus-vm = { ... }: {
|
};
|
||||||
environment.sessionVariables.DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/sidebus.sock";
|
|
||||||
systemd.sockets.sidebus-agent = {
|
|
||||||
# SocketMode= is 0666 by default
|
|
||||||
listenStreams = [ "/run/sidebus.sock" ];
|
|
||||||
wantedBy = [ "sockets.target" ];
|
|
||||||
documentation = [ "https://git.clan.lol/valpackett/sidebus" ];
|
|
||||||
};
|
|
||||||
systemd.services.sidebus-agent = {
|
|
||||||
# TODO: confinement (can do a lot)
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = "${rustPackage "sidebus-agent"}/bin/sidebus-agent";
|
|
||||||
ImportCredential = "sidebus.*";
|
|
||||||
};
|
|
||||||
documentation = [ "https://git.clan.lol/valpackett/sidebus" ];
|
|
||||||
};
|
|
||||||
systemd.mounts = [
|
|
||||||
{
|
|
||||||
type = "virtiofs";
|
|
||||||
what = "vm-doc-portal";
|
|
||||||
where = "/run/vm-doc-portal";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
nixosModules/sidebus-vm.nix
Normal file
25
nixosModules/sidebus-vm.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
environment.sessionVariables.DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/sidebus.sock";
|
||||||
|
systemd.sockets.sidebus-agent = {
|
||||||
|
# SocketMode= is 0666 by default
|
||||||
|
listenStreams = [ "/run/sidebus.sock" ];
|
||||||
|
wantedBy = [ "sockets.target" ];
|
||||||
|
documentation = [ "https://git.clan.lol/valpackett/sidebus" ];
|
||||||
|
};
|
||||||
|
systemd.services.sidebus-agent = {
|
||||||
|
# TODO: confinement (can do a lot)
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = throw "sidebus-vm module requires setting systemd.services.sidebus-agent.serviceConfig.ExecStart to a sidebus-agent package";
|
||||||
|
ImportCredential = "sidebus.*";
|
||||||
|
};
|
||||||
|
documentation = [ "https://git.clan.lol/valpackett/sidebus" ];
|
||||||
|
};
|
||||||
|
systemd.mounts = [
|
||||||
|
{
|
||||||
|
type = "virtiofs";
|
||||||
|
what = "vm-doc-portal";
|
||||||
|
where = "/run/vm-doc-portal";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
channel = "1.88.0"
|
|
||||||
components = ["rust-analyzer", "rust-src", "clippy"]
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use eyre::OptionExt;
|
|
||||||
use tokio::net::UnixListener;
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -10,38 +8,23 @@ async fn main() -> eyre::Result<()> {
|
||||||
let vsock_port = std::fs::read_to_string(creds_dir.join("sidebus.port"))?
|
let vsock_port = std::fs::read_to_string(creds_dir.join("sidebus.port"))?
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<u32>()?;
|
.parse::<u32>()?;
|
||||||
|
let vsock_addr = zbus::Address::from(zbus::address::Transport::Vsock(
|
||||||
|
zbus::address::transport::Vsock::new(2, vsock_port),
|
||||||
|
));
|
||||||
|
|
||||||
let unix_listener = UnixListener::from_std(
|
info!("connecting to session bus");
|
||||||
listenfd::ListenFd::from_env()
|
let session_conn = zbus::connection::Builder::session()?.p2p().build().await?;
|
||||||
.take_unix_listener(0)?
|
|
||||||
.ok_or_eyre("no unix listener provided")?,
|
let vsock_conn = zbus::connection::Builder::address(vsock_addr)?
|
||||||
)?;
|
.server(session_conn.server_guid())
|
||||||
info!("listening for unix clients");
|
.unwrap()
|
||||||
|
.p2p()
|
||||||
|
.auth_mechanism(zbus::AuthMechanism::Anonymous)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
info!(guid = %vsock_conn.server_guid(), "connected to vsock bus");
|
||||||
|
sidebus_common::raw::splice_conns(vsock_conn, session_conn).await;
|
||||||
|
|
||||||
while let Ok((unix_client, client_addr)) = unix_listener.accept().await {
|
|
||||||
info!(?client_addr, "new unix client");
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let vsock_addr = zbus::Address::from(zbus::address::Transport::Vsock(
|
|
||||||
zbus::address::transport::Vsock::new(2, vsock_port),
|
|
||||||
));
|
|
||||||
let vsock_conn = zbus::connection::Builder::address(vsock_addr)
|
|
||||||
.unwrap()
|
|
||||||
.p2p()
|
|
||||||
.auth_mechanism(zbus::AuthMechanism::Anonymous)
|
|
||||||
.build()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
info!(guid = %vsock_conn.server_guid(), "connected to vsock bus");
|
|
||||||
let client_conn = zbus::connection::Builder::unix_stream(unix_client)
|
|
||||||
.server(vsock_conn.server_guid())
|
|
||||||
.unwrap()
|
|
||||||
.p2p()
|
|
||||||
.auth_mechanism(zbus::AuthMechanism::External)
|
|
||||||
.build()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
sidebus_common::raw::splice_conns(client_conn, vsock_conn).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,4 @@ rustix = { version = "1.0.8", features = ["fs"] }
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
libc = "0.2.178"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::{hash::DefaultHasher, sync::Arc};
|
||||||
use tokio_stream::StreamExt as _;
|
use tokio_stream::StreamExt as _;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
pub struct HostedBus {
|
pub struct HostedBus {
|
||||||
peers: Arc<busd::peers::Peers>,
|
peers: Arc<busd::peers::Peers>,
|
||||||
|
|
@ -57,10 +57,14 @@ impl HostedBus {
|
||||||
.map_err(|err| eyre::eyre!(Box::new(err))) // https://github.com/eyre-rs/eyre/issues/31 XXX: busd should not use anyhow!
|
.map_err(|err| eyre::eyre!(Box::new(err))) // https://github.com/eyre-rs/eyre/issues/31 XXX: busd should not use anyhow!
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect_unix(&mut self, socket: tokio::net::UnixStream) -> eyre::Result<()> {
|
pub async fn connect_unix(
|
||||||
|
&mut self,
|
||||||
|
socket: tokio::net::UnixStream,
|
||||||
|
auth: zbus::AuthMechanism,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
let id = self.next_id();
|
let id = self.next_id();
|
||||||
self.peers
|
self.peers
|
||||||
.add(&self.guid, id, socket.into(), zbus::AuthMechanism::External)
|
.add(&self.guid, id, socket.into(), auth)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| eyre::eyre!(Box::new(err)))
|
.map_err(|err| eyre::eyre!(Box::new(err)))
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +81,7 @@ impl HostedBus {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SharedHostedBus {
|
pub trait SharedHostedBus {
|
||||||
async fn run_unix_listener(self, listener: tokio::net::UnixListener);
|
async fn run_unix_listener(self, listener: tokio::net::UnixListener, auth: zbus::AuthMechanism);
|
||||||
async fn spawn_external_client(
|
async fn spawn_external_client(
|
||||||
self,
|
self,
|
||||||
command: &mut tokio::process::Command,
|
command: &mut tokio::process::Command,
|
||||||
|
|
@ -85,9 +89,15 @@ pub trait SharedHostedBus {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedHostedBus for Arc<tokio::sync::Mutex<HostedBus>> {
|
impl SharedHostedBus for Arc<tokio::sync::Mutex<HostedBus>> {
|
||||||
async fn run_unix_listener(self, listener: tokio::net::UnixListener) {
|
async fn run_unix_listener(
|
||||||
|
self,
|
||||||
|
listener: tokio::net::UnixListener,
|
||||||
|
auth: zbus::AuthMechanism,
|
||||||
|
) {
|
||||||
while let Ok((socket, _remote_addr)) = listener.accept().await {
|
while let Ok((socket, _remote_addr)) = listener.accept().await {
|
||||||
self.lock().await.connect_unix(socket).await.unwrap()
|
if let Err(e) = self.lock().await.connect_unix(socket, auth).await {
|
||||||
|
error!("unix connection: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +110,7 @@ impl SharedHostedBus for Arc<tokio::sync::Mutex<HostedBus>> {
|
||||||
let abstract_path = format!("/run/sidebus-broker/{}", zbus::Guid::generate());
|
let abstract_path = format!("/run/sidebus-broker/{}", zbus::Guid::generate());
|
||||||
let listener = tokio::net::UnixListener::bind(format!("\0{abstract_path}"))?;
|
let listener = tokio::net::UnixListener::bind(format!("\0{abstract_path}"))?;
|
||||||
debug!(%abstract_path, "opened listener for external client");
|
debug!(%abstract_path, "opened listener for external client");
|
||||||
tokio::spawn(self.run_unix_listener(listener));
|
tokio::spawn(self.run_unix_listener(listener, zbus::AuthMechanism::External));
|
||||||
Ok(command
|
Ok(command
|
||||||
.env(
|
.env(
|
||||||
"DBUS_SESSION_BUS_ADDRESS",
|
"DBUS_SESSION_BUS_ADDRESS",
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@ mod vsock;
|
||||||
|
|
||||||
use bus::SharedHostedBus;
|
use bus::SharedHostedBus;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use eyre::OptionExt;
|
||||||
|
use futures::{TryFutureExt, stream::FuturesUnordered};
|
||||||
|
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||||
use tokio::{net::UnixListener, process::Command, sync::Mutex};
|
use tokio::{net::UnixListener, process::Command, sync::Mutex};
|
||||||
use tracing::error;
|
use tokio_stream::StreamExt as _;
|
||||||
|
use tracing::{Instrument, debug, error, info_span};
|
||||||
use zbus::names::WellKnownName;
|
use zbus::names::WellKnownName;
|
||||||
|
|
||||||
// https://github.com/rust-lang/rfcs/issues/2407#issuecomment-385291238
|
// https://github.com/rust-lang/rfcs/issues/2407#issuecomment-385291238
|
||||||
|
|
@ -22,7 +25,7 @@ macro_rules! enclose {
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct BrokerCli {
|
struct BrokerCli {
|
||||||
/// Create unix socket listeners for all internal busses in the provided directory
|
/// Create unix socket listeners for internal busses in the provided directory
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
debug_access: Option<PathBuf>,
|
debug_access: Option<PathBuf>,
|
||||||
|
|
||||||
|
|
@ -34,9 +37,29 @@ struct BrokerCli {
|
||||||
#[clap(long, default_value = "/run/vm-doc-portal")]
|
#[clap(long, default_value = "/run/vm-doc-portal")]
|
||||||
guest_mountpoint: PathBuf,
|
guest_mountpoint: PathBuf,
|
||||||
|
|
||||||
/// Vsock port number to listen on
|
/// Mappings from guest paths to host paths for passthrough file systems (for file transfer), in guest=host format
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
vsock_port: u32,
|
path_mapping: Vec<String>,
|
||||||
|
|
||||||
|
/// Vsock port number to listen on for the VM bus
|
||||||
|
#[clap(long)]
|
||||||
|
vsock_port: Option<u32>,
|
||||||
|
|
||||||
|
/// Unix socket path to listen on for the VM bus
|
||||||
|
#[clap(long)]
|
||||||
|
unix_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Use ANONYMOUS auth to connect to the guest bus instead of EXTERNAL with the provided --guest-uid
|
||||||
|
#[clap(long)]
|
||||||
|
guest_bus_anonymous_auth: bool,
|
||||||
|
|
||||||
|
/// The user ID for the appvm user inside of the guest
|
||||||
|
#[clap(long, default_value = "1337")]
|
||||||
|
guest_uid: u32,
|
||||||
|
|
||||||
|
/// The group ID for the appvm group inside of the guest
|
||||||
|
#[clap(long, default_value = "1337")]
|
||||||
|
guest_gid: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_hosted_bus() -> eyre::Result<(
|
async fn new_hosted_bus() -> eyre::Result<(
|
||||||
|
|
@ -51,110 +74,272 @@ async fn new_hosted_bus() -> eyre::Result<(
|
||||||
Ok((Arc::new(Mutex::new(bus)), guid, owner_stream))
|
Ok((Arc::new(Mutex::new(bus)), guid, owner_stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_path_mapping(s: &str) -> eyre::Result<(PathBuf, PathBuf)> {
|
||||||
|
let mut split = s.split('=');
|
||||||
|
let guest_path = PathBuf::from(split.next().ok_or_eyre("failed to split mapping")?);
|
||||||
|
let host_path = PathBuf::from(split.next().ok_or_eyre("failed to split mapping")?);
|
||||||
|
Ok((guest_path, host_path))
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let cli = BrokerCli::parse();
|
let cli = BrokerCli::parse();
|
||||||
|
let mut path_prefix_to_host: Vec<(PathBuf, PathBuf)> = cli
|
||||||
|
.path_mapping
|
||||||
|
.iter()
|
||||||
|
.flat_map(|arg| match parse_path_mapping(arg) {
|
||||||
|
Ok(mapping) => Some(mapping),
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, %arg, "could not parse path mapping");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
path_prefix_to_host.sort_unstable_by_key(|(prefix, _)| -(prefix.as_os_str().len() as isize));
|
||||||
|
debug!(?path_prefix_to_host, "parsed path mappings");
|
||||||
|
|
||||||
let (vm_bus, vm_bus_guid, _) = new_hosted_bus().await?;
|
|
||||||
let (priv_bus, _, mut priv_lst) = new_hosted_bus().await?;
|
let (priv_bus, _, mut priv_lst) = new_hosted_bus().await?;
|
||||||
|
|
||||||
|
let mut server_tasks = tokio::task::JoinSet::new();
|
||||||
|
let mut child_procs = Vec::new();
|
||||||
|
|
||||||
if let Some(dir_path) = cli.debug_access {
|
if let Some(dir_path) = cli.debug_access {
|
||||||
if !dir_path.is_dir() {
|
if !dir_path.is_dir() {
|
||||||
error!(path = %dir_path.display(), "--debug-access path is not an existing directory");
|
error!(path = %dir_path.display(), "--debug-access path is not an existing directory");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
let vm_dbg_listener = UnixListener::bind(dir_path.join("vm.sock"))?;
|
|
||||||
let _vm_dbg_task = tokio::spawn(vm_bus.clone().run_unix_listener(vm_dbg_listener));
|
|
||||||
let priv_dbg_listener = UnixListener::bind(dir_path.join("priv.sock"))?;
|
let priv_dbg_listener = UnixListener::bind(dir_path.join("priv.sock"))?;
|
||||||
let _priv_dbg_task = tokio::spawn(priv_bus.clone().run_unix_listener(priv_dbg_listener));
|
server_tasks.spawn(
|
||||||
|
priv_bus
|
||||||
|
.clone()
|
||||||
|
.run_unix_listener(priv_dbg_listener, zbus::AuthMechanism::External),
|
||||||
|
);
|
||||||
// TODO: unlink sockets on exit
|
// TODO: unlink sockets on exit
|
||||||
}
|
}
|
||||||
|
|
||||||
std::fs::create_dir_all(&cli.runtime_dir)?;
|
std::fs::create_dir_all(&cli.runtime_dir)?;
|
||||||
|
|
||||||
let _xps = priv_bus
|
child_procs.push(
|
||||||
.clone()
|
priv_bus
|
||||||
.spawn_external_client(
|
.clone()
|
||||||
Command::new(env!("BIN_XDG_PERMISSION_STORE"))
|
.spawn_external_client(
|
||||||
.env("XDG_RUNTIME_DIR", cli.runtime_dir.as_os_str())
|
Command::new(env!("BIN_XDG_PERMISSION_STORE"))
|
||||||
.kill_on_drop(true),
|
.env("XDG_RUNTIME_DIR", cli.runtime_dir.as_os_str())
|
||||||
)
|
.kill_on_drop(true),
|
||||||
.await?;
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
let impl_permission_store =
|
let impl_permission_store =
|
||||||
WellKnownName::from_static_str("org.freedesktop.impl.portal.PermissionStore")?.into();
|
WellKnownName::from_static_str("org.freedesktop.impl.portal.PermissionStore")?.into();
|
||||||
priv_lst.wait_for_acquisition(impl_permission_store).await?;
|
priv_lst.wait_for_acquisition(impl_permission_store).await?;
|
||||||
|
|
||||||
let _xdp = priv_bus
|
child_procs.push(
|
||||||
.clone()
|
priv_bus
|
||||||
.spawn_external_client(
|
.clone()
|
||||||
Command::new(env!("BIN_XDG_DOCUMENT_PORTAL"))
|
.spawn_external_client(
|
||||||
.env("XDG_RUNTIME_DIR", cli.runtime_dir.as_os_str())
|
Command::new(env!("BIN_XDG_DOCUMENT_PORTAL"))
|
||||||
.kill_on_drop(true),
|
.env("XDG_RUNTIME_DIR", cli.runtime_dir.as_os_str())
|
||||||
)
|
.kill_on_drop(true),
|
||||||
.await?;
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
let portal_documents =
|
let portal_documents =
|
||||||
WellKnownName::from_static_str("org.freedesktop.portal.Documents")?.into();
|
WellKnownName::from_static_str("org.freedesktop.portal.Documents")?.into();
|
||||||
priv_lst.wait_for_acquisition(portal_documents).await?;
|
priv_lst.wait_for_acquisition(portal_documents).await?;
|
||||||
|
|
||||||
let _vfs = Command::new(env!("BIN_VIRTIOFSD"))
|
child_procs.push(
|
||||||
.args(&[
|
Command::new(env!("BIN_VIRTIOFSD"))
|
||||||
"--shared-dir",
|
.args(&[
|
||||||
cli.runtime_dir.join("doc").to_str().unwrap(),
|
"--shared-dir",
|
||||||
"--socket-path",
|
cli.runtime_dir.join("doc").to_str().unwrap(),
|
||||||
cli.runtime_dir.join("fs.sock").to_str().unwrap(),
|
"--socket-path",
|
||||||
"--uid-map",
|
cli.runtime_dir.join("fs.sock").to_str().unwrap(),
|
||||||
":1000:1001:1:",
|
"--uid-map",
|
||||||
"--gid-map",
|
&format!(":{}:{}:1:", cli.guest_uid, unsafe { libc::getuid() }),
|
||||||
":100:100:1:",
|
"--gid-map",
|
||||||
"--log-level",
|
&format!(":{}:{}:1:", cli.guest_gid, unsafe { libc::getgid() }),
|
||||||
"debug",
|
"--log-level",
|
||||||
])
|
"debug",
|
||||||
.env("XDG_RUNTIME_DIR", cli.runtime_dir.as_os_str())
|
])
|
||||||
.kill_on_drop(true)
|
.env("XDG_RUNTIME_DIR", cli.runtime_dir.as_os_str())
|
||||||
.spawn();
|
.kill_on_drop(true)
|
||||||
// TODO: die when it exits
|
.spawn()?,
|
||||||
|
);
|
||||||
|
|
||||||
let vm_bus_conn = vm_bus.lock().await.connect_channel(false).await?;
|
|
||||||
let priv_bus_conn = priv_bus.lock().await.connect_channel(false).await?;
|
let priv_bus_conn = priv_bus.lock().await.connect_channel(false).await?;
|
||||||
let host_session_conn = zbus::connection::Builder::session()?.build().await?;
|
let host_session_conn = zbus::connection::Builder::session()?.build().await?;
|
||||||
let file_chooser_imp = portal::file_chooser::FileChooser::new(
|
let file_chooser_imp = portal::file_chooser::FileChooser::new(
|
||||||
&host_session_conn,
|
&host_session_conn,
|
||||||
&priv_bus_conn,
|
&priv_bus_conn,
|
||||||
cli.guest_mountpoint,
|
cli.guest_mountpoint.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
vm_bus_conn
|
let file_transfer_imp = portal::file_transfer::FileTransfer::new(
|
||||||
.request_name("org.freedesktop.portal.Desktop")
|
&host_session_conn,
|
||||||
.await?;
|
&priv_bus_conn,
|
||||||
let true = vm_bus_conn
|
cli.guest_mountpoint,
|
||||||
.object_server()
|
path_prefix_to_host,
|
||||||
.at("/org/freedesktop/portal/desktop", file_chooser_imp)
|
)
|
||||||
.await?
|
|
||||||
else {
|
|
||||||
unreachable!("our own fresh bus can't have interfaces already provided");
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: modprobe vhost_vsock first!
|
|
||||||
// NOTE: Every individual D-Bus client inside of the VM is a new client here!
|
|
||||||
vsock::ListenerBuilder::new(vsock::VsockAddr::new(
|
|
||||||
vsock::VMADDR_CID_HOST,
|
|
||||||
cli.vsock_port,
|
|
||||||
))
|
|
||||||
.with_label("VM Bus")
|
|
||||||
.listen(move |client| {
|
|
||||||
enclose! { (vm_bus, vm_bus_guid) async move {
|
|
||||||
// TODO: Not necessary to go through the channel, add vsock support to the Peer too
|
|
||||||
let client_conn = client.build((&vm_bus_guid).into()).await?;
|
|
||||||
let vmbus_conn = vm_bus.lock().await.connect_channel(true).await?;
|
|
||||||
sidebus_common::raw::splice_conns(client_conn, vmbus_conn).await;
|
|
||||||
Ok(())
|
|
||||||
} }
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
let notification_imp = portal::notification::Notification::new(&host_session_conn).await?;
|
||||||
|
let print_imp = portal::print::Print::new(&host_session_conn).await?;
|
||||||
|
let settings_imp = portal::settings::Settings::new(&host_session_conn).await?;
|
||||||
|
|
||||||
|
async fn on_vm_bus_connected(
|
||||||
|
vm_bus_conn: zbus::Connection,
|
||||||
|
file_chooser: portal::file_chooser::FileChooser,
|
||||||
|
file_transfer: portal::file_transfer::FileTransfer,
|
||||||
|
notification: portal::notification::Notification,
|
||||||
|
print: portal::print::Print,
|
||||||
|
settings: portal::settings::Settings,
|
||||||
|
) -> Result<(), eyre::Report> {
|
||||||
|
if !vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.at("/org/freedesktop/portal/desktop", file_chooser)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
error!("org.freedesktop.portal.FileChooser already provided");
|
||||||
|
};
|
||||||
|
|
||||||
|
if !vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.at("/org/freedesktop/portal/documents", file_transfer)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
error!("org.freedesktop.portal.FileTransfer already provided");
|
||||||
|
};
|
||||||
|
let file_transfer_ref = vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.interface::<_, portal::file_transfer::FileTransfer>(
|
||||||
|
"/org/freedesktop/portal/documents",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let file_transfer = file_transfer_ref.get().await;
|
||||||
|
let emitter = file_transfer_ref.signal_emitter();
|
||||||
|
if let Err(err) = file_transfer.forward_transfer_closed(emitter.clone()).await {
|
||||||
|
error!(%err, "forwarding forward_transfer_closed changes ended");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.at("/org/freedesktop/portal/desktop", notification)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
error!("org.freedesktop.portal.Notification already provided");
|
||||||
|
};
|
||||||
|
let notification_ref = vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.interface::<_, portal::notification::Notification>("/org/freedesktop/portal/desktop")
|
||||||
|
.await?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let notification = notification_ref.get().await;
|
||||||
|
let emitter = notification_ref.signal_emitter();
|
||||||
|
if let Err(err) = notification.forward_actions(emitter.clone()).await {
|
||||||
|
error!(%err, "forwarding notification changes ended");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.at("/org/freedesktop/portal/desktop", print)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
error!("org.freedesktop.portal.Print already provided");
|
||||||
|
};
|
||||||
|
|
||||||
|
if !vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.at("/org/freedesktop/portal/desktop", settings)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
error!("org.freedesktop.portal.Settings already provided");
|
||||||
|
};
|
||||||
|
let settings_ref = vm_bus_conn
|
||||||
|
.object_server()
|
||||||
|
.interface::<_, portal::settings::Settings>("/org/freedesktop/portal/desktop")
|
||||||
|
.await?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let settings = settings_ref.get().await;
|
||||||
|
let emitter = settings_ref.signal_emitter();
|
||||||
|
if let Err(err) = settings.forward_changes(emitter).await {
|
||||||
|
error!(%err, "forwarding settings changes ended");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// XXX: no method for "wait until the conn dies"?
|
||||||
|
Ok(std::future::pending::<()>().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = cli.unix_path {
|
||||||
|
let vm_unix_listener = UnixListener::bind(path)?;
|
||||||
|
server_tasks.spawn(enclose!((file_chooser_imp, file_transfer_imp, notification_imp, print_imp, settings_imp) async move {
|
||||||
|
while let Ok((socket, remote_addr)) = vm_unix_listener.accept().await {
|
||||||
|
let f = enclose!((file_chooser_imp, file_transfer_imp, notification_imp, print_imp, settings_imp) async move {
|
||||||
|
let client_conn = if cli.guest_bus_anonymous_auth {
|
||||||
|
zbus::connection::Builder::unix_stream(socket).auth_mechanism(zbus::AuthMechanism::Anonymous)
|
||||||
|
} else {
|
||||||
|
zbus::connection::Builder::unix_stream(socket).user_id(cli.guest_uid)
|
||||||
|
}
|
||||||
|
.name("org.freedesktop.portal.Desktop")?
|
||||||
|
.name("org.freedesktop.portal.Documents")?
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
on_vm_bus_connected(client_conn, file_chooser_imp, file_transfer_imp, notification_imp, print_imp, settings_imp).await
|
||||||
|
});
|
||||||
|
tokio::spawn(
|
||||||
|
async {
|
||||||
|
match f.await {
|
||||||
|
Ok(()) => debug!("done with server"),
|
||||||
|
Err(err) => error!(%err, "error dealing with server"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.instrument(info_span!("serve", ?remote_addr)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(port) = cli.vsock_port {
|
||||||
|
// TODO: modprobe vhost_vsock first!
|
||||||
|
server_tasks.spawn(
|
||||||
|
vsock::ListenerBuilder::new(vsock::VsockAddr::new(vsock::VMADDR_CID_HOST, port))
|
||||||
|
.with_label("VM Bus")
|
||||||
|
.listen(move |client| {
|
||||||
|
enclose!((file_chooser_imp, file_transfer_imp, notification_imp, print_imp, settings_imp) async move {
|
||||||
|
// TODO: Not necessary to go through the channel, add vsock support to the Peer too?
|
||||||
|
let client_conn = client.build().await?;
|
||||||
|
on_vm_bus_connected(client_conn, file_chooser_imp, file_transfer_imp, notification_imp, print_imp, settings_imp).await
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map_ok_or_else(
|
||||||
|
|e| {
|
||||||
|
error!("vsock listener: {:?}", e);
|
||||||
|
},
|
||||||
|
|()| (),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut waiter = child_procs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|child| child.wait())
|
||||||
|
.collect::<FuturesUnordered<_>>();
|
||||||
|
tokio::select! {
|
||||||
|
_ = server_tasks.join_all() => debug!("server tasks ended"),
|
||||||
|
res = waiter.next() => debug!(?res, "child process terminated"),
|
||||||
|
_ = tokio::signal::ctrl_c() => debug!("interrupt signal"),
|
||||||
|
};
|
||||||
|
drop(waiter);
|
||||||
|
for mut child in child_procs {
|
||||||
|
if let Err(e) = child.kill().await {
|
||||||
|
error!(?e, "could not kill process");
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use zbus::{Connection, ObjectServer, fdo::Result, zvariant};
|
||||||
use super::documents::DocumentsProxy;
|
use super::documents::DocumentsProxy;
|
||||||
use super::request::{RESPONSE_SUCCESS, ReqHandler, ResultTransformer};
|
use super::request::{RESPONSE_SUCCESS, ReqHandler, ResultTransformer};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct FileChooser {
|
pub struct FileChooser {
|
||||||
host: FileChooserProxy<'static>,
|
host: FileChooserProxy<'static>,
|
||||||
docs: DocumentsProxy<'static>,
|
docs: DocumentsProxy<'static>,
|
||||||
|
|
@ -52,6 +53,7 @@ impl FileChooser {
|
||||||
docs: self.docs.clone(),
|
docs: self.docs.clone(),
|
||||||
guest_root: self.guest_root.clone(),
|
guest_root: self.guest_root.clone(),
|
||||||
for_save: false,
|
for_save: false,
|
||||||
|
persistent: true,
|
||||||
directory: options.get_as("directory")?.unwrap_or(false),
|
directory: options.get_as("directory")?.unwrap_or(false),
|
||||||
})
|
})
|
||||||
.perform(async || self.host.open_file(parent_window, title, options).await)
|
.perform(async || self.host.open_file(parent_window, title, options).await)
|
||||||
|
|
@ -72,6 +74,7 @@ impl FileChooser {
|
||||||
docs: self.docs.clone(),
|
docs: self.docs.clone(),
|
||||||
guest_root: self.guest_root.clone(),
|
guest_root: self.guest_root.clone(),
|
||||||
for_save: true,
|
for_save: true,
|
||||||
|
persistent: true,
|
||||||
directory: false,
|
directory: false,
|
||||||
})
|
})
|
||||||
.perform(async || self.host.save_file(parent_window, title, options).await)
|
.perform(async || self.host.save_file(parent_window, title, options).await)
|
||||||
|
|
@ -92,6 +95,7 @@ impl FileChooser {
|
||||||
docs: self.docs.clone(),
|
docs: self.docs.clone(),
|
||||||
guest_root: self.guest_root.clone(),
|
guest_root: self.guest_root.clone(),
|
||||||
for_save: true,
|
for_save: true,
|
||||||
|
persistent: true,
|
||||||
directory: false,
|
directory: false,
|
||||||
})
|
})
|
||||||
.perform(async || self.host.save_files(parent_window, title, options).await)
|
.perform(async || self.host.save_files(parent_window, title, options).await)
|
||||||
|
|
@ -105,11 +109,13 @@ impl FileChooser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileTransformer {
|
#[derive(Clone)]
|
||||||
docs: DocumentsProxy<'static>,
|
pub struct FileTransformer {
|
||||||
guest_root: PathBuf,
|
pub docs: DocumentsProxy<'static>,
|
||||||
for_save: bool,
|
pub guest_root: PathBuf,
|
||||||
directory: bool,
|
pub for_save: bool,
|
||||||
|
pub persistent: bool,
|
||||||
|
pub directory: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ref: send_response_in_thread_func
|
// ref: send_response_in_thread_func
|
||||||
|
|
@ -133,6 +139,14 @@ impl ResultTransformer for FileTransformer {
|
||||||
.async_map(|u| self.add_path_as_doc(u))
|
.async_map(|u| self.add_path_as_doc(u))
|
||||||
.await
|
.await
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.map(|path| match url::Url::from_file_path(&path) {
|
||||||
|
Ok(url) => Some(url.to_string()),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, ?path, "could not make url from returned path");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
results.insert("uris", guest_uris.into());
|
results.insert("uris", guest_uris.into());
|
||||||
|
|
@ -149,7 +163,7 @@ const DIRECTORY: u32 = 1 << 3;
|
||||||
// https://github.com/flatpak/xdg-desktop-portal/blob/10e712e06aa8eb9cd0e59c73c5be62ba53e981a4/src/xdp-documents.c#L71
|
// https://github.com/flatpak/xdg-desktop-portal/blob/10e712e06aa8eb9cd0e59c73c5be62ba53e981a4/src/xdp-documents.c#L71
|
||||||
|
|
||||||
impl FileTransformer {
|
impl FileTransformer {
|
||||||
async fn add_path_as_doc(&self, path: PathBuf) -> Option<String> {
|
pub async fn add_path_as_doc(&self, path: PathBuf) -> Option<PathBuf> {
|
||||||
use rustix::fs::{Mode, OFlags};
|
use rustix::fs::{Mode, OFlags};
|
||||||
|
|
||||||
let o_path_fd = match rustix::fs::open(
|
let o_path_fd = match rustix::fs::open(
|
||||||
|
|
@ -165,8 +179,8 @@ impl FileTransformer {
|
||||||
};
|
};
|
||||||
|
|
||||||
let flags = REUSE_EXISTING
|
let flags = REUSE_EXISTING
|
||||||
| PERSISTENT
|
|
||||||
| AS_NEEDED_BY_APP
|
| AS_NEEDED_BY_APP
|
||||||
|
| if self.persistent { PERSISTENT } else { 0 }
|
||||||
| if self.directory { DIRECTORY } else { 0 };
|
| if self.directory { DIRECTORY } else { 0 };
|
||||||
|
|
||||||
// XXX: portal impl can return writable=false but host frontend does not pass that back..
|
// XXX: portal impl can return writable=false but host frontend does not pass that back..
|
||||||
|
|
@ -211,14 +225,7 @@ impl FileTransformer {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let path = self.guest_root.join(doc_id).join(filename);
|
Some(self.guest_root.join(doc_id).join(filename))
|
||||||
match url::Url::from_file_path(&path) {
|
|
||||||
Ok(url) => Some(url.to_string()),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, ?path, "could not make url from returned path");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
205
sidebus-broker/src/portal/file_transfer.rs
Normal file
205
sidebus-broker/src/portal/file_transfer.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
os::fd::{AsFd, AsRawFd},
|
||||||
|
os::unix::ffi::OsStrExt,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tracing::{debug, error};
|
||||||
|
use zbus::{
|
||||||
|
Connection, fdo::Result, names::OwnedUniqueName, object_server::SignalEmitter, zvariant,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{documents::DocumentsProxy, file_chooser::FileTransformer};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FileTransfer {
|
||||||
|
host: FileTransferProxy<'static>,
|
||||||
|
file_transformer: FileTransformer,
|
||||||
|
tx: broadcast::Sender<ForwarderCommand>,
|
||||||
|
path_prefix_to_host: Vec<(PathBuf, PathBuf)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ForwarderCommand {
|
||||||
|
Add(String, OwnedUniqueName),
|
||||||
|
// Remove(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus::interface(
|
||||||
|
name = "org.freedesktop.portal.FileTransfer",
|
||||||
|
proxy(
|
||||||
|
default_service = "org.freedesktop.portal.Documents",
|
||||||
|
default_path = "/org/freedesktop/portal/documents"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
impl FileTransfer {
|
||||||
|
async fn add_files(
|
||||||
|
&self,
|
||||||
|
key: &str,
|
||||||
|
fds: Vec<zbus::zvariant::Fd<'_>>,
|
||||||
|
options: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut host_paths = Vec::with_capacity(fds.len());
|
||||||
|
for fd in fds.iter() {
|
||||||
|
let link = rustix::fs::readlink(
|
||||||
|
format!("/proc/self/fd/{}", fd.as_fd().as_raw_fd()),
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||||
|
let guest_path = std::path::PathBuf::from(std::ffi::OsStr::from_bytes(
|
||||||
|
&link.to_string_lossy().as_bytes(),
|
||||||
|
));
|
||||||
|
let (prefix, host_prefix) = self
|
||||||
|
.path_prefix_to_host
|
||||||
|
.iter()
|
||||||
|
.find(|(prefix, _)| guest_path.starts_with(prefix))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
zbus::fdo::Error::Failed("Could not find host mapping for path".to_owned())
|
||||||
|
})?;
|
||||||
|
let guest_suffix = guest_path
|
||||||
|
.strip_prefix(prefix)
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||||
|
let host_path = if guest_suffix.as_os_str().is_empty() {
|
||||||
|
// Edge case: a bind-mounted file exposed at the same path would get an extra '/' after its path
|
||||||
|
host_prefix.to_path_buf()
|
||||||
|
} else {
|
||||||
|
host_prefix.join(guest_suffix)
|
||||||
|
};
|
||||||
|
debug!(
|
||||||
|
?guest_path,
|
||||||
|
?prefix,
|
||||||
|
?guest_suffix,
|
||||||
|
?host_prefix,
|
||||||
|
?host_path,
|
||||||
|
"mapped path"
|
||||||
|
);
|
||||||
|
let path_fd = rustix::fs::open(
|
||||||
|
host_path,
|
||||||
|
rustix::fs::OFlags::PATH,
|
||||||
|
rustix::fs::Mode::empty(),
|
||||||
|
)
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||||
|
host_paths.push(path_fd.into()); // OwnedFd variant of zbus's Fd enum, so still owned by the Vec
|
||||||
|
}
|
||||||
|
self.host.add_files(key, host_paths, options).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn retrieve_files(
|
||||||
|
&self,
|
||||||
|
key: &str,
|
||||||
|
options: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
let host_paths = self.host.retrieve_files(key, options).await?;
|
||||||
|
let mut result = Vec::with_capacity(host_paths.len());
|
||||||
|
for host_path in host_paths {
|
||||||
|
if let Some(guest_path) = self
|
||||||
|
.file_transformer
|
||||||
|
.add_path_as_doc(PathBuf::from(&host_path))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
result.push(guest_path.to_string_lossy().into_owned());
|
||||||
|
} else {
|
||||||
|
debug!(%host_path, "could not add path as doc to retrieve file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_transfer(
|
||||||
|
&self,
|
||||||
|
#[zbus(header)] hdr: zbus::message::Header<'_>,
|
||||||
|
options: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
) -> Result<String> {
|
||||||
|
let sender = hdr
|
||||||
|
.sender()
|
||||||
|
.ok_or_else(|| zbus::Error::MissingField)?
|
||||||
|
.to_owned();
|
||||||
|
let key = self.host.start_transfer(options).await?;
|
||||||
|
debug!(%key, %sender, "started transfer");
|
||||||
|
if let Err(err) = self
|
||||||
|
.tx
|
||||||
|
.send(ForwarderCommand::Add(key.clone(), sender.into()))
|
||||||
|
{
|
||||||
|
error!(?err, "file_transfer internal channel error");
|
||||||
|
return Err(zbus::fdo::Error::IOError("channel error".to_owned()));
|
||||||
|
}
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop_transfer(&self, key: &str) -> Result<()> {
|
||||||
|
debug!(%key, "stopping transfer");
|
||||||
|
self.host.stop_transfer(key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn transfer_closed(signal_emitter: &SignalEmitter<'_>, key: &str) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
#[zbus(property, name = "version")]
|
||||||
|
fn version(&self) -> Result<u32> {
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileTransfer {
|
||||||
|
pub async fn new(
|
||||||
|
host_session_conn: &Connection,
|
||||||
|
priv_conn: &Connection,
|
||||||
|
guest_root: PathBuf,
|
||||||
|
path_prefix_to_host: Vec<(PathBuf, PathBuf)>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let host = FileTransferProxy::builder(host_session_conn)
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
let docs = DocumentsProxy::builder(priv_conn).build().await?;
|
||||||
|
let file_transformer = FileTransformer {
|
||||||
|
docs,
|
||||||
|
guest_root,
|
||||||
|
for_save: false,
|
||||||
|
persistent: false,
|
||||||
|
directory: false,
|
||||||
|
};
|
||||||
|
let (tx, _) = broadcast::channel(8);
|
||||||
|
Ok(FileTransfer {
|
||||||
|
host,
|
||||||
|
file_transformer,
|
||||||
|
tx,
|
||||||
|
path_prefix_to_host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_transfer_closed(
|
||||||
|
&self,
|
||||||
|
mut signal_emitter: SignalEmitter<'static>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut stream = self.host.receive_transfer_closed().await?;
|
||||||
|
let mut cmds = self.tx.subscribe();
|
||||||
|
let mut receivers = HashMap::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Ok(cmd) = cmds.recv() => match cmd {
|
||||||
|
ForwarderCommand::Add(key, receiver) => { receivers.insert(key, receiver); },
|
||||||
|
// ForwarderCommand::Remove(key) => { receivers.remove(&key); },
|
||||||
|
},
|
||||||
|
Some(signal) = stream.next() => {
|
||||||
|
debug!(?signal, "transfer closed");
|
||||||
|
if let Ok((key,)) = signal.0.deserialize::<(&str,)>() {
|
||||||
|
if let Some(bus_name) = receivers.remove(key) {
|
||||||
|
signal_emitter = signal_emitter.set_destination(zbus::names::BusName::Unique(bus_name.clone().into()));
|
||||||
|
if let Err(err) = FileTransfer::transfer_closed(&signal_emitter, key).await {
|
||||||
|
error!(?err, %key, "could not forward signal");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!(%key, "got a signal for unknown key");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("could not deserialize transfer closed signal");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
pub mod documents;
|
pub mod documents;
|
||||||
pub mod file_chooser;
|
pub mod file_chooser;
|
||||||
|
pub mod file_transfer;
|
||||||
|
pub mod notification;
|
||||||
|
pub mod print;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
|
pub mod settings;
|
||||||
|
|
|
||||||
95
sidebus-broker/src/portal/notification.rs
Normal file
95
sidebus-broker/src/portal/notification.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tracing::warn;
|
||||||
|
use zbus::{Connection, fdo::Result, names::UniqueName, object_server::SignalEmitter, zvariant};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Notification {
|
||||||
|
host: NotificationProxy<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus::interface(
|
||||||
|
name = "org.freedesktop.portal.Notification",
|
||||||
|
proxy(
|
||||||
|
default_service = "org.freedesktop.portal.Desktop",
|
||||||
|
default_path = "/org/freedesktop/portal/desktop"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
impl Notification {
|
||||||
|
async fn add_notification(
|
||||||
|
&self,
|
||||||
|
#[zbus(header)] hdr: zbus::message::Header<'_>,
|
||||||
|
id: &str,
|
||||||
|
notification: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let sender = hdr.sender().ok_or_else(|| zbus::Error::MissingField)?;
|
||||||
|
self.host
|
||||||
|
.add_notification(
|
||||||
|
&format!("{sender}\x0C\x0CSIDEBUS\x0C\x0C{id}"),
|
||||||
|
notification,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_notification(
|
||||||
|
&self,
|
||||||
|
#[zbus(header)] hdr: zbus::message::Header<'_>,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let sender = hdr.sender().ok_or_else(|| zbus::Error::MissingField)?;
|
||||||
|
self.host
|
||||||
|
.remove_notification(&format!("{sender}\x0C\x0CSIDEBUS\x0C\x0C{id}"))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn action_invoked(
|
||||||
|
signal_emitter: &SignalEmitter<'_>,
|
||||||
|
id: &str,
|
||||||
|
action: &str,
|
||||||
|
parameter: Vec<zvariant::Value<'_>>,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn supported_options(&self) -> Result<HashMap<String, zvariant::OwnedValue>> {
|
||||||
|
self.host
|
||||||
|
.supported_options()
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "version")]
|
||||||
|
fn version(&self) -> Result<u32> {
|
||||||
|
Ok(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Notification {
|
||||||
|
pub async fn new(host_session_conn: &Connection) -> Result<Self> {
|
||||||
|
let host = NotificationProxy::builder(host_session_conn)
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
Ok(Self { host })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_actions(&self, mut signal_emitter: SignalEmitter<'static>) -> Result<()> {
|
||||||
|
let mut stream = self.host.receive_action_invoked().await?;
|
||||||
|
while let Some(x) = stream.next().await {
|
||||||
|
let args = x.args()?;
|
||||||
|
let mut split = args.id.split("\x0C\x0CSIDEBUS\x0C\x0C");
|
||||||
|
let sender = split
|
||||||
|
.next()
|
||||||
|
.and_then(|x| UniqueName::try_from(x).ok().map(|x| x.to_owned()))
|
||||||
|
.ok_or_else(|| zbus::fdo::Error::Failed("bad ID".to_owned()))?;
|
||||||
|
let id = split
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| zbus::fdo::Error::Failed("bad ID".to_owned()))?;
|
||||||
|
signal_emitter = signal_emitter.set_destination(sender.into());
|
||||||
|
Notification::action_invoked(&signal_emitter, id, args.action, args.parameter).await?;
|
||||||
|
()
|
||||||
|
}
|
||||||
|
warn!("actions stream end");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
66
sidebus-broker/src/portal/print.rs
Normal file
66
sidebus-broker/src/portal/print.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use zbus::{Connection, ObjectServer, fdo::Result, zvariant};
|
||||||
|
|
||||||
|
use super::request::ReqHandler;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Print {
|
||||||
|
host: PrintProxy<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus::interface(
|
||||||
|
name = "org.freedesktop.portal.Print",
|
||||||
|
proxy(
|
||||||
|
default_service = "org.freedesktop.portal.Desktop",
|
||||||
|
default_path = "/org/freedesktop/portal/desktop"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
impl Print {
|
||||||
|
async fn prepare_print(
|
||||||
|
&self,
|
||||||
|
#[zbus(header)] hdr: zbus::message::Header<'_>,
|
||||||
|
#[zbus(object_server)] server: &ObjectServer,
|
||||||
|
#[zbus(connection)] conn: &Connection,
|
||||||
|
parent_window: &str,
|
||||||
|
title: &str,
|
||||||
|
settings: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
page_setup: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
options: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
) -> Result<zvariant::OwnedObjectPath> {
|
||||||
|
ReqHandler::prepare(&self.host, hdr, server, conn, &options)
|
||||||
|
.perform(async || {
|
||||||
|
self.host
|
||||||
|
.prepare_print(parent_window, title, settings, page_setup, options)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn print(
|
||||||
|
&self,
|
||||||
|
#[zbus(header)] hdr: zbus::message::Header<'_>,
|
||||||
|
#[zbus(object_server)] server: &ObjectServer,
|
||||||
|
#[zbus(connection)] conn: &Connection,
|
||||||
|
parent_window: &str,
|
||||||
|
title: &str,
|
||||||
|
fd: zvariant::Fd<'_>,
|
||||||
|
options: HashMap<&str, zvariant::Value<'_>>,
|
||||||
|
) -> Result<zvariant::OwnedObjectPath> {
|
||||||
|
ReqHandler::prepare(&self.host, hdr, server, conn, &options)
|
||||||
|
.perform(async || self.host.print(parent_window, title, fd, options).await)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "version")]
|
||||||
|
fn version(&self) -> Result<u32> {
|
||||||
|
Ok(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Print {
|
||||||
|
pub async fn new(host_session_conn: &Connection) -> Result<Self> {
|
||||||
|
let host = PrintProxy::builder(host_session_conn).build().await?;
|
||||||
|
Ok(Self { host })
|
||||||
|
}
|
||||||
|
}
|
||||||
66
sidebus-broker/src/portal/settings.rs
Normal file
66
sidebus-broker/src/portal/settings.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tracing::warn;
|
||||||
|
use zbus::{Connection, fdo::Result, object_server::SignalEmitter, zvariant};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
host: SettingsProxy<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus::interface(
|
||||||
|
name = "org.freedesktop.portal.Settings",
|
||||||
|
proxy(
|
||||||
|
default_service = "org.freedesktop.portal.Desktop",
|
||||||
|
default_path = "/org/freedesktop/portal/desktop"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
impl Settings {
|
||||||
|
async fn read(&self, namespace: &str, key: &str) -> Result<zvariant::OwnedValue> {
|
||||||
|
self.host.read(namespace, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_all(
|
||||||
|
&self,
|
||||||
|
namespaces: Vec<&str>,
|
||||||
|
) -> Result<HashMap<String, HashMap<String, zvariant::OwnedValue>>> {
|
||||||
|
self.host.read_all(namespaces).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_one(&self, namespace: &str, key: &str) -> Result<zvariant::OwnedValue> {
|
||||||
|
self.host.read_one(namespace, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SettingChanged signal
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn setting_changed(
|
||||||
|
signal_emitter: &SignalEmitter<'_>,
|
||||||
|
namespace: &str,
|
||||||
|
key: &str,
|
||||||
|
value: zvariant::Value<'_>,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
#[zbus(property, name = "version")]
|
||||||
|
async fn version(&self) -> Result<u32> {
|
||||||
|
Ok(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub(crate) async fn new(host_session_conn: &Connection) -> Result<Self> {
|
||||||
|
let host = SettingsProxy::builder(host_session_conn).build().await?;
|
||||||
|
Ok(Self { host })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_changes(&self, signal_emitter: &SignalEmitter<'static>) -> Result<()> {
|
||||||
|
let mut stream = self.host.receive_setting_changed().await?;
|
||||||
|
while let Some(x) = stream.next().await {
|
||||||
|
let args = x.args()?;
|
||||||
|
Settings::setting_changed(signal_emitter, args.namespace, args.key, args.value).await?;
|
||||||
|
()
|
||||||
|
}
|
||||||
|
warn!("settings change stream end");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,12 +13,11 @@ impl ConnectionBuilder {
|
||||||
&self.remote_addr
|
&self.remote_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build<'a>(self, guid: zbus::Guid<'a>) -> eyre::Result<zbus::Connection> {
|
pub async fn build<'a>(self) -> eyre::Result<zbus::Connection> {
|
||||||
zbus::connection::Builder::vsock_stream(self.socket)
|
zbus::connection::Builder::vsock_stream(self.socket)
|
||||||
.server(guid)
|
|
||||||
.unwrap()
|
|
||||||
.p2p()
|
|
||||||
.auth_mechanism(zbus::AuthMechanism::Anonymous)
|
.auth_mechanism(zbus::AuthMechanism::Anonymous)
|
||||||
|
.name("org.freedesktop.portal.Desktop")?
|
||||||
|
.name("org.freedesktop.portal.Documents")?
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.into())
|
.map_err(|e| e.into())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue