citadel-update: copy image files to tmp directory before updating

This commit is contained in:
Bruce Leidl 2021-12-16 16:22:30 -05:00
parent 12eed4d557
commit 668227af1e
2 changed files with 66 additions and 26 deletions

View File

@ -3,7 +3,9 @@ use std::path::{Path, PathBuf};
use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
use crate::update::kernel::{KernelInstaller, KernelVersion};
use std::collections::HashSet;
use std::fs::DirEntry;
use std::fs::{DirEntry, File};
use std::io;
use tempfile::Builder;
mod kernel;
@ -11,6 +13,9 @@ const FLAG_SKIP_SHA: u32 = 0x01;
const FLAG_NO_PREFER: u32 = 0x02;
const FLAG_QUIET: u32 = 0x04;
const RESOURCES_DIRECTORY: &str = "/storage/resources";
const TEMP_DIRECTORY: &str = "/storage/resources/tmp";
pub fn main(args: Vec<String>) {
let mut args = args.iter().skip(1);
let mut flags = 0;
@ -42,14 +47,14 @@ pub fn main(args: Vec<String>) {
// Search directory containing installed image files for an
// image file that has an identical shasum and abort the installation
// if a duplicate is found.
fn detect_duplicates(image: &ResourceImage) -> Result<()> {
let metainfo = image.metainfo();
fn detect_duplicates(header: &ImageHeader) -> Result<()> {
let metainfo = header.metainfo();
let channel = metainfo.channel();
let shasum = metainfo.shasum();
validate_channel_name(&channel)?;
let resource_dir = Path::new("/storage/resources/")
let resource_dir = Path::new(RESOURCES_DIRECTORY)
.join(channel);
if !resource_dir.exists() {
@ -57,23 +62,52 @@ fn detect_duplicates(image: &ResourceImage) -> Result<()> {
}
util::read_directory(&resource_dir, |dent| {
match ResourceImage::from_path(dent.path()) {
Ok(img) => if img.metainfo().shasum() == shasum {
bail!("A duplicate image file with the same shasum already exists at {}", img.path().display());
},
Err(err) => warn!("{}", err),
if let Ok(hdr) = ImageHeader::from_file(dent.path()) {
if hdr.metainfo().shasum() == shasum {
bail!("A duplicate image file with the same shasum already exists at {}", dent.path().display());
}
}
Ok(())
})
}
fn install_image(path: &Path, flags: u32) -> Result<()> {
if !path.exists() {
bail!("file path {} does not exist", path.display());
fn create_tmp_copy(path: &Path) -> Result<PathBuf> {
if !Path::new(TEMP_DIRECTORY).exists() {
util::create_dir(TEMP_DIRECTORY)?;
}
let mut tmpfile = Builder::new()
.prefix("update-")
.suffix(".img")
.tempfile_in(TEMP_DIRECTORY)
.map_err(context!("Failed to open temporary file in {}", TEMP_DIRECTORY))?;
let mut src = File::open(path)
.map_err(context!("Failed to open image file {}", path.display()))?;
info!("Copying image to temporary file {}", tmpfile.path().display());
io::copy(&mut src, tmpfile.as_file_mut())
.map_err(context!("Failed copying image file to temporary file"))?;
let (_,path) = tmpfile
.keep().map_err(context!("Failed to persist temporary file"))?;
Ok(path)
}
let mut image = ResourceImage::from_path(path)?;
detect_duplicates(&image)?;
fn install_image(path: &Path, flags: u32) -> Result<()> {
if !path.exists() || path.file_name().is_none() {
bail!("file path {} does not exist", path.display());
}
if !util::is_euid_root() {
bail!("Image updates must be installed by root user");
}
let header = ImageHeader::from_file(path)?;
detect_duplicates(&header)?;
let tmpfile = create_tmp_copy(path)?;
let mut image = ResourceImage::from_header(header, tmpfile)?;
prepare_image(&image, flags)?;
match image.metainfo().image_type() {
@ -277,6 +311,7 @@ fn install_rootfs_image(image: &ResourceImage, flags: u32) -> Result<()> {
image.write_to_partition(&partition)?;
info!("Image written to {:?}", partition.path());
util::remove_file(image.path())?;
Ok(())
}

View File

@ -1,5 +1,4 @@
use std::fs::{File,DirEntry};
use std::ffi::OsStr;
use std::io::{self, Seek, SeekFrom};
use std::path::{Path, PathBuf};
@ -73,14 +72,18 @@ impl ResourceImage {
}
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let header = ImageHeader::from_file(path.as_ref())?;
pub fn from_header<P: AsRef<Path>>(header: ImageHeader, path: P) -> Result<Self> {
if !header.is_magic_valid() {
bail!("image file {:?} does not have a valid header", path.as_ref());
bail!("image file {} does not have a valid header", path.as_ref().display());
}
Ok(Self::new(path.as_ref(), header))
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let header = ImageHeader::from_file(path.as_ref())?;
Self::from_header(header, path)
}
pub fn is_valid_image(&self) -> bool {
self.header.is_magic_valid()
}
@ -103,8 +106,6 @@ impl ResourceImage {
}
fn new(path: &Path, header: ImageHeader) -> Self {
assert_eq!(path.extension(), Some(OsStr::new("img")), "image filename must have .img extension");
ResourceImage {
path: path.to_owned(),
header,
@ -485,15 +486,19 @@ fn maybe_add_dir_entry(entry: &DirEntry,
images: &mut Vec<ResourceImage>) -> Result<()> {
let path = entry.path();
if Some(OsStr::new("img")) != path.extension() {
return Ok(())
}
let meta = entry.metadata()
.map_err(context!("failed to read metadata for {:?}", entry.path()))?;
if meta.len() < ImageHeader::HEADER_SIZE as u64 {
if !meta.is_file() || meta.len() < ImageHeader::HEADER_SIZE as u64 {
return Ok(())
}
let header = ImageHeader::from_file(&path)?;
let header = match ImageHeader::from_file(&path) {
Ok(header) => header,
Err(err) => {
warn!("Unable to read image header from directory entry {}: {}", path.display(), err);
return Ok(())
}
};
if !header.is_magic_valid() {
return Ok(())
}