Newer
Older
/*
* Copyright (c) 2021, 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/>.
*/
use super::{CheckResult, CheckStatus, NextTarget};
// 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
fn generate_standard(
&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);
let mut disk = gpt.open(node)?;
let mut new_partitions = ::std::collections::BTreeMap::<
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)
align_raw, Alignment, Index, PartFrom, PartId, PartTo, PartType,
PartUUID, Partition,
// 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
fn get_current(
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 {
id: "DFIM generated for {}".to_owned()
+ 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: *num,
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()),
250
251
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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) => partnum as u32,
};
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)
}
impl super::TargetApply for GPT {
fn check(
&mut self,
logger: &::slog::Logger,
cfg: &crate::state::cfg::Cfg,
dev: &::udev::Device,
) -> Result<CheckResult, ::std::io::Error> {
if let Ok(true) = crate::state::is_empty(dev) {
return Ok(CheckResult {
status: CheckStatus::CanBeApplied,
next: Vec::new(),
});
}
let to_be_applied = self.generate_standard(dev)?;
let current_gpt = match GPT::get_current(dev) {
Ok(current_gpt) => current_gpt,
Err(e) => {
::slog::debug!(logger, "GPT load error: {}", e);
status: if cfg_dev.only_if_empty {
CheckStatus::DoNotApply
} else {
CheckStatus::CanBeApplied
},
::slog::debug!(
logger,
"GPT: device {:?} has different partitions than target {}",
dev.syspath(),
self.0.id
);
status: if cfg_dev.only_if_empty {
CheckStatus::DoNotApply
} else {
CheckStatus::CanBeApplied
},
let next_targets = self.find_children(&to_be_applied, dev)?;
}
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 to_be_applied = self.generate_standard(dev)?;
let gptconf = ::gpt::GptConfig::new()
.writable(!cfg.main.dry_run)
.initialized(false);
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
423
424
425
426
427
428
429
430
431
432
433
434
435
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,
name: conf_p.label.clone(),
};
disk_parts.insert(conf_p.number, p);
}
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
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;
let mut disk_file = match ::std::fs::File::open(node) {
Ok(disk_file) => disk_file,
Err(e) => {
::slog::debug!(
logger,
"GPT: can't reload partition table: {:?}",
e
);
return Err(e);
}
};
let fd = unsafe { disk_file.as_raw_fd() };
// reread partition table
// include/uapi/linux/fs.h: #define BLKRRPART _IO(0x12,95)
::nix::ioctl_none!(reread_partition_table, 0x12, 95);
unsafe {
reread_partition_table(fd);
}
self.find_children(&to_be_applied, dev)