use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::os::fd::{FromRawFd, IntoRawFd};
use std::path::Path;
use crate::core::errors::ScriptError;
use crate::core::partition::PartitionList;
use crate::fdisk::Fdisk;
use crate::ffi_utils;
#[derive(Debug)]
#[repr(transparent)]
pub struct Script<'fdisk> {
pub(crate) inner: *mut libfdisk::fdisk_script,
_marker: PhantomData<&'fdisk Fdisk<'fdisk>>,
}
impl<'fdisk> Script<'fdisk> {
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn incr_ref_counter(&mut self) {
unsafe { libfdisk::fdisk_ref_script(self.inner) }
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn decr_ref_counter(&mut self) {
unsafe { libfdisk::fdisk_unref_script(self.inner) }
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) unsafe fn ref_from_boxed_ptr<'a>(
ptr: Box<*mut libfdisk::fdisk_script>,
) -> (*mut *mut libfdisk::fdisk_script, &'a Self) {
let raw_ptr = Box::into_raw(ptr);
let entry_ref = unsafe { &*(raw_ptr as *const _ as *const Self) };
(raw_ptr, entry_ref)
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) unsafe fn mut_from_boxed_ptr<'a>(
ptr: Box<*mut libfdisk::fdisk_script>,
) -> (*mut *mut libfdisk::fdisk_script, &'a mut Self) {
let raw_ptr = Box::into_raw(ptr);
let entry_ref = unsafe { &mut *(raw_ptr as *mut Self) };
(raw_ptr, entry_ref)
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn new(_: &'fdisk Fdisk, inner: *mut libfdisk::fdisk_script) -> Script<'fdisk> {
log::debug!("Script::new creating a new `Script` instance");
Self {
inner,
_marker: PhantomData,
}
}
#[doc(hidden)]
fn read_file(ptr: &mut Self, file: &mut File) -> Result<(), ScriptError> {
let file_stream = ffi_utils::read_only_c_file_stream_from(file).map_err(|e| {
let err_msg = format!("failed to read script file {e}");
ScriptError::IoError(err_msg)
})?;
let result = unsafe { libfdisk::fdisk_script_read_file(ptr.inner, file_stream as *mut _) };
match result {
0 => {
log::debug!("Script::read_file file read");
Ok(())
}
code => {
let err_msg = "failed to read file".to_owned();
log::debug!("Script::read_file {}. libfdisk::fdisk_script_read_file returned error code: {:?}", err_msg, code);
Err(ScriptError::Read(err_msg))
}
}
}
pub fn import_file<T>(&mut self, file_path: T) -> Result<(), ScriptError>
where
T: AsRef<Path>,
{
let file_path = file_path.as_ref();
log::debug!("Script::import_file importing file: {:?}", file_path);
let mut file = OpenOptions::new().read(true).open(file_path).map_err(|e| {
let err_msg = format!("failed to open file {} {e}", file_path.display());
ScriptError::IoError(err_msg)
})?;
Self::read_file(self, &mut file)
}
pub fn import_stream(&mut self, file: &mut File) -> Result<(), ScriptError> {
log::debug!("Script::import_stream importing from file");
Self::read_file(self, file)
}
pub fn set_custom_read_line<R>(&mut self, fn_read_line: R) -> Result<(), ScriptError>
where
R: FnMut(&mut File, &mut [i8]) -> io::Result<usize> + 'static,
{
#[doc(hidden)]
unsafe extern "C" fn read_callback<R>(
script: *mut libfdisk::fdisk_script,
buf: *mut libc::c_char,
count: usize,
file_stream: *mut libfdisk::FILE,
) -> *mut libc::c_char
where
R: FnMut(&mut File, &mut [i8]) -> io::Result<usize> + 'static,
{
let rc = unsafe { libc::fileno(file_stream as *mut _) };
let mut file = match rc {
-1 =>
panic!("Script::read_callback failed to obtain a file file descriptor from the FILE struct passed to the custom file reader function"),
fd =>
unsafe { File::from_raw_fd(fd) },
};
let mut buffer: Vec<libc::c_char> = vec![0; count - 1];
let mut user_data_ptr = MaybeUninit::<*mut libc::c_void>::zeroed();
unsafe {
user_data_ptr.write(libfdisk::fdisk_script_get_userdata(script));
}
let user_data = unsafe { user_data_ptr.assume_init() };
let read = &mut *(user_data as *mut R);
match read(&mut file, buffer.as_mut_slice()) {
Ok(total_read) => {
buffer.push(0);
let res = unsafe { libc::strncpy(buf, buffer.as_ptr(), total_read + 1) };
let _ = file.into_raw_fd();
res
}
Err(e) => {
log::debug!("Script::read_callback error while reading file. {:?}", e);
let _ = file.into_raw_fd();
std::ptr::null_mut()
}
}
}
let user_data = Box::into_raw(Box::new(fn_read_line));
let result =
unsafe { libfdisk::fdisk_script_set_userdata(self.inner, user_data as *mut _) };
match result {
0 => {
log::debug!(
"Script::set_custom_read_line set custom file reader function as user data"
);
let result = unsafe {
libfdisk::fdisk_script_set_fgets(self.inner, Some(read_callback::<R>))
};
match result {
0 => {
log::debug!("Script::set_custom_read_line custom reader function set");
Ok(())
}
code => {
let err_msg =
"failed to set custom file reader callback function".to_owned();
log::debug!("Script::set_custom_read_line {}. libfdisk::fdisk_script_set_fgets returned error code: {:?}", err_msg, code);
let _ = unsafe { Box::from_raw(user_data) };
Err(ScriptError::Config(err_msg))
}
}
}
code => {
let err_msg = "failed to set custom file reader function as user data".to_owned();
log::debug!("Script::set_custom_read_line {}. libfdisk::fdisk_script_set_userdata returned error code: {:?}", err_msg, code);
let _ = unsafe { Box::from_raw(user_data) };
Err(ScriptError::Config(err_msg))
}
}
}
pub fn read_line(
&mut self,
file: &mut File,
buffer: &mut [libc::c_char],
) -> Result<(), ScriptError> {
log::debug!("Script::read_line reading a line from file");
let file_stream = ffi_utils::read_only_c_file_stream_from(file).map_err(|e| {
let err_msg = format!("failed to read file stream {e}");
ScriptError::IoError(err_msg)
})?;
let result = unsafe {
libfdisk::fdisk_script_read_line(
self.inner,
file_stream as *mut _,
buffer.as_mut_ptr(),
buffer.len(),
)
};
match result {
0 => {
log::debug!("Script::read_line read a line from file");
Ok(())
}
code => {
let err_msg = if code == 1 {
"no line to read from file".to_owned()
} else {
"failed to read a line from file".to_owned()
};
log::debug!("Script::read_line {}. libfdisk::fdisk_script_read_line returned error code: {:?}", err_msg, code);
Err(ScriptError::Read(err_msg))
}
}
}
pub fn count_lines(&self) -> Option<usize> {
log::debug!("Script::count_lines getting the number of script lines");
let result = unsafe { libfdisk::fdisk_script_get_nlines(self.inner) };
match result {
count if count < 0 => {
let err_msg = "failed to get the number of script lines".to_owned();
log::debug!("Script::count_lines {}. libfdisk::fdisk_script_get_nlines returned error code: {:?}", err_msg, count);
None
}
count => {
log::debug!("Script::count_lines number of script lines: {:?}", count);
Some(count as usize)
}
}
}
pub fn partition_table_entries(&self) -> Option<PartitionList> {
log::debug!("Script::partition_table_entries getting partition table entries");
let mut ptr = MaybeUninit::<*mut libfdisk::fdisk_table>::zeroed();
unsafe {
ptr.write(libfdisk::fdisk_script_get_table(self.inner));
}
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
let err_msg = "failed to get partition table entries".to_owned();
log::debug!("Script::partition_table_entries {}. libfdisk::fdisk_script_get_table returned a NULL pointer", err_msg);
None
}
ptr => {
log::debug!("Script::partition_table_entries got partition table entries");
let entries = PartitionList::borrow_ptr(ptr);
Some(entries)
}
}
}
pub fn override_partition_table(&mut self, entries: PartitionList) -> Result<(), ScriptError> {
log::debug!("Script::override_partition_table overriding entries in partition table");
let result = unsafe { libfdisk::fdisk_script_set_table(self.inner, entries.inner) };
match result {
0 => {
log::debug!("Script::override_partition_table overrode entries in partition table");
Ok(())
}
code => {
let err_msg = "failed to override entries in partition table".to_owned();
log::debug!("Script::override_partition_table {}. libfdisk::fdisk_script_set_table returned error code: {:?}", err_msg, code);
Err(ScriptError::Override(err_msg))
}
}
}
#[doc(hidden)]
fn json_output(ptr: &mut Self, enable: bool) -> Result<(), ScriptError> {
let op_str = if enable {
"enable".to_owned()
} else {
"disable".to_owned()
};
let op = if enable { 1 } else { 0 };
let result = unsafe { libfdisk::fdisk_script_enable_json(ptr.inner, op) };
match result {
0 => {
log::debug!("Script::json_output {}d JSON output.", op_str);
Ok(())
}
code => {
let err_msg = format!("failed to {} JSON output", op_str);
log::debug!("Script::json_output {}. libfdisk::fdisk_script_enable_json returned error code: {:?}", err_msg, code );
Err(ScriptError::Config(err_msg))
}
}
}
pub fn enable_json_output(&mut self) -> Result<(), ScriptError> {
log::debug!("Script::enable_json_output enabling JSON output");
Self::json_output(self, true)
}
pub fn disable_json_output(&mut self) -> Result<(), ScriptError> {
log::debug!("Script::disable_json_output disabling JSON output");
Self::json_output(self, false)
}
#[doc(hidden)]
fn write_file(ptr: &mut Self, file: &mut File) -> Result<(), ScriptError> {
let file_stream = ffi_utils::write_only_c_file_stream_from(file).map_err(|e| {
let err_msg = format!("failed to write file stream {e}");
ScriptError::IoError(err_msg)
})?;
let result = unsafe { libfdisk::fdisk_script_write_file(ptr.inner, file_stream as *mut _) };
match result {
0 => {
log::debug!("Script::write_file wrote script to file");
Ok(())
}
code => {
let err_msg = "failed to write script to file".to_owned();
log::debug!("Script::write_file {}. libfdisk::fdisk_script_write_file returned error code: {:?}", err_msg, code);
Err(ScriptError::Write(err_msg))
}
}
}
pub fn export_to_file<T>(&mut self, file_path: T) -> Result<(), ScriptError>
where
T: AsRef<Path>,
{
let file_path = file_path.as_ref();
log::debug!("Script::export_to_file exporting to file: {:?}", file_path);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(file_path)
.map_err(|e| {
let err_msg = format!("failed to open file {} {e}", file_path.display());
ScriptError::IoError(err_msg)
})?;
Self::write_file(self, &mut file)
}
pub fn export_to_stream(&mut self, file: &mut File) -> Result<(), ScriptError> {
log::debug!("Script::export_to_stream exporting to file stream");
Self::write_file(self, file)
}
#[doc(hidden)]
fn read_context(
ptr: &mut Self,
ctx_ptr: *mut libfdisk::fdisk_context,
) -> Result<(), ScriptError> {
let result = unsafe { libfdisk::fdisk_script_read_context(ptr.inner, ctx_ptr) };
match result {
0 => {
log::debug!("Script::read_context composed script");
Ok(())
}
code => {
let err_msg = "failed to compose script".to_owned();
log::debug!("Script::read_context {}. libfdisk::fdisk_script_read_context returned error code: {:?}", err_msg, code);
Err(ScriptError::Compose(err_msg))
}
}
}
pub fn compose_script(&mut self) -> Result<(), ScriptError> {
log::debug!("Script::compose_script composing script from associated `Fdisk`");
Self::read_context(self, std::ptr::null_mut())
}
pub fn compose_script_from(&mut self, context: &Fdisk) -> Result<(), ScriptError> {
log::debug!("Script::compose_script composing script from external `Fdisk`");
Self::read_context(self, context.inner)
}
#[doc(hidden)]
fn set_header(
ptr: &mut Self,
name: *const libc::c_char,
value: *const libc::c_char,
) -> Result<(), ScriptError> {
let result = unsafe { libfdisk::fdisk_script_set_header(ptr.inner, name, value) };
match result {
0 => {
log::debug!("Script::set_header header set");
Ok(())
}
code => {
let err_msg = "failed to set header".to_owned();
log::debug! {"Script::set_header {}. libfdisk::fdisk_script_set_header returned error code: {:?}", err_msg, code};
Err(ScriptError::Compose(err_msg))
}
}
}
pub fn add_header<T>(&mut self, name: T, value: T) -> Result<(), ScriptError>
where
T: AsRef<str>,
{
let name = name.as_ref();
let name_cstr = ffi_utils::as_ref_str_to_c_string(name).map_err(|e| {
let err_msg = format!("failed to convert value to `CString` {e}");
ScriptError::CStringConversion(err_msg)
})?;
let value = value.as_ref();
let value_cstr = ffi_utils::as_ref_str_to_c_string(value).map_err(|e| {
let err_msg = format!("failed to convert value to `CString` {e}");
ScriptError::CStringConversion(err_msg)
})?;
log::debug!(
"Script::add_header adding header named: {:?} with value: {:?}",
name,
value
);
Self::set_header(self, name_cstr.as_ptr(), value_cstr.as_ptr())
}
pub fn remove_header<T>(&mut self, name: T) -> Result<(), ScriptError>
where
T: AsRef<str>,
{
let name = name.as_ref();
let name_cstr = ffi_utils::as_ref_str_to_c_string(name).map_err(|e| {
let err_msg = format!("failed to convert value to `CString` {e}");
ScriptError::CStringConversion(err_msg)
})?;
log::debug!("Script::add_header removing header named: {:?}", name,);
Self::set_header(self, name_cstr.as_ptr(), std::ptr::null())
}
pub fn header_value<T>(&self, name: T) -> Option<&str>
where
T: AsRef<str>,
{
let name = name.as_ref();
let name_cstr = ffi_utils::as_ref_str_to_c_string(name).ok()?;
log::debug!("Script::header_value getting script header: {:?}", name);
let mut ptr = MaybeUninit::<*const libc::c_char>::zeroed();
unsafe {
ptr.write(libfdisk::fdisk_script_get_header(
self.inner,
name_cstr.as_ptr(),
));
}
match unsafe { ptr.assume_init() } {
ptr if ptr.is_null() => {
log::debug!("Script::header_value script has no header named: {:?}. libfdisk::fdisk_script_get_header returned a NULL pointer", name);
None
}
value_ptr => {
let value = ffi_utils::const_char_array_to_str_ref(value_ptr).ok();
log::debug!(
"Script::header_value header named: {:?} has value: {:?}",
name,
value
);
value
}
}
}
pub fn has_header_label(&self) -> bool {
let state = unsafe { libfdisk::fdisk_script_has_force_label(self.inner) == 1 };
log::debug!("Script::has_header_label value: {:?}", state);
state
}
}
impl<'fdisk> AsRef<Script<'fdisk>> for Script<'fdisk> {
#[inline]
fn as_ref(&self) -> &Script<'fdisk> {
self
}
}
impl<'fdisk> Drop for Script<'fdisk> {
fn drop(&mut self) {
log::debug!("Script::drop deallocating `Script` instance");
unsafe { libfdisk::fdisk_unref_script(self.inner) }
}
}