ni_syscfg/
software.rs

1//! Module for managing software on NI systems.
2//!
3
4use std::ffi::CString;
5use std::path::Path;
6
7use ni_syscfg_sys::*;
8
9use crate::error::{api_status, NiSystemConfigurationError, Result};
10use crate::types::FfiBoolean;
11use crate::Session;
12
13type Uuid = String;
14
15/// Contains the metadata which can be saved with system images.
16pub struct ImageInfo {
17    /// A title for the image.
18    pub title: String,
19    /// A unique ID for the image. Specifies the unique identifier for the system image.
20    /// If you specify this parameter, it must be in the GUID format "{00000000-0000-0000-0000-000000000000}" where each '0' represents a hexadecimal digit. This should be in the form
21    pub id: Uuid,
22    /// A short description for the image.
23    pub description: String,
24    /// A version number for the image.
25    pub version: String,
26}
27
28/// The network interface settings that can be applied when setting a system image.
29///
30/// ## Definitions
31///
32/// * **Reset**: Set the adapters back to default. Normally DHCP.
33/// * **Apply**: Set the adapters to the settings of the system the image was taken to.
34/// * **Preserve**: Keep the adapter settings of the current system.
35#[repr(i32)]
36pub enum NetworkInterfaceSettings {
37    ResetPrimaryResetOthers = NISysCfgNetworkInterfaceSettings_NISysCfgResetPrimaryResetOthers,
38    PreservePrimaryResetOthers =
39        NISysCfgNetworkInterfaceSettings_NISysCfgPreservePrimaryResetOthers,
40    PreservePrimaryPreserveOthers =
41        NISysCfgNetworkInterfaceSettings_NISysCfgPreservePrimaryPreserveOthers,
42    PreservePrimaryApplyOthers =
43        NISysCfgNetworkInterfaceSettings_NISysCfgPreservePrimaryApplyOthers,
44    ApplyPrimaryResetOthers = NISysCfgNetworkInterfaceSettings_NISysCfgApplyPrimaryResetOthers,
45    ApplyPrimaryPreserveOthers =
46        NISysCfgNetworkInterfaceSettings_NISysCfgApplyPrimaryPreserveOthers,
47    ApplyPrimaryApplyOthers = NISysCfgNetworkInterfaceSettings_NISysCfgApplyPrimaryApplyOthers,
48}
49
50impl Session {
51    /// Creates an image of the system in the `image` path.
52    /// The image is a folder containing metadata and the disk image.
53    ///
54    /// This wraps the `NISysCfgCreateSystemImageAsFolder` method from the C API.
55    ///
56    /// # Parameters
57    /// * `image` should be a path to the directory to be created with the image contents.
58    /// * `image_info` provides metadata to be saved with the image.
59    /// * `encryption_passphrase` should be [Some] with a value if you wish to encrypt the image or [None] to save the image unencrypted.
60    /// * `excluded_files_folders` can contain a list of files and folders which should not be captured in the image.
61    /// * `auto_restart` Restarts the system into install mode by default before the operation is performed, and restarts back to a running state after the operation is complete.
62    /// If you choose not to restart automatically, and the system is not in install mode, this function returns an error.
63    /// * `overwrite_if_exists` defines whether to replace an existing image in the `image` path.
64    pub fn get_system_image(
65        &self,
66        image: &Path,
67        image_info: &ImageInfo,
68        encryption_passphrase: Option<&str>,
69        excluded_files_folders: &[&str],
70        auto_restart: bool,
71        overwrite_if_exists: bool,
72    ) -> Result<()> {
73        let handle = self.handle();
74        let path = CString::new(image.as_os_str().to_string_lossy().as_ref())?;
75        let password = encryption_passphrase.map(|password| CString::new(password));
76        let password_ptr = if let Some(actual_password) = password {
77            actual_password?.as_ptr()
78        } else {
79            std::ptr::null()
80        };
81
82        let title = CString::new(image_info.title.as_str())?;
83        let id = CString::new(image_info.id.as_str())?;
84        let version = CString::new(image_info.version.as_str())?;
85        let description = CString::new(image_info.description.as_str())?;
86
87        // Build this list. We do it in two steps to ensure Vec<CString> is owned by the function
88        // for the duration of the call.
89        let excluded_c = str_slice_to_cstring(excluded_files_folders)?;
90        let mut excluded_ptrs = cstring_vec_to_ptr_array(&excluded_c[..]);
91
92        unsafe {
93            api_status(NISysCfgCreateSystemImageAsFolder(
94                *handle,
95                title.as_ptr(),
96                id.as_ptr(),
97                version.as_ptr(),
98                description.as_ptr(),
99                FfiBoolean::from(auto_restart) as i32,
100                path.as_ptr(),
101                password_ptr,
102                excluded_ptrs.len() as u32,
103                excluded_ptrs.as_mut_ptr(),
104                FfiBoolean::from(overwrite_if_exists) as i32,
105            ))?;
106        }
107        Ok(())
108    }
109
110    /// Copy a system image to the target from the `image` path.
111    /// The image is a folder containing metadata and the disk image captured with [Session::get_system_image]
112    ///
113    /// This wraps the `NISysCfgSetSystemImageFromFolder2` method from the C API.
114    ///
115    /// # Parameters
116    /// * `image` should be a path to the directory to be created with the image contents.
117    /// * `encryption_passphrase` should be [Some] with a value if the image is encrypted or [None] if the image is unencrypted.
118    /// * `excluded_files_folders` can contain a list of files and folders which should not be ovewritten on the tearget.
119    /// * `auto_restart` Restarts the system into install mode by default before the operation is performed, and restarts back to a running state after the operation is complete.
120    /// If you choose not to restart automatically, and the system is not in install mode, the resulting image may not be valid.
121    /// * `original_system_only` defines whether this should only be applied to the same system the image came from based on the MAC address.
122    /// * `network_settings` defines the state of the network configuration after the system image has been applied.
123    pub fn set_system_image(
124        &self,
125        image: &Path,
126        encryption_passphrase: Option<&str>,
127        excluded_files_folders: &[&str],
128        auto_restart: bool,
129        original_system_only: bool,
130        network_settings: NetworkInterfaceSettings,
131    ) -> Result<()> {
132        let handle = self.handle();
133        let path = CString::new(image.as_os_str().to_string_lossy().as_ref())?;
134        let password = encryption_passphrase.map(|password| CString::new(password));
135        let password_ptr = if let Some(actual_password) = password {
136            actual_password?.as_ptr()
137        } else {
138            std::ptr::null()
139        };
140
141        // Build this list. We do it in two steps to ensure Vec<CString> is owned by the function
142        // for the duration of the call.
143        let excluded_c = str_slice_to_cstring(excluded_files_folders)?;
144        let mut excluded_ptrs = cstring_vec_to_ptr_array(&excluded_c[..]);
145
146        unsafe {
147            api_status(NISysCfgSetSystemImageFromFolder2(
148                *handle,
149                FfiBoolean::from(auto_restart) as i32,
150                path.as_ptr(),
151                password_ptr,
152                excluded_ptrs.len() as u32,
153                excluded_ptrs.as_mut_ptr(),
154                FfiBoolean::from(original_system_only) as i32,
155                network_settings as i32,
156            ))?;
157        }
158        Ok(())
159    }
160}
161
162fn str_slice_to_cstring(slice: &[&str]) -> Result<Vec<CString>> {
163    slice
164        .iter()
165        .map(|&item| CString::new(item))
166        .map(|item| item.map_err(|e| NiSystemConfigurationError::NulStringError(e)))
167        .collect::<Result<Vec<CString>>>()
168}
169
170fn cstring_vec_to_ptr_array(list: &[CString]) -> Vec<*const i8> {
171    list.iter().map(|cstring| cstring.as_ptr()).collect()
172}
173
174#[cfg(test)]
175mod test {
176    use std::ffi::CStr;
177
178    use super::{cstring_vec_to_ptr_array, str_slice_to_cstring};
179
180    /// Test the conversion to an array of pointers by converting back and confirming contents.
181    #[test]
182    fn test_string_list_conversion() {
183        let original = ["test1", "test2", "test3"];
184
185        //First complete our conversion to FFI Types.
186
187        let cstring = str_slice_to_cstring(&original).unwrap();
188        let mut pointers = cstring_vec_to_ptr_array(&cstring);
189
190        let length = pointers.len() as u32;
191        let ptr = pointers.as_mut_ptr();
192
193        // This section attempts to replicate what C will do to access this.
194        let new_strings: Vec<&CStr> = unsafe {
195            let new_pointers = std::slice::from_raw_parts(ptr, length as usize);
196            new_pointers
197                .iter()
198                .map(|&ptr| CStr::from_ptr(ptr))
199                .collect()
200        };
201
202        for (&new, original) in new_strings.iter().zip(original) {
203            let new_string = new.to_str().unwrap();
204            assert_eq!(new_string, original);
205        }
206    }
207}