Print actual file names rather than /proc/self/fd/NN

When virtualization service spawns a crosvm process, it doesn't pass
path to the disk files, but opens those files by itself and passes
/proc/self/fd/N paths where N is the open FD. This however makes the
logs difficult to understand.

This CL annotates those /proc/self/fd/N paths with their original paths
when printing the commandline arguments for the crosvm process.

Following is an example log:
```
I VirtualizationService: virtualizationservice::crosvm: Running crosvm
with args: ["--extended-status", "--log-level", "info,disk=off", "run",
"--disable-sandbox", "--no-balloon", "--cid", "10", "--mem", "88",
"--cpus", "1",
"--serial=type=file,path=/proc/self/fd/24,hardware=serial,num=1",
"--serial=type=file,path=/proc/self/fd/15,hardware=serial,num=2",
"--serial=type=file,path=/proc/self/fd/24,hardware=virtio-console,num=1",
"--serial=type=file,path=/proc/self/fd/52 (/data/misc/virtualizationservice/10/ramdump),hardware=virtio-console,num=2",
"--serial=type=file,path=/proc/self/fd/25,hardware=virtio-console,num=3",
"--initrd", "/proc/self/fd/58 (/apex/com.android.virt/etc/microdroid_initrd_full_debuggable.img)",
"--disk", "/proc/self/fd/42 (/data/misc/virtualizationservice/10/composite-0.img)",
"--rwdisk", "/proc/self/fd/47 (/data/misc/virtualizationservice/10/composite-1.img)",
"--disk", "/proc/self/fd/56 (/data/misc/virtualizationservice/10/composite-2.img)",
"/proc/self/fd/57 (/apex/com.android.virt/etc/fs/microdroid_kernel)",
"--socket", "/proc/self/fd/16"]
```

Bug: 251751405
Test: start a VM and see the log
Change-Id: I4ae3bf50c942426164f2f5cc45b8d6bb8e68b85b
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index f3abee0..f5c894a 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -23,6 +23,7 @@
 use log::{debug, error, info};
 use semver::{Version, VersionReq};
 use nix::{fcntl::OFlag, unistd::pipe2};
+use regex::{Captures, Regex};
 use shared_child::SharedChild;
 use std::borrow::Cow;
 use std::fs::{remove_dir_all, File};
@@ -546,7 +547,8 @@
     debug!("Preserving FDs {:?}", preserved_fds);
     command.preserved_fds(preserved_fds);
 
-    info!("Running {:?}", command);
+    print_crosvm_args(&command);
+
     let result = SharedChild::spawn(&mut command)?;
     debug!("Spawned crosvm({}).", result.id());
     Ok(result)
@@ -573,6 +575,31 @@
     Ok(())
 }
 
+/// Print arguments of the crosvm command. In doing so, /proc/self/fd/XX is annotated with the
+/// actual file path if the FD is backed by a regular file. If not, the /proc path is printed
+/// unmodified.
+fn print_crosvm_args(command: &Command) {
+    let re = Regex::new(r"/proc/self/fd/[\d]+").unwrap();
+    info!(
+        "Running crosvm with args: {:?}",
+        command
+            .get_args()
+            .map(|s| s.to_string_lossy())
+            .map(|s| {
+                re.replace_all(&s, |caps: &Captures| {
+                    let path = &caps[0];
+                    if let Ok(realpath) = std::fs::canonicalize(path) {
+                        format!("{} ({})", path, realpath.to_string_lossy())
+                    } else {
+                        path.to_owned()
+                    }
+                })
+                .into_owned()
+            })
+            .collect::<Vec<_>>()
+    );
+}
+
 /// Adds the file descriptor for `file` to `preserved_fds`, and returns a string of the form
 /// "/proc/self/fd/N" where N is the file descriptor.
 fn add_preserved_fd(preserved_fds: &mut Vec<RawFd>, file: &dyn AsRawFd) -> String {