qcow2_rs/
utils.rs

1use crate::dev::{Qcow2Dev, Qcow2DevParams};
2use crate::error::Qcow2Result;
3use crate::helpers::Qcow2IoBuf;
4use crate::meta::Qcow2Header;
5use crate::ops::*;
6#[cfg(not(target_os = "windows"))]
7use crate::sync_io::Qcow2IoSync;
8#[cfg(target_os = "linux")]
9use crate::uring::Qcow2IoUring;
10use async_recursion::async_recursion;
11use std::io::Write;
12use std::path::{Path, PathBuf};
13
14#[macro_export]
15macro_rules! qcow2_default_params {
16    ($ro: expr, $dio: expr) => {
17        Qcow2DevParams::new(9, None, None, $ro, $dio)
18    };
19}
20
21/// 4K is usually enough for holding generic qcow2 header
22const DEF_HEADER_SIZE: usize = 4096;
23
24/// 64K is big enough to hold any kind of qcow2 header
25const MAX_HEADER_SIZE: usize = 65536;
26
27/// Allocate one qcow2 device and qcow2 header needs to be parsed
28/// for allocating the device.
29pub fn qcow2_alloc_dev_sync<T: Qcow2IoOps>(
30    path: &Path,
31    io: T,
32    params: &Qcow2DevParams,
33) -> Qcow2Result<(Qcow2Dev<T>, Option<PathBuf>)> {
34    fn read_header(path: &Path, bytes: usize) -> Qcow2Result<Qcow2IoBuf<u8>> {
35        use std::io::Read;
36        let mut buf = Qcow2IoBuf::<u8>::new(bytes);
37        let mut file = std::fs::File::open(path).unwrap();
38        let _ = file.read(&mut buf).unwrap();
39        Ok(buf)
40    }
41
42    let buf = read_header(path, DEF_HEADER_SIZE)?;
43    let header = match Qcow2Header::from_buf(&buf) {
44        Ok(h) => h,
45        Err(_) => {
46            let buf = read_header(path, MAX_HEADER_SIZE)?;
47            Qcow2Header::from_buf(&buf)?
48        }
49    };
50    let back_path = header.backing_filename().map(|s| PathBuf::from(s.clone()));
51
52    Ok((
53        Qcow2Dev::new(path, header, params, io).expect("new dev failed"),
54        back_path,
55    ))
56}
57
58/// Allocate one qcow2 device and qcow2 header needs to be parsed
59/// for allocating the device.
60pub async fn qcow2_alloc_dev<T: Qcow2IoOps>(
61    path: &Path,
62    io: T,
63    params: &Qcow2DevParams,
64) -> Qcow2Result<(Qcow2Dev<T>, Option<PathBuf>)> {
65    async fn read_header<T: Qcow2IoOps>(io: &T, bytes: usize) -> Qcow2Result<Qcow2IoBuf<u8>> {
66        let mut buf = Qcow2IoBuf::<u8>::new(bytes);
67        let _ = io.read_to(0, &mut buf).await?;
68        Ok(buf)
69    }
70    let buf = read_header(&io, DEF_HEADER_SIZE).await?;
71    let header = match Qcow2Header::from_buf(&buf) {
72        Ok(h) => h,
73        Err(_) => {
74            let buf = read_header(&io, MAX_HEADER_SIZE).await?;
75            Qcow2Header::from_buf(&buf)?
76        }
77    };
78    let back_path = header.backing_filename().map(|s| PathBuf::from(s.clone()));
79
80    Ok((
81        Qcow2Dev::new(path, header, params, io).expect("new dev failed"),
82        back_path,
83    ))
84}
85
86/// Build one async helper which can setup one qcow2 device
87///
88/// The backing device is setup automatically in the built helper.
89#[macro_export]
90macro_rules! qcow2_setup_dev_fn {
91    ($type:ty, $fn_name: ident) => {
92        #[async_recursion(?Send)]
93        pub async fn $fn_name(
94            path: &Path,
95            params: &Qcow2DevParams,
96        ) -> Qcow2Result<Qcow2Dev<$type>> {
97            let io = <$type>::new(path, params.is_read_only(), params.is_direct_io()).await;
98            let (mut dev, backing) = qcow2_alloc_dev(&path, io, params).await?;
99            match backing {
100                Some(back_path) => {
101                    let p = params.clone();
102                    p.mark_backing_dev(Some(true));
103                    let bdev = $fn_name(&back_path.as_path(), &p).await?;
104                    dev.set_backing_dev(Box::new(bdev));
105                }
106                _ => {}
107            };
108
109            dev.__qcow2_prep_io().await?;
110            Ok(dev)
111        }
112    };
113}
114
115#[cfg(target_os = "linux")]
116qcow2_setup_dev_fn!(Qcow2IoUring, qcow2_setup_dev_uring);
117qcow2_setup_dev_fn!(crate::tokio_io::Qcow2IoTokio, qcow2_setup_dev_tokio);
118
119/// Build one helper which can setup one qcow2 device, and this helper
120/// needn't be async/.await
121///
122/// The backing device is setup automatically in the built helper.
123#[macro_export]
124macro_rules! qcow2_setup_dev_fn_sync {
125    ($type:ty, $fn_name: ident) => {
126        pub fn $fn_name(path: &Path, params: &Qcow2DevParams) -> Qcow2Result<Qcow2Dev<$type>> {
127            let io = <$type>::new(path, params.is_read_only(), params.is_direct_io());
128            let (mut dev, backing) = qcow2_alloc_dev_sync(&path, io, params)?;
129            match backing {
130                Some(back_path) => {
131                    let p = params.clone();
132                    p.mark_backing_dev(Some(true));
133                    let bdev = $fn_name(&back_path.as_path(), &p)?;
134                    dev.set_backing_dev(Box::new(bdev));
135                }
136                _ => {}
137            };
138
139            Ok(dev)
140        }
141    };
142}
143
144#[cfg(not(target_os = "windows"))]
145qcow2_setup_dev_fn_sync!(Qcow2IoSync, qcow2_setup_dev_sync);
146
147fn make_qcow2_buf(cluster_bits: usize, refcount_order: u8, size: u64) -> Vec<u8> {
148    let bs_shift = 9_u8;
149    let bs = 1 << bs_shift;
150    let (rc_t, rc_b, _) =
151        Qcow2Header::calculate_meta_params(size, cluster_bits, refcount_order, bs);
152    let clusters = 1 + rc_t.1 + rc_b.1;
153    let img_size = ((clusters as usize) << cluster_bits) + 512;
154    let mut buf = vec![0u8; img_size];
155
156    Qcow2Header::format_qcow2(&mut buf, size, cluster_bits, refcount_order, bs).unwrap();
157
158    buf
159}
160
161pub fn make_temp_qcow2_img(
162    size: u64,
163    cluster_bits: usize,
164    refcount_order: u8,
165) -> tempfile::NamedTempFile {
166    let tmpfile = tempfile::NamedTempFile::new().unwrap();
167    let mut file = std::fs::File::create(tmpfile.path()).unwrap();
168
169    let buf = make_qcow2_buf(cluster_bits, refcount_order, size);
170    file.write_all(&buf).unwrap();
171
172    tmpfile
173}