-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathfile.rs
137 lines (118 loc) · 4.51 KB
/
file.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Abstractions for disks with a raw file backend.
use camino::{Utf8Path, Utf8PathBuf};
use propolis_client::types::{FileStorageBackend, StorageBackendV0};
use tracing::{debug, error, warn};
use uuid::Uuid;
use crate::{guest_os::GuestOsKind, zfs::ClonedFile as ZfsClonedFile};
/// Describes the method used to create the backing file for a file-backed disk.
#[derive(Debug)]
enum BackingFile {
/// The disk is a ZFS clone of the original artifact.
Zfs(ZfsClonedFile),
/// The disk is a hard copy of the original artifact.
HardCopy(Utf8PathBuf),
}
impl BackingFile {
/// Creates a new backing file from the artifact with the supplied
/// `artifact_path`. If possible, this routine will create a ZFS clone of
/// the dataset containing the file; otherwise it will fall back to creating
/// a hard copy of the original artifact.
fn create_from_source(
artifact_path: &Utf8Path,
data_dir: &Utf8Path,
) -> anyhow::Result<Self> {
match ZfsClonedFile::create_from_path(artifact_path) {
Ok(file) => return Ok(Self::Zfs(file)),
Err(error) => warn!(
%artifact_path,
%error,
"failed to make ZFS clone of backing artifact, will copy it"
),
}
let mut disk_path = data_dir.to_path_buf();
disk_path.push(format!("{}.phd_disk", Uuid::new_v4()));
debug!(
source = %artifact_path,
disk_path = %disk_path,
"Copying source image to create temporary disk",
);
std::fs::copy(artifact_path, &disk_path)?;
Ok(Self::HardCopy(disk_path))
}
/// Yields the path to this disk's backing file.
fn path(&self) -> Utf8PathBuf {
match self {
BackingFile::Zfs(zfs) => zfs.path(),
BackingFile::HardCopy(path) => path.clone(),
}
}
}
impl Drop for BackingFile {
fn drop(&mut self) {
// ZFS clones are cleaned up by their own drop impls.
if let BackingFile::HardCopy(path) = self {
debug!(%path, "deleting hard copy of guest disk image");
if let Err(e) = std::fs::remove_file(&path) {
error!(
?e,
%path,
"failed to delete hard copy of guest disk image"
);
}
}
}
}
/// An RAII wrapper for a disk wrapped by a file.
#[derive(Debug)]
pub struct FileBackedDisk {
/// The name to use for instance spec backends that refer to this disk.
backend_name: String,
/// The backing file for this disk.
file: BackingFile,
/// The kind of guest OS image this guest contains, or `None` if the disk
/// was not initialized from a guest OS artifact.
guest_os: Option<GuestOsKind>,
}
impl FileBackedDisk {
/// Creates a new file-backed disk whose initial contents are copied from
/// the specified artifact on the host file system.
pub(crate) fn new_from_artifact(
backend_name: String,
artifact_path: &impl AsRef<Utf8Path>,
data_dir: &impl AsRef<Utf8Path>,
guest_os: Option<GuestOsKind>,
) -> Result<Self, super::DiskError> {
let artifact = BackingFile::create_from_source(
artifact_path.as_ref(),
data_dir.as_ref(),
)?;
// Make sure the disk is writable (the artifact may have been
// read-only).
let disk_file = std::fs::File::open(artifact.path())?;
let mut permissions = disk_file.metadata()?.permissions();
// TODO: Clippy is upset that `set_readonly(false)` results in
// world-writable files on UNIX-like OSes. Suppress the lint for now
// until someone gets around to a more specific solution.
#[allow(clippy::permissions_set_readonly_false)]
permissions.set_readonly(false);
disk_file.set_permissions(permissions)?;
Ok(Self { backend_name, file: artifact, guest_os })
}
}
impl super::DiskConfig for FileBackedDisk {
fn backend_spec(&self) -> (String, StorageBackendV0) {
(
self.backend_name.clone(),
StorageBackendV0::File(FileStorageBackend {
path: self.file.path().to_string(),
readonly: false,
}),
)
}
fn guest_os(&self) -> Option<GuestOsKind> {
self.guest_os
}
}