diff --git a/citadel-tool/src/update/mod.rs b/citadel-tool/src/update/mod.rs index fb0333f..4e992e9 100644 --- a/citadel-tool/src/update/mod.rs +++ b/citadel-tool/src/update/mod.rs @@ -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) { let mut args = args.iter().skip(1); let mut flags = 0; @@ -42,14 +47,14 @@ pub fn main(args: Vec) { // 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,29 +62,58 @@ 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 create_tmp_copy(path: &Path) -> Result { + 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) +} + fn install_image(path: &Path, flags: u32) -> Result<()> { - if !path.exists() { + 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)?; - let mut image = ResourceImage::from_path(path)?; - detect_duplicates(&image)?; prepare_image(&image, flags)?; match image.metainfo().image_type() { "kernel" => install_kernel_image(&mut image), "extra" => install_extra_image(&image), - "rootfs" => install_rootfs_image(&image, flags), + "rootfs" => install_rootfs_image(&image, flags), image_type => bail!("Unknown image type: {}", 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(()) } diff --git a/libcitadel/src/resource.rs b/libcitadel/src/resource.rs index 151b2a9..e1d0081 100644 --- a/libcitadel/src/resource.rs +++ b/libcitadel/src/resource.rs @@ -1,6 +1,5 @@ use std::fs::{File,DirEntry}; -use std::ffi::OsStr; -use std::io::{self,Seek,SeekFrom}; +use std::io::{self, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use crate::{Result, CommandLine, OsRelease, ImageHeader, MetaInfo, Partition, Mounts, util, LoopDevice}; @@ -73,12 +72,16 @@ impl ResourceImage { } } + pub fn from_header>(header: ImageHeader, path: P) -> Result { + if !header.is_magic_valid() { + bail!("image file {} does not have a valid header", path.as_ref().display()); + } + Ok(Self::new(path.as_ref(), header)) + } + pub fn from_path>(path: P) -> Result { let header = ImageHeader::from_file(path.as_ref())?; - if !header.is_magic_valid() { - bail!("image file {:?} does not have a valid header", path.as_ref()); - } - Ok(Self::new(path.as_ref(), header )) + Self::from_header(header, path) } pub fn is_valid_image(&self) -> bool { @@ -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) -> 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(()) }