Newer
Older
* Copyright (c) 2021-2022, Luca Fulchir <luker@fenrirproject.org>
*
* This file is part of dfim.
*
* dfim is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* dfim is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with dfim. If not, see <https://www.gnu.org/licenses/>.
*/
// we could use lifetimes here instead of copying stuff, but
// - we should have realatively low usage anyway
// - lifetimes can get messy
// - we don't have enough trgts to fully know how lifetimes will impact the
pub fn new(cfg: &trgt::gpt::GPT) -> Self {
impl GPT {
/// generate a standard config that can be checked easily
/// so that we can compare the config GPT with the device GPT
&self,
dev: &::udev::Device,
) -> Result<trgt::gpt::GPT, ::std::io::Error> {
let (min_lba, max_lba, disk_alignment) = {
// enclose this in a block just to drop the ::gpt::Disk early
// we now have to recalculate the partition actual size,
// and move them all to LBA
// create (but never write) a new GPT table
// and work with that to translate to LBA
let node = dev.devnode().ok_or(::std::io::Error::new(
::std::io::ErrorKind::Other,
"device does not have a node",
))?;
let gpt =
::gpt::GptConfig::new().writable(false).initialized(false);
u32,
::gpt::partition::Partition,
>::new();
disk.update_partitions(new_partitions.clone()).unwrap();
let disk_alignment = match disk.logical_block_size() {
::gpt::disk::LogicalBlockSize::Lb512 => 512,
::gpt::disk::LogicalBlockSize::Lb4096 => 4096,
};
let (min_lba, max_lba) =
disk.find_free_sectors().get(0).unwrap().clone();
(min_lba, max_lba, disk_alignment)
use trgt::gpt::{align_raw, Alignment, Index, PartFrom, PartTo};
// convert all to Index(LBA), Alignment(LBA) for ease of usage
for p in &mut ret.partitions {
p.from.lba_align(disk_alignment, min_lba, max_lba);
p.to.lba_align(disk_alignment, min_lba, max_lba);
let mut last_lba: u64 = min_lba - 1;
// now convert everything to
// PartFrom::Index(Index::LBA(_), Alignment/LBA(0))
p.from = match &p.from {
idx @ PartFrom::Index(_, _) => idx.clone(),
PartFrom::SkipFree(
Index::LBA(l),
Alignment::Alignment(Index::LBA(al)),
) => {
let start_aligned = align_raw(start_from, *al);
last_lba = start_aligned;
PartFrom::SkipFreePercent(
perc,
Alignment::Alignment(Index::LBA(al)),
) => {
let skip_lba: u64 = (((max_lba - last_lba) as f64) / 100.0
* perc)
.ceil() as u64;
let start_from = last_lba + 1 + skip_lba;
let start_aligned = align_raw(start_from, *al);
last_lba = start_aligned;
Alignment::Alignment(Index::LBA(0)),
)
}
_ => {
assert!(false, "GPT: from: not everything is LBA");
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"internal error: \"from\" should be LBA",
));
}
};
p.to = match &p.to {
idx @ PartTo::Index(_, _) => idx.clone(),
PartTo::Size(
Index::LBA(l),
Alignment::Alignment(Index::LBA(al)),
) => {
let up_to_aligned = align_raw(up_to, *al);
last_lba = up_to_aligned;
Alignment::Alignment(Index::LBA(0)),
)
}
PartTo::Leave(
Index::LBA(l),
Alignment::Alignment(Index::LBA(al)),
) => {
let up_to_aligned = align_raw(up_to, *al);
last_lba = up_to_aligned;
Alignment::Alignment(Index::LBA(0)),
)
}
PartTo::Percent(perc, Alignment::Alignment(Index::LBA(al))) => {
let size: u64 = (((max_lba - last_lba) as f64) / 100.0
* perc)
.ceil() as u64;
let up_to = last_lba + size;
let up_to_aligned = align_raw(up_to, *al);
last_lba = up_to_aligned;
Alignment::Alignment(Index::LBA(0)),
)
}
PartTo::LeavePercent(
perc,
Alignment::Alignment(Index::LBA(al)),
) => {
let size: u64 = (((max_lba - last_lba) as f64) / 100.0
* perc)
.ceil() as u64;
let up_to = max_lba - size;
let up_to_aligned = align_raw(up_to, *al);
last_lba = up_to_aligned;
Alignment::Alignment(Index::LBA(0)),
)
}
_ => {
assert!(false, "GPT: to: not everything is LBA");
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"internal error: \"to\" should be LBA",
));
}
};
if let PartFrom::Index(Index::LBA(l_f), _) = p.from {
if let PartTo::Index(Index::LBA(l_t), _) = p.to {
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"Wrong partition boundaries",
));
}
/// get the current device GPT. make it standard enough that we can
/// easily check with what comes from make_standard(dev)
fn get_current_conf(
dev: &::udev::Device,
) -> Result<trgt::gpt::GPT, ::std::io::Error> {
let gpt = ::gpt::GptConfig::new().writable(false);
let node = dev.devnode().ok_or(::std::io::Error::new(
::std::io::ErrorKind::Other,
"device does not have a node",
))?;
let disk = gpt.open(node)?;
let disk_header =
disk.primary_header().ok_or(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"Failed to retrieve GPT primary header",
))?;
let mut ret = trgt::gpt::GPT {
+ dev.syspath().to_str().unwrap(),
guid: trgt::gpt::GptUuid::UUID(disk.guid().clone()),
max_partitions: disk_header.part_size,
partitions: Vec::with_capacity(disk_header.num_parts as usize),
};
for (num, disk_p) in disk.partitions() {
use trgt::gpt::{
Alignment, Index, PartFrom, PartId, PartTo, PartUUID, Partition,
};
let p = Partition {
label: disk_p.name.clone(),
number: ::std::num::NonZeroU32::new(*num).ok_or(
::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"Found partition id 0 on drive",
),
)?,
part_type: PartId::UUID(
::uuid::Uuid::parse_str(&disk_p.part_type_guid.guid)
.unwrap(),
),
part_uuid: PartUUID::UUID(disk_p.part_guid),
flags: trgt::gpt::Flag::from_bits(disk_p.flags),
from: PartFrom::Index(
Index::LBA(disk_p.first_lba),
Alignment::Alignment(Index::LBA(0)),
),
to: PartTo::Index(
Index::LBA(disk_p.last_lba),
Alignment::Alignment(Index::LBA(0)),
),
target: trgt::TargetId::Id("DFIM generated None".to_owned()),
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
fn find_children(
&self,
cfg: &crate::config::trgt::gpt::GPT,
dev: &::udev::Device,
) -> Result<Vec<NextTarget>, ::std::io::Error> {
let mut scanner = ::udev::Enumerator::new()?;
scanner.match_parent(dev)?;
scanner.match_subsystem(
crate::config::device::DevType::Partition.to_str(),
)?;
let mut ret = Vec::with_capacity(cfg.partitions.len());
let children = scanner.scan_devices()?;
for child in children {
// these should be partitions of the disk
// ...but how do we distinguish one from the other?
match child.parent() {
None => continue,
Some(parent) => {
// I am unsure if we match also the children's children
// Therefore make sure we are
// really only looking at the first
// children of the dev we are given
if parent.syspath() != dev.syspath() {
continue;
}
}
}
if None == child.devnode() {
assert!(false, "We have a partition without a node?");
continue;
}
let partnum = match child.sysnum() {
None => {
assert!(false, "Partition without sys number?");
continue;
}
Some(partnum) => ::std::num::NonZeroU32::new(partnum as u32)
.ok_or(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"dev num 0 but we can't have partition 0",
))?,
};
let p = match cfg.partitions.iter().find(|x| x.number == partnum) {
Some(p) => p,
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"We tought we had partitions we don't have",
));
}
};
ret.push(NextTarget {
id: p.target.id().to_owned(),
dev: child,
});
}
Ok(ret)
}
_logger: &::slog::Logger,
_cfg: &crate::state::cfg::Cfg,
_cfg_dev: &crate::config::device::Device,
) -> Result<crate::config::trgt::Target, ::std::io::Error> {
let current = self.make_standard(dev)?;
Ok(crate::config::trgt::Target::GPT(current))
}
fn get_current(
&mut self,
_logger: &::slog::Logger,
_cfg: &crate::state::cfg::Cfg,
_cfg_dev: &crate::config::device::Device,
dev: &::udev::Device,
) -> Result<super::GetCurrentResult, ::std::io::Error> {
let current = GPT::get_current_conf(dev)?;
match self.0 == current {
true => Ok(super::GetCurrentResult {
status: CheckStatus::AlreadyApplied,
conf: crate::config::trgt::Target::GPT(current),
next: self.find_children(&self.0, dev)?,
}),
false => Ok(super::GetCurrentResult {
status: CheckStatus::CanBeApplied,
conf: crate::config::trgt::Target::GPT(current),
}
}
fn apply(
&mut self,
logger: &::slog::Logger,
cfg: &crate::state::cfg::Cfg,
cfg_dev: &crate::config::device::Device,
dev: &::udev::Device,
) -> Result<Vec<NextTarget>, ::std::io::Error> {
"device does not have a node",
))?;
if cfg_dev.only_if_empty {
if !crate::state::is_empty(dev)? {
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"GPT: can't apply: not empty disk",
));
}
}
let gptconf = ::gpt::GptConfig::new()
.writable(!cfg.main.dry_run)
.initialized(false);
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
let mut disk = gptconf.open(node)?;
if let trgt::gpt::GptUuid::UUID(disk_uuid) = to_be_applied.guid {
disk.update_guid(Some(disk_uuid))?;
}
let mut disk_parts = ::std::collections::BTreeMap::<
u32,
::gpt::partition::Partition,
>::new();
use trgt::gpt::{Index, PartFrom, PartId, PartTo, PartUUID};
for conf_p in to_be_applied.partitions.iter() {
let from_lba = match conf_p.from {
PartFrom::Index(Index::LBA(from_lba), _) => from_lba,
_ => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"GPT: nonstandard standard partition",
));
}
};
let to_lba = match conf_p.to {
PartTo::Index(Index::LBA(to_lba), _) => to_lba,
_ => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"GPT: nonstandard standard partition",
));
}
};
let type_uuid = match conf_p.part_type {
PartId::UUID(type_uuid) => type_uuid,
_ => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"GPT: nonstandard standard partition",
));
}
};
let part_uuid = match conf_p.part_uuid {
PartUUID::UUID(part_uuid) => part_uuid.clone(),
PartUUID::Random => ::uuid::Uuid::new_v4(),
};
let p = ::gpt::partition::Partition {
part_type_guid: ::gpt::partition_types::Type::from_uuid(
&type_uuid,
)
.unwrap(),
part_guid: part_uuid,
first_lba: from_lba,
last_lba: to_lba,
disk_parts.insert(conf_p.number.get(), p);
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
match disk.update_partitions_embedded(disk_parts, self.0.max_partitions)
{
Ok(_) => {}
Err(e) => {
::slog::error!(
logger,
"GPT: target {}: wrong partition definition?",
self.0.id,
);
return Err(e);
}
}
match cfg.main.dry_run {
true => {
::slog::info!(
logger,
"DRY RUN: GPT: refusing to write target {} on drive {:?}",
self.0.id,
dev.syspath()
);
return Ok(Vec::new());
}
false => {
match disk.write() {
Err(e) => {
::slog::debug!(
logger,
"GPT: error writing table: {:?}",
e
);
return Err(e);
}
Ok(_) => {}
}
use std::os::unix::io::AsRawFd;
Ok(disk_file) => disk_file,
Err(e) => {
::slog::debug!(
logger,
"GPT: can't reload partition table: {:?}",
e
);
return Err(e);
}
};
// reread partition table
// include/uapi/linux/fs.h: #define BLKRRPART _IO(0x12,95)
::nix::ioctl_none!(reread_partition_table, 0x12, 95);
unsafe {
if reread_partition_table(fd) != Ok(0) {
::slog::error!(
logger,
"Partition table reload failed. Trying to \
continue..."
);
}
self.find_children(&to_be_applied, dev)