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 libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
use crate::update::kernel::{KernelInstaller, KernelVersion}; use crate::update::kernel::{KernelInstaller, KernelVersion};
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::DirEntry; use std::fs::{DirEntry, File};
use std::io;
use tempfile::Builder;
mod kernel; mod kernel;
@ -11,6 +13,9 @@ const FLAG_SKIP_SHA: u32 = 0x01;
const FLAG_NO_PREFER: u32 = 0x02; const FLAG_NO_PREFER: u32 = 0x02;
const FLAG_QUIET: u32 = 0x04; const FLAG_QUIET: u32 = 0x04;
const RESOURCES_DIRECTORY: &str = "/storage/resources";
const TEMP_DIRECTORY: &str = "/storage/resources/tmp";
pub fn main(args: Vec<String>) { pub fn main(args: Vec<String>) {
let mut args = args.iter().skip(1); let mut args = args.iter().skip(1);
let mut flags = 0; let mut flags = 0;
@ -42,14 +47,14 @@ pub fn main(args: Vec<String>) {
// Search directory containing installed image files for an // Search directory containing installed image files for an
// image file that has an identical shasum and abort the installation // image file that has an identical shasum and abort the installation
// if a duplicate is found. // if a duplicate is found.
fn detect_duplicates(image: &ResourceImage) -> Result<()> { fn detect_duplicates(header: &ImageHeader) -> Result<()> {
let metainfo = image.metainfo(); let metainfo = header.metainfo();
let channel = metainfo.channel(); let channel = metainfo.channel();
let shasum = metainfo.shasum(); let shasum = metainfo.shasum();
validate_channel_name(&channel)?; validate_channel_name(&channel)?;
let resource_dir = Path::new("/storage/resources/") let resource_dir = Path::new(RESOURCES_DIRECTORY)
.join(channel); .join(channel);
if !resource_dir.exists() { if !resource_dir.exists() {
@ -57,29 +62,58 @@ fn detect_duplicates(image: &ResourceImage) -> Result<()> {
} }
util::read_directory(&resource_dir, |dent| { util::read_directory(&resource_dir, |dent| {
match ResourceImage::from_path(dent.path()) { if let Ok(hdr) = ImageHeader::from_file(dent.path()) {
Ok(img) => if img.metainfo().shasum() == shasum { if hdr.metainfo().shasum() == shasum {
bail!("A duplicate image file with the same shasum already exists at {}", img.path().display()); bail!("A duplicate image file with the same shasum already exists at {}", dent.path().display());
}, }
Err(err) => warn!("{}", err),
} }
Ok(()) Ok(())
}) })
} }
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)
}
fn install_image(path: &Path, flags: u32) -> Result<()> { 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()); 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)?; prepare_image(&image, flags)?;
match image.metainfo().image_type() { match image.metainfo().image_type() {
"kernel" => install_kernel_image(&mut image), "kernel" => install_kernel_image(&mut image),
"extra" => install_extra_image(&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), 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)?; image.write_to_partition(&partition)?;
info!("Image written to {:?}", partition.path()); info!("Image written to {:?}", partition.path());
util::remove_file(image.path())?;
Ok(()) Ok(())
} }

View File

@ -1,6 +1,5 @@
use std::fs::{File,DirEntry}; 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 std::path::{Path, PathBuf};
use crate::{Result, CommandLine, OsRelease, ImageHeader, MetaInfo, Partition, Mounts, util, LoopDevice}; use crate::{Result, CommandLine, OsRelease, ImageHeader, MetaInfo, Partition, Mounts, util, LoopDevice};
@ -73,12 +72,16 @@ impl ResourceImage {
} }
} }
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().display());
}
Ok(Self::new(path.as_ref(), header))
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> { pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let header = ImageHeader::from_file(path.as_ref())?; let header = ImageHeader::from_file(path.as_ref())?;
if !header.is_magic_valid() { Self::from_header(header, path)
bail!("image file {:?} does not have a valid header", path.as_ref());
}
Ok(Self::new(path.as_ref(), header ))
} }
pub fn is_valid_image(&self) -> bool { pub fn is_valid_image(&self) -> bool {
@ -103,8 +106,6 @@ impl ResourceImage {
} }
fn new(path: &Path, header: ImageHeader) -> Self { fn new(path: &Path, header: ImageHeader) -> Self {
assert_eq!(path.extension(), Some(OsStr::new("img")), "image filename must have .img extension");
ResourceImage { ResourceImage {
path: path.to_owned(), path: path.to_owned(),
header, header,
@ -485,15 +486,19 @@ fn maybe_add_dir_entry(entry: &DirEntry,
images: &mut Vec<ResourceImage>) -> Result<()> { images: &mut Vec<ResourceImage>) -> Result<()> {
let path = entry.path(); let path = entry.path();
if Some(OsStr::new("img")) != path.extension() {
return Ok(())
}
let meta = entry.metadata() let meta = entry.metadata()
.map_err(context!("failed to read metadata for {:?}", entry.path()))?; .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(()) 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() { if !header.is_magic_valid() {
return Ok(()) return Ok(())
} }