podmod/
lib.rs

1/*
2 * This program is free software: you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation, either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
14 */
15
16use std::fs;
17use std::path;
18use std::process;
19use std::str;
20
21pub mod config;
22mod fetch;
23
24fn is_module_supported(data_dir: &str, module: &str) -> bool {
25    // If the module is supported, it must have a subdirectory under 'data_dir'
26    let path = format!("{}/modules/{}", data_dir, module);
27    path::Path::new(&path).is_dir()
28}
29
30fn get_build_image_identifier(kernel_version: &str) -> String {
31    format!("{}-builder:{}-{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), kernel_version)
32}
33
34fn get_runtime_image_identifier(kernel_version: &str) -> String {
35    format!("{}-runtime:{}-{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), kernel_version)
36}
37
38fn get_module_image_identifier(module: &str, module_version: &str, kernel_version: &str) -> String {
39    format!("{}-{}:{}-{}", env!("CARGO_PKG_NAME"), module, module_version, kernel_version)
40}
41
42fn image_exists(identifier: &str) -> bool {
43    // Call 'podman exists' to check for existing image
44    // The command only succeeds if image is found
45    process::Command::new("podman")
46        .args(["image", "exists", identifier])
47        .status()
48        .expect("Error while checking for pre-existing image")
49        .success()
50}
51
52pub fn build(config: &config::Config, module: &config::ModuleConfig, idempotent: bool, no_prune: bool) {
53    // Ensure module is supported
54    if !is_module_supported(&config.data_dir, &module.name) {
55        panic!("Module {} is not supported", module.name);
56    }
57
58    // We'll need some information about the system when
59    // compiling the kernel module
60    let kernel_version = fetch::kernel_version();
61    let arch = fetch::architecture();
62    let podmod_version = env!("CARGO_PKG_VERSION");
63
64    let build_image_name = get_build_image_identifier(&kernel_version);
65    let runtime_image_name = get_runtime_image_identifier(&kernel_version);
66    let module_image_name = get_module_image_identifier(&module.name, &module.version, &kernel_version);
67
68    // Check for existing image
69    if image_exists(&module_image_name) {
70        if idempotent {
71            return;
72        }
73
74        panic!("Module {} is already built", module.name);
75    }
76
77    // Build builder image
78    if !image_exists(&build_image_name) {
79        println!("Building builder image for kernel version {} ...", kernel_version);
80
81        process::Command::new("podman")
82            .args(["build", "-t", &build_image_name])
83            .args(["--build-arg", &format!("ARCH={}", arch)])
84            .args(["--build-arg", &format!("KERNEL_VERSION={}", kernel_version)])
85            .args(["--file", "Builder.containerfile"])
86            .arg(format!("{}/common/", config.data_dir))
87            .status()
88            .expect("Error while building the builder image");
89    }
90
91    // Build runtime image
92    if !image_exists(&runtime_image_name) {
93        println!("Building runtime image for kernel version {} ...", kernel_version);
94
95        process::Command::new("podman")
96            .args(["build", "-t", &runtime_image_name])
97            .args(["--build-arg", &format!("KERNEL_VERSION={}", kernel_version)])
98            .args(["--build-arg", &format!("PODMOD_VERSION={}", podmod_version)])
99            .args(["--file", "Runtime.containerfile"])
100            .arg(format!("{}/common/", config.data_dir))
101            .status()
102            .expect("Error while building the runtime image");
103    }
104
105    println!("Building module {} for kernel version {} ...", module.name, kernel_version);
106
107    // Build the new image
108    // We already know the target architecture and kernel version
109    let mut command = process::Command::new("podman");
110
111    command
112        .args(["build", "-t", &module_image_name])
113        .args(["--build-arg", &format!("ARCH={}", arch)])
114        .args(["--build-arg", &format!("KERNEL_VERSION={}", kernel_version)])
115        .args(["--build-arg", &format!("MODULE_VERSION={}", module.version)])
116        .args(["--build-arg", &format!("PODMOD_VERSION={}", podmod_version)]);
117
118    // Add additional build parameter passed to the function
119    for (key, value) in &module.build_args {
120        command.args(["--build-arg", &format!("{}={}", key, value)]);
121    }
122
123    command
124        .arg(format!("{}/modules/{}", &config.data_dir, module.name))
125        .status()
126        .expect("Error while building the kernel module");
127
128    // By default, we'll prune any intermediary images that the build generates
129    // The user probably isn't building the same image multiple times,
130    // so keeping the cached build stages isn't very useful
131    if !no_prune {
132        process::Command::new("podman")
133            .args(["system", "prune", "-f"])
134            .status()
135            .expect("Error while pruning intermediary images");
136    }
137}
138
139pub fn load(module: &config::ModuleConfig, idempotent: bool) {
140    // Check if module is already loaded
141    if fetch::is_module_loaded(&module.name) {
142        if idempotent {
143            return;
144        }
145
146        panic!("Module {} is already loaded", module.name);
147    }
148
149    println!("Loading module {} ...", module.name);
150
151    let mut command = vec![String::from("load")];
152    command.extend(module.kernel_args.clone());
153
154    // Call the load script inside a new container
155    // Add additional kernel parameters passed to the function
156    run(module, &command);
157}
158
159pub fn modules(config: &config::Config) {
160    println!("The following kernel modules are supported:");
161
162    // Each supported module has a subdirectory in 'data_dir'
163    let modules = fs::read_dir(format!("{}/modules", config.data_dir))
164        .expect("Error while reading data directory");
165
166    for module in modules {
167        // Print the path's basename
168        let module = module.unwrap().path();
169        let module = module.file_name().unwrap();
170        let module = module.to_str().unwrap();
171        println!("{}", module);
172    }
173}
174
175pub fn run(module: &config::ModuleConfig, command: &Vec<String>) {
176    // podmod's container images are always named predictably
177    let kernel_version = fetch::kernel_version();
178    let image_name = get_module_image_identifier(&module.name, &module.version, &kernel_version);
179
180    // Ensure module is built
181    if !image_exists(&image_name) {
182        panic!("Module {} is not built", module.name);
183    }
184
185    println!("Executing command {:?}, in module {} ...", command, module.name);
186
187    // Run the command inside a new container
188    // Add additional Podman arguments from module configuration to the function
189    process::Command::new("podman")
190        .args(["run", "--rm", "--privileged"])
191        .args(&module.container_args)
192        .arg(&image_name)
193        .args(command)
194        .status()
195        .expect("Error while loading the kernel module");
196}
197
198pub fn shell(module: &config::ModuleConfig, shell: &str) {
199    let mut module = module.clone();
200    module.container_args.extend(vec![String::from("-it")]);
201
202    println!("Starting shell session in module {} ...", module.name);
203
204    // Call the load script inside a new container
205    // Add additional kernel parameters passed to the function
206    run(&module, &vec![String::from(shell)]);
207}
208
209pub fn unload(module: &config::ModuleConfig, idempotent: bool) {
210    // Check if module is loaded
211    if !fetch::is_module_loaded(&module.name) {
212        if idempotent {
213            return;
214        }
215
216        panic!("Module {} is not loaded", module.name);
217    }
218
219    // podmod's container images are always named predictably
220    let kernel_version = fetch::kernel_version();
221    let image_name = get_module_image_identifier(&module.name, &module.version, &kernel_version);
222
223    println!("Unloading module {} ...", module.name);
224
225    // Call the unload script inside a new container
226    process::Command::new("podman")
227        .args(["run", "--rm", "--privileged", &image_name, "unload"])
228        .status()
229        .expect("Error while unloading the kernel module");
230}