-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow logging to internal disk as a workaround
The recommended way to persistent logging during the takeover process is to provide an external disk or internal disk that's not the target for balenaOS. This PR adds a mechanism that keeps the log in RAM and then dumps the logs to the disk running the os. This can be helpful when running a takeover remotely. - Adds a new CLI option `--log-to-balenaos`. This is not compatible with other logging options takeover provides - Dumps partial logs at captured if the takeover process fails at any point during the different stages This mechanism is being added to allow running as part of a balenaOS to balenaOS update whereby the disk partition layout changes across OS versions. Change-type: minor Signed-off-by: Rahul Thakoor <[email protected]>
- Loading branch information
1 parent
beb21a1
commit 7787d68
Showing
7 changed files
with
400 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
use log::{error, info}; | ||
use std::fs::{self, OpenOptions}; | ||
use std::{ | ||
fs::copy, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use crate::common::defs::BALENA_DATA_MP; | ||
use crate::common::{path_append, Error, ToError}; | ||
use crate::{ | ||
common::{ | ||
debug, | ||
defs::{BALENA_DATA_FSTYPE, NIX_NONE}, | ||
disk_util::DEF_BLOCK_SIZE, | ||
error::Result, | ||
loop_device::LoopDevice, | ||
ErrorKind, | ||
}, | ||
stage2::get_partition_infos, | ||
}; | ||
|
||
use nix::{ | ||
mount::{mount, umount, MsFlags}, | ||
unistd::sync, | ||
}; | ||
|
||
use self::stage2_config::Stage2Config; | ||
|
||
use super::{ | ||
defs::{BALENA_PART_MP, OLD_ROOT_MP}, | ||
stage2_config, | ||
}; | ||
|
||
pub const LOG_CONSOLIDATED_TMPFS: &str = "/tmp/consolidated.log"; | ||
pub const FALLBACK_LOG_DEST_DIR: &str = "/balenahup"; | ||
|
||
fn create_dir_if_not_exist(path: &str) -> Result<()> { | ||
let dir_path = Path::new(path); | ||
if !dir_path.is_dir() { | ||
println!("Directory does not exist. Creating: {}", dir_path.display()); | ||
fs::create_dir_all(dir_path).upstream_with_context( | ||
format!("Failed to create directory {}", dir_path.display()).as_str(), | ||
)?; | ||
println!("Directory created successfully."); | ||
} else { | ||
println!("Directory already exists: {}", dir_path.display()); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
// Helper function to copy a source file to a directory | ||
// it keeps the same file name | ||
pub fn copy_file_to_destination_dir(source_file_path: &str, dest_dir_path: &str) -> Result<()> { | ||
info!( | ||
"copy_file_to_destination_dir! Copying {} from tmpfs to {}", | ||
source_file_path, dest_dir_path | ||
); | ||
|
||
let source_file = Path::new(source_file_path); | ||
if source_file.exists() && source_file.is_file() { | ||
let file_name = source_file | ||
.file_name() | ||
.map(|name| name.to_string_lossy().to_string()) | ||
.expect("Failed to extract file name from path"); | ||
|
||
copy( | ||
PathBuf::from(source_file), | ||
path_append(dest_dir_path, format!("/{}", file_name)), | ||
)?; | ||
|
||
Ok(()) | ||
} else { | ||
Err(Error::with_context( | ||
ErrorKind::FileNotFound, | ||
&format!("source file {} does not exist", source_file_path), | ||
)) | ||
} | ||
} | ||
|
||
pub fn open_tmpfs_log_file() -> Option<std::fs::File> { | ||
let log_file = match OpenOptions::new() | ||
.append(true) // Append to the file, don't overwrite | ||
.create(true) // Create the file if it does not exist | ||
.open(LOG_CONSOLIDATED_TMPFS) | ||
{ | ||
Ok(file) => Some(file), | ||
Err(why) => { | ||
error!("Could not open {}, error {:?}", LOG_CONSOLIDATED_TMPFS, why); | ||
None | ||
} | ||
}; | ||
log_file | ||
} | ||
|
||
pub fn persist_consolidated_log_to_data_partition(s2_config: &Stage2Config) -> Result<()> { | ||
if Path::new(BALENA_DATA_MP).exists() { | ||
// if data partition is still mounted | ||
// first check expected path before calling pivot root | ||
|
||
let dest_dir = format!("{}/{}", BALENA_DATA_MP, FALLBACK_LOG_DEST_DIR); | ||
create_dir_if_not_exist(&dest_dir)?; | ||
|
||
copy_file_to_destination_dir(LOG_CONSOLIDATED_TMPFS, dest_dir.as_str())?; | ||
} else if Path::new(path_append(OLD_ROOT_MP, BALENA_DATA_MP).as_os_str()).exists() { | ||
// else if data partition is still mounted but is relative to OLD_ROOT_MP | ||
|
||
let dest_dir = format!( | ||
"{}/{}/{}", | ||
OLD_ROOT_MP, BALENA_DATA_MP, FALLBACK_LOG_DEST_DIR | ||
); | ||
create_dir_if_not_exist(&dest_dir)?; | ||
copy_file_to_destination_dir(LOG_CONSOLIDATED_TMPFS, dest_dir.as_str())?; | ||
} else { | ||
// Mount raw data partition | ||
let device = &s2_config.flash_dev; | ||
|
||
let (_boot_part, _root_a_part, data_part) = get_partition_infos(device)?; | ||
|
||
let mut loop_device = LoopDevice::get_free(true)?; | ||
info!("Create loop device: '{}'", loop_device.get_path().display()); | ||
let byte_offset = data_part.start_lba * DEF_BLOCK_SIZE as u64; | ||
let size_limit = data_part.num_sectors * DEF_BLOCK_SIZE as u64; | ||
|
||
debug!( | ||
"Setting up device '{}' with offset {}, sizelimit {} on '{}'", | ||
device.display(), | ||
byte_offset, | ||
size_limit, | ||
loop_device.get_path().display() | ||
); | ||
|
||
loop_device | ||
.setup(device, Some(byte_offset), Some(size_limit)) | ||
.unwrap(); | ||
info!( | ||
"Setup device '{}' with offset {}, sizelimit {} on '{}'", | ||
device.display(), | ||
byte_offset, | ||
size_limit, | ||
loop_device.get_path().display() | ||
); | ||
|
||
mount( | ||
Some(loop_device.get_path()), | ||
BALENA_PART_MP, | ||
Some(BALENA_DATA_FSTYPE.as_bytes()), | ||
MsFlags::empty(), | ||
NIX_NONE, | ||
) | ||
.upstream_with_context(&format!( | ||
"Failed to mount '{}' to '{}'", | ||
loop_device.get_path().display(), | ||
BALENA_PART_MP, | ||
))?; | ||
|
||
info!( | ||
"Mounted data partition as {} on {}", | ||
loop_device.get_path().display(), | ||
BALENA_PART_MP | ||
); | ||
|
||
let dest_dir = format!("{}/{}", BALENA_PART_MP, FALLBACK_LOG_DEST_DIR); | ||
create_dir_if_not_exist(&dest_dir)?; | ||
|
||
copy_file_to_destination_dir(LOG_CONSOLIDATED_TMPFS, dest_dir.as_str())?; | ||
|
||
sync(); | ||
umount(BALENA_PART_MP).upstream_with_context("Failed to unmount data partition")?; | ||
info!("Unmounted data partition from {}", BALENA_PART_MP); | ||
|
||
loop_device.unset()?; | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.