virtiofs: Add AVF API to share directory paths
This patch adds the API to share directory path between host
and guest.
The path to be shared is taken as an input from vm_config.json;
```
"sharedPath": [
{
"sharedPath": "/data/data/com.google.android.virtualization.terminal/files"
}
],
```
The UID-GID mapping between host and guest is constructed on the fly
based on the active user.
Currently, terminal app's internal storage is set as default; but this
can be changed as required.
This is now disabled by default - the feature will be enabled once
all the dependencies related to crosvm lands.
Bug: 372171883
Test: Share terminal app internal storage to guest VM. Read "debian.log"
files inside the guest. Verify UID-GID mappings are set correctly to the
files.
Change-Id: I9f2e7d9167a00c8383523bbd238a48b06afa4c2e
Signed-off-by: Akilesh Kailash <akailash@google.com>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 52bfd87..5dac07f 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -17,7 +17,7 @@
use crate::{get_calling_pid, get_calling_uid, get_this_pid};
use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
-use crate::crosvm::{AudioConfig, CrosvmConfig, DiskFile, DisplayConfig, GpuConfig, InputDeviceOption, PayloadState, UsbConfig, VmContext, VmInstance, VmState};
+use crate::crosvm::{AudioConfig, CrosvmConfig, DiskFile, SharedPathConfig, DisplayConfig, GpuConfig, InputDeviceOption, PayloadState, UsbConfig, VmContext, VmInstance, VmState};
use crate::debug_config::DebugConfig;
use crate::dt_overlay::{create_device_tree_overlay, VM_DT_OVERLAY_MAX_SIZE, VM_DT_OVERLAY_PATH};
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
@@ -32,6 +32,7 @@
AssignableDevice::AssignableDevice,
CpuTopology::CpuTopology,
DiskImage::DiskImage,
+ SharedPath::SharedPath,
InputDevice::InputDevice,
IVirtualMachine::{self, BnVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
@@ -613,6 +614,8 @@
})
.collect::<Result<Vec<DiskFile>, _>>()?;
+ let shared_paths = assemble_shared_paths(&config.sharedPaths, &temporary_directory)?;
+
let (cpus, host_cpu_topology) = match config.cpuTopology {
CpuTopology::MATCH_HOST => (None, true),
CpuTopology::ONE_CPU => (NonZeroU32::new(1), false),
@@ -719,6 +722,7 @@
kernel,
initrd,
disks,
+ shared_paths,
params: config.params.to_owned(),
protected: *is_protected,
debug_config,
@@ -956,6 +960,32 @@
},
})
}
+
+fn assemble_shared_paths(
+ shared_paths: &[SharedPath],
+ temporary_directory: &Path,
+) -> Result<Vec<SharedPathConfig>, Status> {
+ if shared_paths.is_empty() {
+ return Ok(Vec::new()); // Return an empty vector if shared_paths is empty
+ }
+
+ shared_paths
+ .iter()
+ .map(|path| {
+ Ok(SharedPathConfig {
+ path: path.sharedPath.clone(),
+ host_uid: path.hostUid,
+ host_gid: path.hostGid,
+ guest_uid: path.guestUid,
+ guest_gid: path.guestGid,
+ mask: path.mask,
+ tag: path.tag.clone(),
+ socket_path: temporary_directory.join(&path.socket).to_string_lossy().to_string(),
+ })
+ })
+ .collect()
+}
+
/// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
///
/// This may involve assembling a composite disk from a set of partition images.
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index 25271f8..fcc5d3b 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -108,6 +108,7 @@
pub kernel: Option<File>,
pub initrd: Option<File>,
pub disks: Vec<DiskFile>,
+ pub shared_paths: Vec<SharedPathConfig>,
pub params: Option<String>,
pub protected: bool,
pub debug_config: DebugConfig,
@@ -224,6 +225,19 @@
pub writable: bool,
}
+/// Shared path between host and guest VM.
+#[derive(Debug)]
+pub struct SharedPathConfig {
+ pub path: String,
+ pub host_uid: i32,
+ pub host_gid: i32,
+ pub guest_uid: i32,
+ pub guest_gid: i32,
+ pub mask: i32,
+ pub tag: String,
+ pub socket_path: String,
+}
+
/// virtio-input device configuration from `external/crosvm/src/crosvm/config.rs`
#[derive(Debug)]
#[allow(dead_code)]
@@ -306,6 +320,8 @@
let tap =
if let Some(tap_file) = &config.tap { Some(tap_file.try_clone()?) } else { None };
+ run_virtiofs(&config)?;
+
// If this fails and returns an error, `self` will be left in the `Failed` state.
let child =
Arc::new(run_vm(config, &instance.crosvm_control_socket_path, failure_pipe_write)?);
@@ -884,6 +900,39 @@
}
}
+fn run_virtiofs(config: &CrosvmConfig) -> io::Result<()> {
+ for shared_path in &config.shared_paths {
+ let ugid_map_value = format!(
+ "{} {} {} {} {} /",
+ shared_path.guest_uid,
+ shared_path.guest_gid,
+ shared_path.host_uid,
+ shared_path.host_gid,
+ shared_path.mask,
+ );
+
+ let cfg_arg = format!("writeback=true,cache_policy=always,ugid_map='{}'", ugid_map_value);
+
+ let mut command = Command::new(CROSVM_PATH);
+ command
+ .arg("device")
+ .arg("fs")
+ .arg(format!("--socket={}", &shared_path.socket_path))
+ .arg(format!("--tag={}", &shared_path.tag))
+ .arg(format!("--shared-dir={}", &shared_path.path))
+ .arg("--cfg")
+ .arg(cfg_arg.as_str())
+ .arg("--disable-sandbox");
+
+ print_crosvm_args(&command);
+
+ let result = SharedChild::spawn(&mut command)?;
+ info!("Spawned virtiofs crosvm({})", result.id());
+ }
+
+ Ok(())
+}
+
/// Starts an instance of `crosvm` to manage a new VM.
fn run_vm(
config: CrosvmConfig,
@@ -1187,6 +1236,12 @@
command.arg(vfio_argument_for_platform_device(&device)?);
}
+ for shared_path in &config.shared_paths {
+ command
+ .arg("--vhost-user-fs")
+ .arg(format!("{},tag={}", &shared_path.socket_path, &shared_path.tag));
+ }
+
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl
new file mode 100644
index 0000000..7be7a5f
--- /dev/null
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualizationservice;
+
+/** Shared directory path between host and guest */
+parcelable SharedPath {
+ /** Shared path between host and guest */
+ String sharedPath;
+
+ /** UID of the path on the host */
+ int hostUid;
+
+ /** GID of the path on the host */
+ int hostGid;
+
+ /** UID of the path on the guest */
+ int guestUid;
+
+ /** GID of the path on the guest */
+ int guestGid;
+
+ /** umask settings for the path */
+ int mask;
+
+ /** virtiofs unique tag per path */
+ String tag;
+
+ /** socket name for vhost-user-fs */
+ String socket;
+}
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index f559a71..9f2a23e 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -21,6 +21,7 @@
import android.system.virtualizationservice.DisplayConfig;
import android.system.virtualizationservice.GpuConfig;
import android.system.virtualizationservice.InputDevice;
+import android.system.virtualizationservice.SharedPath;
import android.system.virtualizationservice.UsbConfig;
/** Raw configuration for running a VM. */
@@ -52,6 +53,9 @@
/** Disk images to be made available to the VM. */
DiskImage[] disks;
+ /** Shared paths between host and guest */
+ SharedPath[] sharedPaths;
+
/** Whether the VM should be a protected VM. */
boolean protectedVm;
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
index de1b081..2bcb40b 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -40,6 +40,7 @@
import android.sysprop.HypervisorProperties;
import android.system.virtualizationservice.DiskImage;
import android.system.virtualizationservice.Partition;
+import android.system.virtualizationservice.SharedPath;
import android.system.virtualizationservice.UsbConfig;
import android.system.virtualizationservice.VirtualMachineAppConfig;
import android.system.virtualizationservice.VirtualMachinePayloadConfig;
@@ -712,6 +713,15 @@
config.disks[i].partitions = partitions.toArray(new Partition[0]);
}
+ config.sharedPaths =
+ new SharedPath
+ [Optional.ofNullable(customImageConfig.getSharedPaths())
+ .map(arr -> arr.length)
+ .orElse(0)];
+ for (int i = 0; i < config.sharedPaths.length; i++) {
+ config.sharedPaths[i] = customImageConfig.getSharedPaths()[i].toParcelable();
+ }
+
config.displayConfig =
Optional.ofNullable(customImageConfig.getDisplayConfig())
.map(dc -> dc.toParcelable())
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 9774585..9b0709d 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -54,6 +54,7 @@
@Nullable private final String bootloaderPath;
@Nullable private final String[] params;
@Nullable private final Disk[] disks;
+ @Nullable private final SharedPath[] sharedPaths;
@Nullable private final DisplayConfig displayConfig;
@Nullable private final AudioConfig audioConfig;
private final boolean touch;
@@ -96,6 +97,11 @@
return params;
}
+ @Nullable
+ public SharedPath[] getSharedPaths() {
+ return sharedPaths;
+ }
+
public boolean useTouch() {
return touch;
}
@@ -132,6 +138,7 @@
String bootloaderPath,
String[] params,
Disk[] disks,
+ SharedPath[] sharedPaths,
DisplayConfig displayConfig,
boolean touch,
boolean keyboard,
@@ -149,6 +156,7 @@
this.bootloaderPath = bootloaderPath;
this.params = params;
this.disks = disks;
+ this.sharedPaths = sharedPaths;
this.displayConfig = displayConfig;
this.touch = touch;
this.keyboard = keyboard;
@@ -300,6 +308,91 @@
}
/** @hide */
+ public static final class SharedPath {
+ private final String path;
+ private final int hostUid;
+ private final int hostGid;
+ private final int guestUid;
+ private final int guestGid;
+ private final int mask;
+ private final String tag;
+ private final String socket;
+
+ public SharedPath(
+ String path,
+ int hostUid,
+ int hostGid,
+ int guestUid,
+ int guestGid,
+ int mask,
+ String tag,
+ String socket) {
+ this.path = path;
+ this.hostUid = hostUid;
+ this.hostGid = hostGid;
+ this.guestUid = guestUid;
+ this.guestGid = guestGid;
+ this.mask = mask;
+ this.tag = tag;
+ this.socket = socket;
+ }
+
+ android.system.virtualizationservice.SharedPath toParcelable() {
+ android.system.virtualizationservice.SharedPath parcelable =
+ new android.system.virtualizationservice.SharedPath();
+ parcelable.sharedPath = this.path;
+ parcelable.hostUid = this.hostUid;
+ parcelable.hostGid = this.hostGid;
+ parcelable.guestUid = this.guestUid;
+ parcelable.guestGid = this.guestGid;
+ parcelable.mask = this.mask;
+ parcelable.tag = this.tag;
+ parcelable.socket = this.socket;
+ return parcelable;
+ }
+
+ /** @hide */
+ public String getSharedPath() {
+ return path;
+ }
+
+ /** @hide */
+ public int getHostUid() {
+ return hostUid;
+ }
+
+ /** @hide */
+ public int getHostGid() {
+ return hostGid;
+ }
+
+ /** @hide */
+ public int getGuestUid() {
+ return guestUid;
+ }
+
+ /** @hide */
+ public int getGuestGid() {
+ return guestGid;
+ }
+
+ /** @hide */
+ public int getMask() {
+ return mask;
+ }
+
+ /** @hide */
+ public String getTag() {
+ return tag;
+ }
+
+ /** @hide */
+ public String getSocket() {
+ return socket;
+ }
+ }
+
+ /** @hide */
public static final class Disk {
private final boolean writable;
private final String imagePath;
@@ -366,6 +459,7 @@
private String bootloaderPath;
private List<String> params = new ArrayList<>();
private List<Disk> disks = new ArrayList<>();
+ private List<SharedPath> sharedPaths = new ArrayList<>();
private AudioConfig audioConfig;
private DisplayConfig displayConfig;
private boolean touch;
@@ -413,6 +507,12 @@
}
/** @hide */
+ public Builder addSharedPath(SharedPath path) {
+ this.sharedPaths.add(path);
+ return this;
+ }
+
+ /** @hide */
public Builder addParam(String param) {
this.params.add(param);
return this;
@@ -493,6 +593,7 @@
this.bootloaderPath,
this.params.toArray(new String[0]),
this.disks.toArray(new Disk[0]),
+ this.sharedPaths.toArray(new SharedPath[0]),
displayConfig,
touch,
keyboard,
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
index 6d39b46..5d6b13f 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
@@ -17,6 +17,7 @@
package com.android.virtualization.vmlauncher;
import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.system.virtualmachine.VirtualMachineConfig;
import android.system.virtualmachine.VirtualMachineCustomImageConfig;
@@ -25,6 +26,7 @@
import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
import android.system.virtualmachine.VirtualMachineCustomImageConfig.Partition;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.SharedPath;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -34,6 +36,7 @@
import java.io.FileReader;
import java.util.Arrays;
+import java.util.Objects;
/** This class and its inner classes model vm_config.json. */
class ConfigJson {
@@ -60,6 +63,7 @@
private InputJson input;
private AudioJson audio;
private DiskJson[] disks;
+ private SharedPathJson[] sharedPath;
private DisplayJson display;
private GpuJson gpu;
@@ -141,9 +145,36 @@
Arrays.stream(disks).map(d -> d.toConfig()).forEach(builder::addDisk);
}
+ if (sharedPath != null) {
+ Arrays.stream(sharedPath)
+ .map(d -> d.toConfig(context))
+ .filter(Objects::nonNull)
+ .forEach(builder::addSharedPath);
+ }
return builder.build();
}
+ private static class SharedPathJson {
+ private SharedPathJson() {}
+
+ // Package ID of Terminal app.
+ private static final String TERMINAL_PACKAGE_ID =
+ "com.google.android.virtualization.terminal";
+ private String sharedPath;
+
+ private SharedPath toConfig(Context context) {
+ try {
+ int uid =
+ context.getPackageManager()
+ .getPackageUidAsUser(TERMINAL_PACKAGE_ID, context.getUserId());
+
+ return new SharedPath(sharedPath, uid, uid, 0, 0, 0007, "android", "android");
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
private static class InputJson {
private InputJson() {}