1use super::{command_mechanism, ActionSpec};
10use sysknife_types::RiskLevel;
11
12const HELPER: &str = "/usr/lib/sysknife/mount-edit";
13
14pub fn specs() -> Vec<ActionSpec> {
15 vec![
16 get_mounts(),
17 add_mount("/dev/sdb1", "/mnt/data", "ext4", Some("defaults")),
18 remove_mount("/mnt/data"),
19 add_swap("/swapfile", 2048),
20 remove_swap("/swapfile"),
21 ]
22}
23
24pub fn get_mounts() -> ActionSpec {
26 ActionSpec {
27 action_name: "GetMounts",
28 mechanism: command_mechanism("findmnt", ["--json"]),
29 risk_level: RiskLevel::Low,
30 reboot_required: false,
31 rollback_available: false,
32 }
33}
34
35pub fn add_mount(
37 device: &str,
38 mountpoint: &str,
39 fstype: &str,
40 options: Option<&str>,
41) -> ActionSpec {
42 let mut args = vec![
43 HELPER.to_string(),
44 "--op".to_string(),
45 "mount".to_string(),
46 "--device".to_string(),
47 device.to_string(),
48 "--mountpoint".to_string(),
49 mountpoint.to_string(),
50 "--fstype".to_string(),
51 fstype.to_string(),
52 ];
53 if let Some(opts) = options {
54 args.push("--options".to_string());
55 args.push(opts.to_string());
56 }
57 ActionSpec {
58 action_name: "AddMount",
59 mechanism: command_mechanism("sudo", args),
60 risk_level: RiskLevel::High,
61 reboot_required: false,
62 rollback_available: false,
63 }
64}
65
66pub fn remove_mount(mountpoint: &str) -> ActionSpec {
68 ActionSpec {
69 action_name: "RemoveMount",
70 mechanism: command_mechanism(
71 "sudo",
72 [HELPER, "--op", "unmount", "--mountpoint", mountpoint],
73 ),
74 risk_level: RiskLevel::High,
75 reboot_required: false,
76 rollback_available: false,
77 }
78}
79
80pub fn add_swap(file: &str, size_mb: u32) -> ActionSpec {
82 let size = size_mb.to_string();
83 ActionSpec {
84 action_name: "AddSwap",
85 mechanism: command_mechanism(
86 "sudo",
87 [
88 HELPER,
89 "--op",
90 "addswap",
91 "--file",
92 file,
93 "--size-mb",
94 &size,
95 ],
96 ),
97 risk_level: RiskLevel::High,
98 reboot_required: false,
99 rollback_available: false,
100 }
101}
102
103pub fn remove_swap(file: &str) -> ActionSpec {
105 ActionSpec {
106 action_name: "RemoveSwap",
107 mechanism: command_mechanism("sudo", [HELPER, "--op", "rmswap", "--file", file]),
108 risk_level: RiskLevel::High,
109 reboot_required: false,
110 rollback_available: false,
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::actions::ActionMechanism;
118
119 fn args_of(spec: &ActionSpec) -> (&'static str, Vec<String>) {
120 match &spec.mechanism {
121 ActionMechanism::Command { program, args } => (program, args.clone()),
122 other => panic!("expected Command, got {other:?}"),
123 }
124 }
125
126 #[test]
127 fn get_mounts_is_read_only_json() {
128 let (program, args) = args_of(&get_mounts());
129 assert_eq!(program, "findmnt");
130 assert_eq!(args, vec!["--json"]);
131 assert_eq!(get_mounts().risk_level, RiskLevel::Low);
132 }
133
134 #[test]
135 fn add_mount_passes_op_and_optional_options() {
136 let (program, args) = args_of(&add_mount(
137 "/dev/sdb1",
138 "/mnt/data",
139 "ext4",
140 Some("noatime"),
141 ));
142 assert_eq!(program, "sudo");
143 assert_eq!(
144 args,
145 vec![
146 HELPER,
147 "--op",
148 "mount",
149 "--device",
150 "/dev/sdb1",
151 "--mountpoint",
152 "/mnt/data",
153 "--fstype",
154 "ext4",
155 "--options",
156 "noatime"
157 ]
158 );
159 let (_, no_opts) = args_of(&add_mount("/dev/sdb1", "/mnt/data", "ext4", None));
161 assert!(!no_opts.iter().any(|a| a == "--options"));
162 }
163
164 #[test]
165 fn swap_ops_shapes() {
166 let (_, add) = args_of(&add_swap("/swapfile", 2048));
167 assert_eq!(
168 add,
169 vec![
170 HELPER,
171 "--op",
172 "addswap",
173 "--file",
174 "/swapfile",
175 "--size-mb",
176 "2048"
177 ]
178 );
179 let (_, rm) = args_of(&remove_swap("/swapfile"));
180 assert_eq!(rm, vec![HELPER, "--op", "rmswap", "--file", "/swapfile"]);
181 assert_eq!(add_swap("/s", 1).risk_level, RiskLevel::High);
182 }
183}