-
Notifications
You must be signed in to change notification settings - Fork 40
/
backing_fs.rs
178 lines (152 loc) · 5.99 KB
/
backing_fs.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// 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/.
//! Operations for dealing with persistent backing mounts for OS data
// On Oxide hardware, the root filesystem is backed by a ramdisk and
// non-persistent. However, there are several things within the root filesystem
// which are useful to preserve across reboots, and these are backed persistent
// datasets on the boot disk.
//
// Each boot disk contains a dataset sled_hardware::disk::M2_BACKING_DATASET
// and for each backing mount, a child dataset is created under there that
// is configured with the desired mountpoint in the root filesystem. Since
// there are multiple disks which can be used to boot, these datasets are also
// marked with the "canmount=noauto" attribute so that they do not all try to
// mount automatically and race -- only one could ever succeed. This allows us
// to come along later and specifically mount the one that we want (the one from
// the current boot disk) and also perform an overlay mount so that it succeeds
// even if there is content from the ramdisk image or early boot services
// present underneath. The overlay mount action is optionally bracketed with a
// service stop/start.
use camino::Utf8PathBuf;
use illumos_utils::zfs::{
EnsureFilesystemError, GetValueError, Mountpoint, SizeDetails, Zfs,
};
#[derive(Debug, thiserror::Error)]
pub enum BackingFsError {
#[error("Error administering service: {0}")]
Adm(#[from] smf::AdmError),
#[error("Error retrieving dataset property: {0}")]
DatasetProperty(#[from] GetValueError),
#[error("Error initializing dataset: {0}")]
Mount(#[from] EnsureFilesystemError),
}
struct BackingFs {
// Dataset name
name: &'static str,
// Mountpoint
mountpoint: &'static str,
// Optional quota, in _bytes_
quota: Option<usize>,
// Optional compression mode
compression: Option<&'static str>,
// Linked service
service: Option<&'static str>,
}
impl BackingFs {
const fn new(name: &'static str) -> Self {
Self {
name,
mountpoint: "legacy",
quota: None,
compression: None,
service: None,
}
}
const fn mountpoint(mut self, mountpoint: &'static str) -> Self {
self.mountpoint = mountpoint;
self
}
const fn quota(mut self, quota: usize) -> Self {
self.quota = Some(quota);
self
}
const fn compression(mut self, compression: &'static str) -> Self {
self.compression = Some(compression);
self
}
const fn service(mut self, service: &'static str) -> Self {
self.service = Some(service);
self
}
}
const BACKING_FMD_DATASET: &'static str = "fmd";
const BACKING_FMD_MOUNTPOINT: &'static str = "/var/fm/fmd";
const BACKING_FMD_SERVICE: &'static str = "svc:/system/fmd:default";
const BACKING_FMD_QUOTA: usize = 500 * (1 << 20); // 500 MiB
const BACKING_COMPRESSION: &'static str = "on";
const BACKINGFS_COUNT: usize = 1;
static BACKINGFS: [BackingFs; BACKINGFS_COUNT] =
[BackingFs::new(BACKING_FMD_DATASET)
.mountpoint(BACKING_FMD_MOUNTPOINT)
.quota(BACKING_FMD_QUOTA)
.compression(BACKING_COMPRESSION)
.service(BACKING_FMD_SERVICE)];
/// Ensure that the backing filesystems are mounted.
/// If the underlying dataset for a backing fs does not exist on the specified
/// boot disk then it will be created.
pub(crate) fn ensure_backing_fs(
log: &slog::Logger,
boot_zpool_name: &illumos_utils::zpool::ZpoolName,
) -> Result<(), BackingFsError> {
let log = log.new(o!(
"component" => "BackingFs",
));
for bfs in BACKINGFS.iter() {
info!(log, "Processing {}", bfs.name);
let dataset = format!(
"{}/{}/{}",
boot_zpool_name,
sled_hardware::disk::M2_BACKING_DATASET,
bfs.name
);
let mountpoint = Mountpoint::Path(Utf8PathBuf::from(bfs.mountpoint));
info!(log, "Ensuring dataset {}", dataset);
let size_details = Some(SizeDetails {
quota: bfs.quota,
compression: bfs.compression,
});
Zfs::ensure_filesystem(
&dataset,
mountpoint.clone(),
false, // zoned
true, // do_format
None, // encryption_details,
size_details,
Some(vec!["canmount=noauto".to_string()]), // options
)?;
// Check if a ZFS filesystem is already mounted on bfs.mountpoint by
// retrieving the ZFS `mountpoint` property and comparing it. This
// might seem counter-intuitive but if there is a filesystem mounted
// there, its mountpoint will match, and if not then we will retrieve
// the mountpoint of a higher level filesystem, such as '/'. If we
// can't retrieve the property at all, then there is definitely no ZFS
// filesystem mounted there - most likely we are running with a non-ZFS
// root, such as when net booted during CI.
if Zfs::get_value(&bfs.mountpoint, "mountpoint")
.unwrap_or("not-zfs".to_string())
== bfs.mountpoint
{
info!(log, "{} is already mounted", bfs.mountpoint);
return Ok(());
}
if let Some(service) = bfs.service {
info!(log, "Stopping service {}", service);
smf::Adm::new()
.disable()
.temporary()
.synchronous()
.run(smf::AdmSelection::ByPattern(&[service]))?;
}
info!(log, "Mounting {} on {}", dataset, mountpoint);
Zfs::mount_overlay_dataset(&dataset, &mountpoint)?;
if let Some(service) = bfs.service {
info!(log, "Starting service {}", service);
smf::Adm::new()
.enable()
.synchronous()
.run(smf::AdmSelection::ByPattern(&[service]))?;
}
}
Ok(())
}