1 // Copyright 2018 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Exported interface to basic qcow functionality to be used from C.
6
7 #![cfg(unix)]
8
9 use std::ffi::CStr;
10 use std::fs::OpenOptions;
11 use std::os::raw::c_char;
12 use std::os::raw::c_int;
13
14 use base::flock;
15 use base::FlockOperation;
16 use disk::DiskFile;
17 use disk::ImageType;
18 #[cfg(feature = "qcow")]
19 use disk::QcowFile;
20 use libc::EINVAL;
21 use libc::EIO;
22 use libc::ENOSYS;
23
24 /// # Safety
25 /// The path passed in must be a valid pointer to a path; only NULL pointers are rejected.
26 #[no_mangle]
27 #[allow(unused_variables)] // If the qcow feature is disabled, this function becomes an empty stub.
create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int28 pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int {
29 #[cfg(feature = "qcow")]
30 {
31 // NULL pointers are checked, but this will access any other invalid pointer passed from C
32 // code. It's the caller's responsibility to pass a valid pointer.
33 if path.is_null() {
34 return -EINVAL;
35 }
36 let c_str = CStr::from_ptr(path);
37 let file_path = match c_str.to_str() {
38 Ok(s) => s,
39 Err(_) => return -EINVAL,
40 };
41
42 let file = match OpenOptions::new()
43 .create(true)
44 .read(true)
45 .write(true)
46 .open(file_path)
47 {
48 Ok(f) => f,
49 Err(_) => return -1,
50 };
51
52 match QcowFile::new(file, virtual_size) {
53 Ok(_) => 0,
54 Err(_) => -1,
55 }
56 }
57 #[cfg(not(feature = "qcow"))]
58 -ENOSYS // Not implemented
59 }
60
61 /// # Safety
62 /// The path passed in must be a valid pointer to a path; only NULL pointers are rejected.
63 #[no_mangle]
expand_disk_image(path: *const c_char, virtual_size: u64) -> c_int64 pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u64) -> c_int {
65 // NULL pointers are checked, but this will access any other invalid pointer passed from C
66 // code. It's the caller's responsibility to pass a valid pointer.
67 if path.is_null() {
68 return -EINVAL;
69 }
70 let c_str = CStr::from_ptr(path);
71 let file_path = match c_str.to_str() {
72 Ok(s) => s,
73 Err(_) => return -EINVAL,
74 };
75
76 let raw_image = match OpenOptions::new().read(true).write(true).open(file_path) {
77 Ok(f) => f,
78 Err(_) => return -EIO,
79 };
80
81 // Lock the disk image to prevent other processes from using it.
82 if flock(&raw_image, FlockOperation::LockExclusive, true).is_err() {
83 return -EIO;
84 }
85
86 let image_type = match disk::detect_image_type(&raw_image) {
87 Ok(t) => t,
88 Err(_) => return -EINVAL,
89 };
90
91 let disk_image: Box<dyn DiskFile> = match image_type {
92 ImageType::Raw => Box::new(raw_image),
93 #[cfg(feature = "qcow")]
94 ImageType::Qcow2 => match QcowFile::from(raw_image, disk::MAX_NESTING_DEPTH) {
95 Ok(f) => Box::new(f),
96 Err(_) => return -EINVAL,
97 },
98 _ => return -EINVAL,
99 };
100
101 // For safety against accidentally shrinking the disk image due to a
102 // programming error elsewhere, verify that the new size is larger than
103 // the current size. This is safe against races due to the exclusive
104 // flock() taken above - this is only an advisory lock, but it is also
105 // acquired by other instances of this function as well as crosvm
106 // itself when running a VM, so this should be safe in all cases that
107 // can access a disk image in normal operation.
108 let current_size = match disk_image.get_len() {
109 Ok(len) => len,
110 Err(_) => return -EIO,
111 };
112 if current_size >= virtual_size {
113 return 0;
114 }
115
116 match disk_image.set_len(virtual_size) {
117 Ok(_) => 0,
118 Err(_) => -ENOSYS,
119 }
120 }
121