interface_rs/
network_interfaces.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
use crate::interface::Interface;
use crate::parser::Parser;
use crate::error::NetworkInterfacesError;
use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::fmt;

/// Represents the collection of network interfaces defined in an `interfaces(5)` file.
///
/// The `NetworkInterfaces` struct provides methods to load, manipulate, and save
/// network interface configurations.
///
/// # Examples
///
/// Loading and modifying interfaces:
///
/// ```rust
/// use interface_rs::NetworkInterfaces;
/// use interface_rs::interface::Interface;
///
/// let mut net_ifaces = NetworkInterfaces::load("/path/to/interfaces").unwrap();
///
/// // Modify an interface
/// if let Some(iface) = net_ifaces.interfaces.get_mut("eth0") {
///     iface.method = Some("static".to_string());
///     iface.options.push(("address".to_string(), "192.168.1.100".to_string()));
/// }
///
/// // Save changes
/// net_ifaces.save().unwrap();
/// ```
#[derive(Debug)]
pub struct NetworkInterfaces {
    /// A mapping of interface names to their configurations.
    pub interfaces: HashMap<String, Interface>,
    /// The path to the interfaces file.
    path: Option<PathBuf>,
    /// The last modified time of the interfaces file.
    last_modified: Option<SystemTime>,
}

impl NetworkInterfaces {
    /// Loads the `interfaces(5)` file into memory.
    ///
    /// # Arguments
    ///
    /// * `path` - The path to the interfaces file.
    ///
    /// # Returns
    ///
    /// A `NetworkInterfaces` instance containing the parsed interfaces.
    ///
    /// # Errors
    ///
    /// Returns a `NetworkInterfacesError` if the file cannot be read or parsed.
    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, NetworkInterfacesError> {
        let path_buf = path.as_ref().to_path_buf();
        let metadata = fs::metadata(&path_buf)?;
        let last_modified = metadata.modified()?;

        let content = fs::read_to_string(&path_buf)?;
        let parser = Parser::new();
        let interfaces = parser.parse(&content)?;

        Ok(NetworkInterfaces {
            interfaces,
            path: Some(path_buf),
            last_modified: Some(last_modified),
        })
    }

    /// Retrieves a reference to an interface by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the interface.
    ///
    /// # Returns
    ///
    /// An `Option` containing a reference to the `Interface` if found.
    pub fn get_interface(&self, name: &str) -> Option<&Interface> {
        self.interfaces.get(name)
    }

    /// Adds or updates an interface in the collection.
    ///
    /// # Arguments
    ///
    /// * `iface` - The `Interface` to add or update.
    pub fn add_interface(&mut self, iface: Interface) {
        self.interfaces.insert(iface.name.clone(), iface);
    }

    /// Deletes an interface by name.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the interface to delete.
    pub fn delete_interface(&mut self, name: &str) {
        self.interfaces.remove(name);
    }

    /// Saves changes back to the `interfaces(5)` file.
    ///
    /// # Errors
    ///
    /// Returns a `NetworkInterfacesError` if the file cannot be written or has been modified on disk.
    pub fn save(&mut self) -> Result<(), NetworkInterfacesError> {
        let path = match &self.path {
            Some(p) => p.clone(),
            None => {
                return Err(NetworkInterfacesError::Other(
                    "No file path specified".to_string(),
                ))
            }
        };

        // Check if file has been modified since last load
        let metadata = fs::metadata(&path)?;
        let current_modified = metadata.modified()?;
        if let Some(last_modified) = self.last_modified {
            if current_modified > last_modified {
                // File has been modified since last read
                return Err(NetworkInterfacesError::FileModified);
            }
        }

        let mut file = fs::File::create(&path)?;

        // Write interfaces to file
        for iface in self.interfaces.values() {
            write!(file, "{}", iface)?;
            writeln!(file)?; // Blank line between interfaces
        }

        // Update last_modified
        self.last_modified = Some(SystemTime::now());
        Ok(())
    }

    /// Reloads the interfaces file from disk.
    ///
    /// # Errors
    ///
    /// Returns a `NetworkInterfacesError` if the file cannot be read or parsed.
    pub fn reload(&mut self) -> Result<(), NetworkInterfacesError> {
        let path = match &self.path {
            Some(p) => p.clone(),
            None => {
                return Err(NetworkInterfacesError::Other(
                    "No file path specified".to_string(),
                ))
            }
        };
        let reloaded = NetworkInterfaces::load(&path)?;
        self.interfaces = reloaded.interfaces;
        self.last_modified = reloaded.last_modified;
        Ok(())
    }
}

// Implement Display for NetworkInterfaces to allow easy printing
impl fmt::Display for NetworkInterfaces {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for iface in self.interfaces.values() {
            write!(f, "{}\n", iface)?;
        }
        Ok(())
    }
}