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(
44 path: &Path,
45 options: OpenDiskOptions,
46) -> anyhow::Result<Resource<DiskHandleKind>> {
47 let read_only = options.read_only;
48 let ensure_no_direct = |ext| {
49 if options.direct {
50 anyhow::bail!("direct I/O is not supported for {ext} files");
51 };
52 Ok(())
53 };
54 Ok(match path.extension().and_then(|s| s.to_str()) {
55 Some("vhd") => {
56 let file = std::fs::OpenOptions::new()
57 .read(true)
58 .write(!read_only)
59 .open(path)
60 .with_context(|| disk_open_error(path, "failed to open"))?;
61
62 match disk_vhd1::Vhd1Disk::open_fixed(file, read_only) {
63 Ok(vhd) => {
64 ensure_no_direct("fixed .vhd")?;
65 Resource::new(disk_backend_resources::FixedVhd1DiskHandle(
66 vhd.into_inner(),
67 ))
68 }
69 Err(disk_vhd1::OpenError::NotFixed) => {
70 #[cfg(windows)]
71 {
72 Resource::new(disk_vhdmp::OpenVhdmpDiskConfig(
73 disk_vhdmp::VhdmpDisk::options()
74 .read_only(read_only)
75 .cached_io(!options.direct)
76 .open(path)
77 .with_context(|| disk_open_error(path, "failed to open"))?,
78 ))
79 }
80 #[cfg(not(windows))]
81 anyhow::bail!("non-fixed VHD not supported on Linux");
82 }
83 Err(err) => return Err(err.into()),
84 }
85 }
86 Some("vhdx") => {
87 #[cfg(windows)]
88 {
89 Resource::new(disk_vhdmp::OpenVhdmpDiskConfig(
90 disk_vhdmp::VhdmpDisk::options()
91 .read_only(read_only)
92 .cached_io(!options.direct)
93 .open(path)
94 .with_context(|| disk_open_error(path, "failed to open"))?,
95 ))
96 }
97 #[cfg(not(windows))]
98 {
99 ensure_no_direct(".vhdx")?;
100 disklayer_vhdx::chain::open_vhdx_chain(path, read_only).await?
101 }
102 }
103 Some("iso") if !read_only => {
104 anyhow::bail!("iso file cannot be opened as read/write")
105 }
106 Some("vmgs") => {
107 ensure_no_direct(".vmgs")?;
108 let file = std::fs::OpenOptions::new()
111 .read(true)
112 .write(!read_only)
113 .open(path)
114 .with_context(|| disk_open_error(path, "failed to open"))?;
115
116 Resource::new(disk_backend_resources::FixedVhd1DiskHandle(file))
117 }
118 _ => open_raw_disk(path, options, None)?,
119 })
120}
121
122pub fn create_disk_type(
124 path: &Path,
125 size: u64,
126 options: OpenDiskOptions,
127) -> anyhow::Result<Resource<DiskHandleKind>> {
128 Ok(match path.extension().and_then(|s| s.to_str()) {
129 Some("vhd") | Some("vmgs") => {
130 if options.direct {
131 anyhow::bail!("direct I/O is not supported for VHD files");
132 }
133 let file = std::fs::OpenOptions::new()
134 .create(true)
135 .truncate(true)
136 .read(true)
137 .write(true)
138 .open(path)
139 .with_context(|| disk_open_error(path, "failed to create"))?;
140
141 file.set_len(size)?;
142 disk_vhd1::Vhd1Disk::make_fixed(&file)?;
143 Resource::new(disk_backend_resources::FixedVhd1DiskHandle(file))
144 }
145 Some("vhdx") => {
146 anyhow::bail!("creating vhdx not supported")
147 }
148 Some("iso") => {
149 anyhow::bail!("creating iso not supported")
150 }
151 _ => open_raw_disk(path, options, Some(size))?,
152 })
153}
154
155fn open_raw_disk(
158 path: &Path,
159 options: OpenDiskOptions,
160 size: Option<u64>,
161) -> anyhow::Result<Resource<DiskHandleKind>> {
162 if options.direct && !cfg!(target_os = "linux") {
163 anyhow::bail!("direct I/O is only supported on Linux");
164 }
165
166 let create = size.is_some();
167 let mut opts = std::fs::OpenOptions::new();
168 opts.read(true).write(!options.read_only);
169 if create {
170 opts.create(true).truncate(true);
171 }
172
173 #[cfg(target_os = "linux")]
174 if options.direct {
175 use std::os::unix::fs::OpenOptionsExt;
176 opts.custom_flags(libc::O_DIRECT);
177 }
178
179 let verb = if create {
180 "failed to create"
181 } else {
182 "failed to open"
183 };
184 let file = opts
185 .open(path)
186 .with_context(|| disk_open_error(path, verb))?;
187
188 if let Some(size) = size {
189 file.set_len(size)?;
190 }
191
192 #[cfg(target_os = "linux")]
193 {
194 Ok(Resource::new(
195 disk_backend_resources::BlockDeviceDiskHandle { file },
196 ))
197 }
198 #[cfg(not(target_os = "linux"))]
199 {
200 Ok(Resource::new(disk_backend_resources::FileDiskHandle(file)))
201 }
202}