1use anyhow::Context;
7use std::path::Path;
8use vm_resource::Resource;
9use vm_resource::kind::DiskHandleKind;
10
11fn disk_open_error(path: &Path, verb: &str) -> String {
12 let mut msg = format!("{verb} '{}'", path.display());
13
14 if cfg!(windows) && std::env::var_os("WSLENV").is_some() {
17 msg += ". Linux paths are not supported when running Windows executables \
18 under WSL, make sure the path is a valid Windows path \
19 (use `wslpath -w` to convert)";
20 }
21
22 msg
23}
24
25#[derive(Clone, Copy)]
27pub struct OpenDiskOptions {
28 pub read_only: bool,
30 pub direct: bool,
32}
33
34pub async fn open_disk_type(
40 path: &Path,
41 options: OpenDiskOptions,
42) -> anyhow::Result<Resource<DiskHandleKind>> {
43 let read_only = options.read_only;
44 let ensure_no_direct = |ext| {
45 if options.direct {
46 anyhow::bail!("direct I/O is not supported for {ext} files");
47 };
48 Ok(())
49 };
50 Ok(match path.extension().and_then(|s| s.to_str()) {
51 Some("vhd") => {
52 let file = std::fs::OpenOptions::new()
53 .read(true)
54 .write(!read_only)
55 .open(path)
56 .with_context(|| disk_open_error(path, "failed to open"))?;
57
58 match disk_vhd1::Vhd1Disk::open_fixed(file, read_only) {
59 Ok(vhd) => {
60 ensure_no_direct("fixed .vhd")?;
61 Resource::new(disk_backend_resources::FixedVhd1DiskHandle(
62 vhd.into_inner(),
63 ))
64 }
65 Err(disk_vhd1::OpenError::NotFixed) => {
66 #[cfg(windows)]
67 {
68 Resource::new(disk_vhdmp::OpenVhdmpDiskConfig(
69 disk_vhdmp::VhdmpDisk::options()
70 .read_only(read_only)
71 .cached_io(!options.direct)
72 .open(path)
73 .with_context(|| disk_open_error(path, "failed to open"))?,
74 ))
75 }
76 #[cfg(not(windows))]
77 anyhow::bail!("non-fixed VHD not supported on Linux");
78 }
79 Err(err) => return Err(err.into()),
80 }
81 }
82 Some("vhdx") => {
83 #[cfg(windows)]
84 {
85 Resource::new(disk_vhdmp::OpenVhdmpDiskConfig(
86 disk_vhdmp::VhdmpDisk::options()
87 .read_only(read_only)
88 .cached_io(!options.direct)
89 .open(path)
90 .with_context(|| disk_open_error(path, "failed to open"))?,
91 ))
92 }
93 #[cfg(not(windows))]
94 anyhow::bail!("VHDX not supported on Linux");
95 }
96 Some("iso") if !read_only => {
97 anyhow::bail!("iso file cannot be opened as read/write")
98 }
99 Some("vmgs") => {
100 ensure_no_direct(".vmgs")?;
101 let file = std::fs::OpenOptions::new()
104 .read(true)
105 .write(!read_only)
106 .open(path)
107 .with_context(|| disk_open_error(path, "failed to open"))?;
108
109 Resource::new(disk_backend_resources::FixedVhd1DiskHandle(file))
110 }
111 _ => open_raw_disk(path, options, None)?,
112 })
113}
114
115pub fn create_disk_type(
117 path: &Path,
118 size: u64,
119 options: OpenDiskOptions,
120) -> anyhow::Result<Resource<DiskHandleKind>> {
121 Ok(match path.extension().and_then(|s| s.to_str()) {
122 Some("vhd") | Some("vmgs") => {
123 if options.direct {
124 anyhow::bail!("direct I/O is not supported for VHD files");
125 }
126 let file = std::fs::OpenOptions::new()
127 .create(true)
128 .truncate(true)
129 .read(true)
130 .write(true)
131 .open(path)
132 .with_context(|| disk_open_error(path, "failed to create"))?;
133
134 file.set_len(size)?;
135 disk_vhd1::Vhd1Disk::make_fixed(&file)?;
136 Resource::new(disk_backend_resources::FixedVhd1DiskHandle(file))
137 }
138 Some("vhdx") => {
139 anyhow::bail!("creating vhdx not supported")
140 }
141 Some("iso") => {
142 anyhow::bail!("creating iso not supported")
143 }
144 _ => open_raw_disk(path, options, Some(size))?,
145 })
146}
147
148fn open_raw_disk(
151 path: &Path,
152 options: OpenDiskOptions,
153 size: Option<u64>,
154) -> anyhow::Result<Resource<DiskHandleKind>> {
155 if options.direct && !cfg!(target_os = "linux") {
156 anyhow::bail!("direct I/O is only supported on Linux");
157 }
158
159 let create = size.is_some();
160 let mut opts = std::fs::OpenOptions::new();
161 opts.read(true).write(!options.read_only);
162 if create {
163 opts.create(true).truncate(true);
164 }
165
166 #[cfg(target_os = "linux")]
167 if options.direct {
168 use std::os::unix::fs::OpenOptionsExt;
169 opts.custom_flags(libc::O_DIRECT);
170 }
171
172 let verb = if create {
173 "failed to create"
174 } else {
175 "failed to open"
176 };
177 let file = opts
178 .open(path)
179 .with_context(|| disk_open_error(path, verb))?;
180
181 if let Some(size) = size {
182 file.set_len(size)?;
183 }
184
185 #[cfg(target_os = "linux")]
186 {
187 Ok(Resource::new(
188 disk_backend_resources::BlockDeviceDiskHandle { file },
189 ))
190 }
191 #[cfg(not(target_os = "linux"))]
192 {
193 Ok(Resource::new(disk_backend_resources::FileDiskHandle(file)))
194 }
195}