In Part 5 of our engineering series, we introduced the high-level features of Linxr v3.0.0. Today in Part 6, we take a deep technical dive into how we solved host-to-guest file sharing on modern Android using Virtio-9P and Android’s Storage Access Framework (SAF).
The Challenge: Android Scoped Storage vs Linux Filesystems
Standard Linux virtual machines expect direct POSIX file path access (/home/root/files, /mnt/storage). However, Android 10+ enforces strict Scoped Storage rules and granular URI permissions.
Passing raw host paths to QEMU without root permissions often fails due to Android app sandbox boundaries. We needed a dual-layer solution:
- Direct high-speed POSIX passthrough for app-managed storage.
- User-configurable folder selection via Android Storage Access Framework (SAF).
Layer 1: Virtio-9P Driver in QEMU
QEMU provides the 9P network filesystem protocol (virtio-9p-pci), which exposes host directory trees to Linux guests with low CPU overhead.
We configured QEMU command-line parameters in VmManager.kt:
val virtioArgs = listOf(
"-fsdev", "local,id=fsdev0,path=$sharePath,security_model=none",
"-device", "virtio-9p-pci,fsdev=fsdev0,mount_tag=LinxrShare"
)
Inside Alpine Linux, an OpenRC startup service automounts this tag on boot:
mount -t 9p -o trans=virtio,version=9p2000.L LinxrShare /mnt/sdcard
Any file saved in /storage/emulated/0/LinxrShare on Android instantly appears inside Alpine Linux under /mnt/sdcard!
Layer 2: Storage Access Framework (SAF) & pickFolder Channel
To allow users to select custom folders (such as Downloads, Documents, or external SD cards), we implemented a native Kotlin MethodChannel handler (pickFolder):
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.ai2th.linxr/vm")
.setMethodCallHandler { call, result ->
if (call.method == "pickFolder") {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
activity.startActivityForResult(intent, REQUEST_CODE_SAF_PICK_FOLDER)
}
}
When a user selects a folder, Android returns a persistent DocumentFile URI. We resolve the underlying POSIX path and pass it to QEMU's Virtio-9P filesystem driver.
What’s Next?
In Part 7, we will explore how we embedded the Docker Engine and built a real-time container management dashboard in Flutter!
👉 Official Website: https://ai2th.github.io/linxr.html
👉 Google Group: https://groups.google.com/g/ai2th
Top comments (0)