Skip to main content

imago/qcow2/
builder.rs

1//! Builders for opening and creating qcow2 images.
2
3use super::*;
4use crate::format::builder::{
5    FormatCreateBuilderBase, FormatDriverBuilderBase, FormatOrBuilder, StorageOrPath,
6};
7use crate::DenyImplicitOpenGate;
8use std::marker::PhantomData;
9use std::path::PathBuf;
10
11/// Options builder for opening a qcow2 image.
12///
13/// Allows setting various options one by one to open a qcow2 image.
14pub struct Qcow2OpenBuilder<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>> {
15    /// Basic options.
16    base: FormatDriverBuilderBase<S>,
17
18    /// Backing image
19    ///
20    /// `None` to open the image as specified by the image header, `Some(None)` to not open any
21    /// backing image, and `Some(Some(_))` to use that backing image.
22    backing: Option<Option<FormatOrBuilder<S, F>>>,
23
24    /// External data file
25    ///
26    /// `None` to open the file as specified by the image header, `Some(None)` to not open any data
27    /// file, and `Some(Some(_))` to use that data file.
28    data_file: Option<Option<StorageOrPath<S>>>,
29}
30
31/// Options builder for creating (formatting) a qcow2 image.
32///
33/// Allows setting various options for a new qcow2 image.
34pub struct Qcow2CreateBuilder<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>>
35{
36    /// Basic options.
37    base: FormatCreateBuilderBase<S>,
38
39    /// Backing image filename and format
40    backing: Option<(String, String)>,
41
42    /// External data file name and the file itself
43    data_file: Option<(String, S)>,
44
45    /// Cluster size
46    cluster_size: usize,
47
48    /// Refcount bit width
49    refcount_width: usize,
50
51    /// Needed for the correct `create_open()` return type
52    _wrapped_format: PhantomData<F>,
53}
54
55impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Qcow2OpenBuilder<S, F> {
56    /// Create a new instance.
57    fn with_base(base: FormatDriverBuilderBase<S>) -> Self {
58        Qcow2OpenBuilder {
59            base,
60            backing: None,
61            data_file: None,
62        }
63    }
64
65    /// Set a backing image.
66    ///
67    /// This overrides the implicit backing image given in the image header.  Passing `None` means
68    /// not to use any backing image (regardless of whether the image header defines a backing
69    /// image).
70    pub fn backing(mut self, backing: Option<F>) -> Self {
71        self.backing = Some(backing.map(FormatOrBuilder::Format));
72        self
73    }
74
75    /// Declare a backing image by path.
76    ///
77    /// Let imago open the given path as an image with the given format.
78    ///
79    /// Use with caution, as the given image will be opened with default options.
80    /// [`Qcow2OpenBuilder::backing()`] is preferable, as it allows you control over how the
81    /// backing image is opened.
82    pub fn backing_path<P: AsRef<Path>>(mut self, backing: P, format: Format) -> Self {
83        self.backing = Some(Some(FormatOrBuilder::new_builder(format, backing)));
84        self
85    }
86
87    /// Set an external data file.
88    ///
89    /// This overrides the implicit external data file given in the image header.  Passing `None`
90    /// means not to use any external data file (regardless of whether the image header defines an
91    /// external data file, and regardless of whether the image header says the image has an
92    /// external data file).
93    ///
94    /// Similarly, passing a data file will then always use that data file, regardless of whether
95    /// the image header says the image has an external data file.
96    ///
97    /// Note that it is wrong to set a data file for an image that does not have one, and it is
98    /// wrong to enforce not using a data file for an image that has one.  There is no way to know
99    /// whether the image needs an external data file until it is opened.
100    ///
101    /// If you want to open a specific data file if and only if the image needs it, call
102    /// `Qcow2OpenBuilder::data_file(None)` to prevent any data file from being automatically
103    /// opened; open the image, then check [`Qcow2::requires_external_data_file()`], and, if true,
104    /// invoke [`Qcow2::set_data_file()`].
105    pub fn data_file(mut self, data_file: Option<S>) -> Self {
106        self.data_file = Some(data_file.map(StorageOrPath::Storage));
107        self
108    }
109}
110
111#[maybe_async(AFIT)]
112impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverBuilder<S>
113    for Qcow2OpenBuilder<S, F>
114{
115    type Format = Qcow2<S, F>;
116    const FORMAT: Format = Format::Qcow2;
117
118    fn new(image: S) -> Self {
119        Self::with_base(FormatDriverBuilderBase::new(image))
120    }
121
122    fn new_path<P: AsRef<Path>>(path: P) -> Self {
123        Self::with_base(FormatDriverBuilderBase::new_path(path))
124    }
125
126    fn write(mut self, write: bool) -> Self {
127        self.base.set_write(write);
128        self
129    }
130
131    fn storage_open_options(mut self, options: StorageOpenOptions) -> Self {
132        self.base.set_storage_open_options(options);
133        self
134    }
135
136    async fn open<G: ImplicitOpenGate<S>>(self, mut gate: G) -> io::Result<Self::Format> {
137        let writable = self.base.get_writable();
138        let storage_opts = self.base.make_storage_opts();
139        let metadata = self.base.open_image(&mut gate).await?;
140
141        let mut qcow2 = Qcow2::<S, F>::do_open(metadata, writable, storage_opts.clone()).await?;
142
143        if let Some(backing) = self.backing {
144            let backing = match backing {
145                None => None,
146                Some(backing) => Some(
147                    backing
148                        .open_format(storage_opts.clone().write(false), &mut gate)
149                        .await
150                        .err_context(|| "Backing file")?,
151                ),
152            };
153            qcow2.set_backing(backing);
154        }
155
156        if let Some(data_file) = self.data_file {
157            let data_file = match data_file {
158                None => None,
159                Some(data_file) => Some(
160                    data_file
161                        .open_storage(storage_opts, &mut gate)
162                        .await
163                        .err_context(|| "External data file")?,
164                ),
165            };
166            qcow2.set_data_file(data_file);
167        }
168
169        qcow2.open_implicit_dependencies_gated(gate).await?;
170
171        Ok(qcow2)
172    }
173
174    fn get_image_path(&self) -> Option<PathBuf> {
175        self.base.get_image_path()
176    }
177
178    fn get_writable(&self) -> bool {
179        self.base.get_writable()
180    }
181
182    fn get_storage_open_options(&self) -> Option<&StorageOpenOptions> {
183        self.base.get_storage_opts()
184    }
185}
186
187impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Qcow2CreateBuilder<S, F> {
188    /// Set a backing image.
189    ///
190    /// Set the path to the backing image to be written into the image header; this path will be
191    /// interpreted relative to the qcow2 image file.
192    ///
193    /// The backing format should be one of “qcow2” or “raw”.
194    ///
195    /// Neither of filename or format are checked for validity.
196    pub fn backing(mut self, backing_filename: String, backing_format: String) -> Self {
197        self.backing = Some((backing_filename, backing_format));
198        self
199    }
200
201    /// Set an external data file.
202    ///
203    /// Set the path for an external data file.  This path will be interpreted relative to the
204    /// qcow2 image file.  This path is not checked for whether it matches `file` or even points to
205    /// anything at all.
206    ///
207    /// `file` is the data file itself; it is necessary to pass this storage object into the
208    /// builder for preallocation purposes.
209    pub fn data_file(mut self, filename: String, file: S) -> Self {
210        self.data_file = Some((filename, file));
211        self
212    }
213
214    /// Set the cluster size (in bytes).
215    ///
216    /// A cluster is the unit of allocation for qcow2 images.  Smaller clusters can lead to better
217    /// COW performance, but worse performance for fully allocated images, and have increased
218    /// metadata size overhead.
219    ///
220    /// Must be a power of two between 512 and 2 MiB (inclusive).
221    ///
222    /// The default is 64 KiB.
223    pub fn cluster_size(mut self, size: usize) -> Self {
224        self.cluster_size = size;
225        self
226    }
227
228    /// Set the refcount width in bits.
229    ///
230    /// Reference counting is used to determine empty areas in the image file, though this only
231    /// needs refcounts of 0 and 1, i.e. a reference bit width of 1.
232    ///
233    /// Larger refcount bit widths are only needed when using internal snapshots, in which case
234    /// multiple snapshots can share clusters.
235    ///
236    /// Must be a power of two between 1 and 64 (inclusive).
237    ///
238    /// The default is 16 bits.
239    pub fn refcount_width(mut self, bits: usize) -> Self {
240        self.refcount_width = bits;
241        self
242    }
243}
244
245#[maybe_async(AFIT)]
246impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatCreateBuilder<S>
247    for Qcow2CreateBuilder<S, F>
248{
249    const FORMAT: Format = Format::Qcow2;
250    type DriverBuilder = Qcow2OpenBuilder<S, F>;
251
252    fn new(image: S) -> Self {
253        Qcow2CreateBuilder {
254            base: FormatCreateBuilderBase::new(image),
255            backing: None,
256            data_file: None,
257            cluster_size: 65536,
258            refcount_width: 16,
259            _wrapped_format: PhantomData,
260        }
261    }
262
263    fn size(mut self, size: u64) -> Self {
264        self.base.set_size(size);
265        self
266    }
267
268    fn preallocate(mut self, prealloc_mode: PreallocateMode) -> Self {
269        self.base.set_preallocate(prealloc_mode);
270        self
271    }
272
273    fn get_size(&self) -> u64 {
274        self.base.get_size()
275    }
276
277    fn get_preallocate(&self) -> PreallocateMode {
278        self.base.get_preallocate()
279    }
280
281    async fn create(self) -> io::Result<()> {
282        self.create_open(DenyImplicitOpenGate::default(), |image| {
283            // data file will be set by `create_open()`
284            Ok(Qcow2::<S, F>::builder(image).backing(None).write(true))
285        })
286        .await?
287        .flush()
288        .await?;
289
290        Ok(())
291    }
292
293    async fn create_open<
294        G: ImplicitOpenGate<S>,
295        OBF: FnOnce(S) -> io::Result<Qcow2OpenBuilder<S, F>>,
296    >(
297        self,
298        open_gate: G,
299        open_builder_fn: OBF,
300    ) -> io::Result<Qcow2<S, F>> {
301        let size = self.base.get_size();
302        let prealloc = self.base.get_preallocate();
303        let image = self.base.get_image();
304
305        let cluster_size = self.cluster_size;
306        if !cluster_size.is_power_of_two() {
307            return Err(io::Error::new(
308                io::ErrorKind::InvalidInput,
309                format!("Cluster size {cluster_size} is not a power of two"),
310            ));
311        }
312
313        let cs_range = MIN_CLUSTER_SIZE..=MAX_CLUSTER_SIZE;
314        if !cs_range.contains(&cluster_size) {
315            return Err(io::Error::new(
316                io::ErrorKind::InvalidInput,
317                format!("Cluster size {cluster_size} not in {cs_range:?}"),
318            ));
319        }
320
321        let cluster_bits = cluster_size.trailing_zeros();
322        assert!(1 << cluster_bits == cluster_size);
323
324        let refcount_width = self.refcount_width;
325        if !refcount_width.is_power_of_two() {
326            return Err(io::Error::new(
327                io::ErrorKind::InvalidInput,
328                format!("Refcount width {refcount_width} is not a power of two"),
329            ));
330        }
331
332        let rw_range = MIN_REFCOUNT_WIDTH..=MAX_REFCOUNT_WIDTH;
333        if !rw_range.contains(&refcount_width) {
334            return Err(io::Error::new(
335                io::ErrorKind::InvalidInput,
336                format!("Refcount width {refcount_width} not in {rw_range:?}"),
337            ));
338        }
339
340        let refcount_order = refcount_width.trailing_zeros();
341        assert!(1 << refcount_order == refcount_width);
342
343        Qcow2::<S, F>::check_valid_preallocation(prealloc, self.backing.is_some())?;
344
345        // Clear of data
346        if image.size()? > 0 {
347            image.resize(size, storage::PreallocateMode::None).await?;
348        }
349
350        // Allocate just header and a minimal refcount structure.  The image will have a length of
351        // 0 at first, so doesn’t need an L1 table.
352        // To give the image the correct size, we just open and resize it.
353        //
354        // Cluster use:
355        // 0. Header
356        // 1. Refcount table
357        // 2. Refcount block
358        //
359        // Technically, we could also just write the header without refcount info, but the dirty
360        // bit set.  Too cheeky for my taste, though.
361
362        let (backing_fname, backing_format) = match self.backing {
363            Some((fname, fmt)) => (Some(fname), Some(fmt)),
364            None => (None, None),
365        };
366
367        let (data_file_name, data_file) = match self.data_file {
368            Some((fname, file)) => (Some(fname), Some(file)),
369            None => (None, None),
370        };
371
372        let mut header = Header::new(
373            cluster_bits,
374            refcount_order,
375            backing_fname,
376            backing_format,
377            data_file_name,
378        );
379
380        let mut rb = RefBlock::new_cleared(&image, &header)?;
381        rb.set_cluster(HostCluster(2));
382        {
383            let mut rb_locked = rb.lock_write().await;
384            rb_locked.increment(0)?; // header
385            rb_locked.increment(1)?; // reftable
386            rb_locked.increment(2)?; // refblock
387        }
388        rb.write(&image).await?;
389
390        let mut rt = RefTable::from_data(Box::new([]), &header).clone_and_grow(&header, 0)?;
391        rt.set_cluster(HostCluster(1));
392        rt.enter_refblock(0, &rb)?;
393        rt.write(&image).await?;
394
395        header.set_reftable(&rt)?;
396        header.write(&image).await?;
397
398        let img = open_builder_fn(image)?
399            .write(true)
400            .data_file(data_file)
401            .open(open_gate)
402            .await?;
403        if size > 0 {
404            img.resize_grow(size, prealloc).await?;
405        }
406
407        Ok(img)
408    }
409}