/* * Copyright (c) 2021-2022, Luca Fulchir * * 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 . */ pub mod cfg; pub mod s_trgt; use super::config; use ::std::io::Read; use ::std::io::Seek; use s_trgt::CheckStatus; /// Check if a device is empty. Meaning: no gpt/mbr, first and last MB all zeros pub fn is_empty(dev: &::udev::Device) -> Result { let node = match dev.devnode() { Some(node) => node, None => { return Err(::std::io::Error::new( ::std::io::ErrorKind::NotFound, "device does not have a node", )); } }; // MBR/GPT have headers in the first/last MB, // so checking those will guarantee us that there is no MBR/GPT let mut device = ::std::fs::File::open(node)?; // 1MB buffer let mut buffer = Vec::::with_capacity(1024 * 1024); unsafe { buffer.set_len(buffer.capacity()) } let bytes = device.read(&mut buffer[..])?; if buffer[..bytes].iter().any(|&x| x != 0) { return Ok(false); } // check the end of file device.seek(::std::io::SeekFrom::End(0))?; let max_size = device.stream_position()?; if max_size > 1024 * 1024 { // else we have already checked it let to_end = ::std::cmp::max(max_size - 1024 * 1024, 1024 * 1024); device.seek(::std::io::SeekFrom::Start(to_end))?; let bytes = device.read(&mut buffer[..])?; if buffer[..bytes].iter().any(|&x| x != 0) { return Ok(false); } } Ok(true) } pub fn get( logger: &::slog::Logger, cfg: &cfg::Cfg, cfg_dev: &config::device::Device, cfg_target_id: &str, dev: &::udev::Device, ) -> Result { // analyze a device, get the partition table and filesystem // to check if the current state and the destination coincide use crate::config::trgt::TargetCommon; use crate::state::s_trgt::TargetApply; let mut queue = Vec::::new(); queue.push(s_trgt::NextTarget { id: cfg_target_id.to_owned(), dev: dev.clone(), }); let mut return_status = CheckStatus::DoNotApply; while queue.len() > 0 { let work = queue.pop().unwrap(); let nonstandard_target = match cfg.targets.iter().find(|x| x.id() == work.id) { Some(nonstandard_target) => nonstandard_target, None => { assert!(false, "A target returned a non-existant id"); continue; } }; match is_empty(&work.dev) { Ok(true) => { return_status = match return_status { CheckStatus::DoNotApply => return Ok(return_status), CheckStatus::AlreadyApplied | CheckStatus::CanBeApplied => { CheckStatus::CanBeApplied } }; // empty device, no children to add to queue continue; } _ => {} } let mut check_target: s_trgt::Target = nonstandard_target.into(); let dev_conf = match check_target.dev_to_config(logger, cfg, cfg_dev, &work.dev) { Ok(dev_conf) => dev_conf, Err(e) => { ::slog::warn!( logger, "Failed dev_to_conf, target {}: {:?}", nonstandard_target.id(), e ); return Err(e); } }; let mut standard_target: s_trgt::Target = (&dev_conf).into(); let mut current_state = standard_target.get_current(logger, cfg, cfg_dev, &work.dev)?; match current_state.status { CheckStatus::DoNotApply => return Ok(CheckStatus::DoNotApply), CheckStatus::CanBeApplied => { return_status = CheckStatus::CanBeApplied } CheckStatus::AlreadyApplied => { queue.append(&mut current_state.next); } } } Ok(return_status) } pub fn set( logger: &::slog::Logger, cfg: &cfg::Cfg, cfg_dev: &config::device::Device, cfg_target_id: &str, dev: &::udev::Device, ) -> Result<(), ::std::io::Error> { use crate::config::trgt::TargetCommon; use crate::state::s_trgt::NextTarget; use crate::state::s_trgt::TargetApply; let mut queue = Vec::::new(); queue.push(NextTarget { id: cfg_target_id.to_owned(), dev: dev.clone(), }); while queue.len() > 0 { let work = queue.pop().unwrap(); let cfg_target = match cfg.targets.iter().find(|x| x.id() == work.id) { Some(cfg_target) => cfg_target, None => { assert!(false, "A target returned a non-existant id"); continue; } }; let mut apply_target: s_trgt::Target = cfg_target.into(); match apply_target.apply(logger, cfg, cfg_dev, &work.dev) { Ok(mut next) => { ::slog::debug!( logger, "Target {} successfully applied", cfg_target.id(), ); queue.append(&mut next); } Err(e) => { ::slog::warn!( logger, "Failed to apply target {}: {:?}", cfg_target.id(), e ); return Err(e); } }; } Ok(()) }