4
4
5
5
//! Abstractions for disks with a raw file backend.
6
6
7
- use std:: path:: { Path , PathBuf } ;
8
-
7
+ use camino:: { Utf8Path , Utf8PathBuf } ;
9
8
use propolis_client:: types:: { FileStorageBackend , StorageBackendV0 } ;
10
- use tracing:: { error, info } ;
9
+ use tracing:: { debug , error, warn } ;
11
10
use uuid:: Uuid ;
12
11
13
- use crate :: guest_os:: GuestOsKind ;
12
+ use crate :: { guest_os:: GuestOsKind , zfs:: ClonedFile as ZfsClonedFile } ;
13
+
14
+ /// Describes the method used to create the backing file for a file-backed disk.
15
+ #[ derive( Debug ) ]
16
+ enum BackingFile {
17
+ /// The disk is a ZFS clone of the original artifact.
18
+ Zfs ( ZfsClonedFile ) ,
19
+
20
+ /// The disk is a hard copy of the original artifact.
21
+ HardCopy ( Utf8PathBuf ) ,
22
+ }
23
+
24
+ impl BackingFile {
25
+ /// Creates a new backing file from the artifact with the supplied
26
+ /// `artifact_path`. If possible, this routine will create a ZFS clone of
27
+ /// the dataset containing the file; otherwise it will fall back to creating
28
+ /// a hard copy of the original artifact.
29
+ fn create_from_source (
30
+ artifact_path : & Utf8Path ,
31
+ data_dir : & Utf8Path ,
32
+ ) -> anyhow:: Result < Self > {
33
+ match ZfsClonedFile :: create_from_path ( artifact_path) {
34
+ Ok ( file) => return Ok ( Self :: Zfs ( file) ) ,
35
+ Err ( error) => warn ! (
36
+ %artifact_path,
37
+ %error,
38
+ "failed to make ZFS clone of backing artifact, will copy it"
39
+ ) ,
40
+ }
41
+
42
+ let mut disk_path = data_dir. to_path_buf ( ) ;
43
+ disk_path. push ( format ! ( "{}.phd_disk" , Uuid :: new_v4( ) ) ) ;
44
+ debug ! (
45
+ source = %artifact_path,
46
+ disk_path = %disk_path,
47
+ "Copying source image to create temporary disk" ,
48
+ ) ;
49
+
50
+ std:: fs:: copy ( artifact_path, & disk_path) ?;
51
+ Ok ( Self :: HardCopy ( disk_path) )
52
+ }
53
+
54
+ /// Yields the path to this disk's backing file.
55
+ fn path ( & self ) -> Utf8PathBuf {
56
+ match self {
57
+ BackingFile :: Zfs ( zfs) => zfs. path ( ) ,
58
+ BackingFile :: HardCopy ( path) => path. clone ( ) ,
59
+ }
60
+ }
61
+ }
62
+
63
+ impl Drop for BackingFile {
64
+ fn drop ( & mut self ) {
65
+ // ZFS clones are cleaned up by their own drop impls.
66
+ if let BackingFile :: HardCopy ( path) = self {
67
+ debug ! ( %path, "deleting hard copy of guest disk image" ) ;
68
+ if let Err ( e) = std:: fs:: remove_file ( & path) {
69
+ error ! (
70
+ ?e,
71
+ %path,
72
+ "failed to delete hard copy of guest disk image"
73
+ ) ;
74
+ }
75
+ }
76
+ }
77
+ }
14
78
15
79
/// An RAII wrapper for a disk wrapped by a file.
16
80
#[ derive( Debug ) ]
17
81
pub struct FileBackedDisk {
18
82
/// The name to use for instance spec backends that refer to this disk.
19
83
backend_name : String ,
20
84
21
- /// The path at which the disk is stored .
22
- disk_path : PathBuf ,
85
+ /// The backing file for this disk.
86
+ file : BackingFile ,
23
87
24
88
/// The kind of guest OS image this guest contains, or `None` if the disk
25
89
/// was not initialized from a guest OS artifact.
@@ -31,23 +95,18 @@ impl FileBackedDisk {
31
95
/// the specified artifact on the host file system.
32
96
pub ( crate ) fn new_from_artifact (
33
97
backend_name : String ,
34
- artifact_path : & impl AsRef < Path > ,
35
- data_dir : & impl AsRef < Path > ,
98
+ artifact_path : & impl AsRef < Utf8Path > ,
99
+ data_dir : & impl AsRef < Utf8Path > ,
36
100
guest_os : Option < GuestOsKind > ,
37
101
) -> Result < Self , super :: DiskError > {
38
- let mut disk_path = data_dir. as_ref ( ) . to_path_buf ( ) ;
39
- disk_path. push ( format ! ( "{}.phd_disk" , Uuid :: new_v4( ) ) ) ;
40
- info ! (
41
- source = %artifact_path. as_ref( ) . display( ) ,
42
- disk_path = %disk_path. display( ) ,
43
- "Copying source image to create temporary disk" ,
44
- ) ;
45
-
46
- std:: fs:: copy ( artifact_path, & disk_path) ?;
102
+ let artifact = BackingFile :: create_from_source (
103
+ artifact_path. as_ref ( ) ,
104
+ data_dir. as_ref ( ) ,
105
+ ) ?;
47
106
48
107
// Make sure the disk is writable (the artifact may have been
49
108
// read-only).
50
- let disk_file = std:: fs:: File :: open ( & disk_path ) ?;
109
+ let disk_file = std:: fs:: File :: open ( artifact . path ( ) ) ?;
51
110
let mut permissions = disk_file. metadata ( ) ?. permissions ( ) ;
52
111
53
112
// TODO: Clippy is upset that `set_readonly(false)` results in
@@ -57,7 +116,7 @@ impl FileBackedDisk {
57
116
permissions. set_readonly ( false ) ;
58
117
disk_file. set_permissions ( permissions) ?;
59
118
60
- Ok ( Self { backend_name, disk_path , guest_os } )
119
+ Ok ( Self { backend_name, file : artifact , guest_os } )
61
120
}
62
121
}
63
122
@@ -66,7 +125,7 @@ impl super::DiskConfig for FileBackedDisk {
66
125
(
67
126
self . backend_name . clone ( ) ,
68
127
StorageBackendV0 :: File ( FileStorageBackend {
69
- path : self . disk_path . to_string_lossy ( ) . to_string ( ) ,
128
+ path : self . file . path ( ) . to_string ( ) ,
70
129
readonly : false ,
71
130
} ) ,
72
131
)
@@ -76,14 +135,3 @@ impl super::DiskConfig for FileBackedDisk {
76
135
self . guest_os
77
136
}
78
137
}
79
-
80
- impl Drop for FileBackedDisk {
81
- fn drop ( & mut self ) {
82
- info ! ( path = %self . disk_path. display( ) , "Deleting guest disk image" ) ;
83
- if let Err ( e) = std:: fs:: remove_file ( & self . disk_path ) {
84
- error ! ( ?e,
85
- path = %self . disk_path. display( ) ,
86
- "Failed to delete guest disk image" ) ;
87
- }
88
- }
89
- }
0 commit comments