1use std::collections::HashMap;
2
3pub struct SystemdConstants;
4
5impl SystemdConstants {
6 pub fn valid_sections() -> Vec<&'static str> {
7 include_str!("../docs/sections.txt").lines().collect()
8 }
9
10 pub fn section_directives() -> HashMap<&'static str, Vec<&'static str>> {
17 let mut map = HashMap::new();
18
19 map.insert(
20 "Unit",
21 include_str!("../docs/directives/unit.txt")
22 .lines()
23 .collect(),
24 );
25 map.insert(
26 "Service",
27 include_str!("../docs/directives/service.txt")
28 .lines()
29 .collect(),
30 );
31 map.insert(
32 "Install",
33 include_str!("../docs/directives/install.txt")
34 .lines()
35 .collect(),
36 );
37 map.insert(
38 "Timer",
39 include_str!("../docs/directives/timer.txt")
40 .lines()
41 .collect(),
42 );
43 map.insert(
44 "Socket",
45 include_str!("../docs/directives/socket.txt")
46 .lines()
47 .collect(),
48 );
49 map.insert(
50 "Mount",
51 include_str!("../docs/directives/mount.txt")
52 .lines()
53 .collect(),
54 );
55 map.insert(
56 "Path",
57 include_str!("../docs/directives/path.txt")
58 .lines()
59 .collect(),
60 );
61 map.insert(
62 "Swap",
63 include_str!("../docs/directives/swap.txt")
64 .lines()
65 .collect(),
66 );
67 map.insert(
68 "Automount",
69 include_str!("../docs/directives/automount.txt")
70 .lines()
71 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
72 .collect(),
73 );
74 map.insert(
75 "Slice",
76 include_str!("../docs/directives/slice.txt")
77 .lines()
78 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
79 .collect(),
80 );
81 map.insert(
82 "Scope",
83 include_str!("../docs/directives/scope.txt")
84 .lines()
85 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
86 .collect(),
87 );
88 map.insert(
89 "Container",
90 include_str!("../docs/directives/container.txt")
91 .lines()
92 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
93 .collect(),
94 );
95 map.insert(
96 "Pod",
97 include_str!("../docs/directives/pod.txt")
98 .lines()
99 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
100 .collect(),
101 );
102 map.insert(
103 "Volume",
104 include_str!("../docs/directives/volume.txt")
105 .lines()
106 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
107 .collect(),
108 );
109 map.insert(
110 "Network",
111 include_str!("../docs/directives/network.txt")
112 .lines()
113 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
114 .collect(),
115 );
116 map.insert(
117 "Kube",
118 include_str!("../docs/directives/kube.txt")
119 .lines()
120 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
121 .collect(),
122 );
123 map.insert(
124 "Build",
125 include_str!("../docs/directives/build.txt")
126 .lines()
127 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
128 .collect(),
129 );
130 map.insert(
131 "Image",
132 include_str!("../docs/directives/image.txt")
133 .lines()
134 .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
135 .collect(),
136 );
137
138 map
139 }
140
141 pub fn directive_descriptions() -> HashMap<(&'static str, &'static str), &'static str> {
142 let mut map = HashMap::new();
143
144 map.insert(
146 ("Unit", "Description"),
147 include_str!("../docs/directives/unit/description.txt"),
148 );
149 map.insert(
150 ("Unit", "Wants"),
151 include_str!("../docs/directives/unit/wants.txt"),
152 );
153
154 map.insert(
156 ("Service", "Type"),
157 include_str!("../docs/directives/service/type.txt"),
158 );
159 map.insert(
160 ("Service", "ExecStart"),
161 include_str!("../docs/directives/service/execstart.txt"),
162 );
163
164 map.insert(
166 ("Install", "WantedBy"),
167 include_str!("../docs/directives/install/wantedby.txt"),
168 );
169
170 map
171 }
172
173 pub fn valid_values() -> HashMap<&'static str, &'static [&'static str]> {
174 let mut map = HashMap::new();
175
176 map.insert(
178 "Restart",
179 &[
180 "no",
181 "on-success",
182 "on-failure",
183 "on-abnormal",
184 "on-watchdog",
185 "on-abort",
186 "always",
187 ] as &[&str],
188 );
189 map.insert(
190 "ProtectSystem",
191 &["true", "false", "strict", "full", "yes", "no"] as &[&str],
192 );
193 map.insert(
194 "ProtectHome",
195 &["true", "false", "read-only", "tmpfs", "yes", "no"] as &[&str],
196 );
197
198 let boolean_values = &["true", "false", "yes", "no", "1", "0"] as &[&str];
200 map.insert("NoNewPrivileges", boolean_values);
201 map.insert("PrivateDevices", boolean_values);
202 map.insert("PrivateNetwork", boolean_values);
203 map.insert("PrivateUsers", boolean_values);
204 map.insert("PrivateMounts", boolean_values);
205 map.insert("ProtectKernelTunables", boolean_values);
206 map.insert("ProtectKernelModules", boolean_values);
207 map.insert("ProtectKernelLogs", boolean_values);
208 map.insert("ProtectControlGroups", boolean_values);
209 map.insert("RestrictRealtime", boolean_values);
210 map.insert("RestrictSUIDSGID", boolean_values);
211 map.insert("RemoveIPC", boolean_values);
212 map.insert("DynamicUser", boolean_values);
213 map.insert("MountAPIVFS", boolean_values);
214 map.insert("LockPersonality", boolean_values);
215 map.insert("MemoryDenyWriteExecute", boolean_values);
216 map.insert("ProtectHostname", boolean_values);
217 map.insert("ProtectClock", boolean_values);
218 map.insert("PrivatePIDs", boolean_values);
219
220 map.insert(
222 "PrivateTmp",
223 &["true", "false", "yes", "no", "1", "0", "disconnected"] as &[&str],
224 );
225
226 map.insert("DevicePolicy", &["auto", "closed", "strict"] as &[&str]);
228
229 map.insert(
231 "ProtectProc",
232 &["noaccess", "invisible", "ptraceable", "default"] as &[&str],
233 );
234
235 map.insert("NotifyAccess", &["none", "main", "exec", "all"] as &[&str]);
237
238 let standard_io_values = &[
239 "inherit",
240 "null",
241 "tty",
242 "journal",
243 "kmsg",
244 "journal+console",
245 "kmsg+console",
246 "file:",
247 "append:",
248 "truncate:",
249 "socket",
250 ] as &[&str];
251 map.insert("StandardOutput", standard_io_values);
252 map.insert("StandardError", standard_io_values);
253
254 map
255 }
256
257 pub fn valid_values_for_section(
258 section: &str,
259 directive: &str,
260 ) -> Option<&'static [&'static str]> {
261 match (section, directive) {
262 ("Service", "Type") => Some(&[
263 "simple",
264 "exec",
265 "forking",
266 "oneshot",
267 "dbus",
268 "notify",
269 "notify-reload",
270 "idle",
271 ]),
272 ("Mount", "Type") => Some(&[
273 "ext4", "ext3", "ext2", "xfs", "btrfs", "vfat", "ntfs", "exfat", "iso9660",
274 "tmpfs", "proc", "sysfs", "devpts", "nfs", "nfs4", "cifs", "sshfs", "bind",
275 "overlay", "squashfs", "fuse", "none", "auto",
276 ]),
277 _ => {
278 let global_values = Self::valid_values();
280 global_values.get(directive).copied()
281 }
282 }
283 }
284
285 pub fn section_documentation() -> HashMap<&'static str, &'static str> {
286 let mut map = HashMap::new();
287
288 map.insert("Unit", include_str!("../docs/unit.md"));
290 map.insert("Service", include_str!("../docs/service.md"));
291 map.insert("Install", include_str!("../docs/install.md"));
292 map.insert("Timer", include_str!("../docs/timer.md"));
293 map.insert("Socket", include_str!("../docs/socket.md"));
294 map.insert("Mount", include_str!("../docs/mount.md"));
295 map.insert("Path", include_str!("../docs/path.md"));
296 map.insert("Swap", include_str!("../docs/swap.md"));
297 map.insert("Container", include_str!("../docs/container.md"));
298 map.insert("Pod", include_str!("../docs/pod.md"));
299 map.insert("Volume", include_str!("../docs/volume.md"));
300 map.insert("Network", include_str!("../docs/network.md"));
301 map.insert("Kube", include_str!("../docs/kube.md"));
302 map.insert("Build", include_str!("../docs/build.md"));
303 map.insert("Image", include_str!("../docs/image.md"));
304
305 map.insert("Automount", include_str!("../docs/automount.md"));
306 map.insert("Slice", include_str!("../docs/slice.md"));
307 map.insert("Scope", include_str!("../docs/scope.md"));
308
309 map
310 }
311
312 pub fn shared_documentation() -> HashMap<&'static str, &'static str> {
315 let mut map = HashMap::new();
316 map.insert("exec", include_str!("../docs/exec.md"));
317 map.insert("kill", include_str!("../docs/kill.md"));
318 map.insert("resource-control", include_str!("../docs/resource-control.md"));
319 map
320 }
321
322 pub fn section_shared_docs(section: &str) -> Vec<&'static str> {
325 match section.to_lowercase().as_str() {
326 "service" => vec!["exec", "kill", "resource-control"],
327 "socket" => vec!["exec", "kill", "resource-control"],
328 "mount" => vec!["exec", "kill", "resource-control"],
329 "swap" => vec!["exec", "kill", "resource-control"],
330 "scope" => vec!["kill", "resource-control"],
331 "slice" => vec!["resource-control"],
332 _ => vec![],
333 }
334 }
335
336 pub const APP_NAME: &'static str = "systemdls";
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_valid_sections_not_empty() {
345 let sections = SystemdConstants::valid_sections();
346 assert!(!sections.is_empty());
347
348 assert!(sections.contains(&"Unit"));
350 assert!(sections.contains(&"Service"));
351 assert!(sections.contains(&"Install"));
352 }
353
354 #[test]
355 fn test_section_directives_contains_expected_sections() {
356 let directives = SystemdConstants::section_directives();
357
358 assert!(directives.contains_key("Unit"));
360 assert!(directives.contains_key("Service"));
361 assert!(directives.contains_key("Install"));
362 assert!(directives.contains_key("Timer"));
363 assert!(directives.contains_key("Socket"));
364 assert!(directives.contains_key("Mount"));
365 assert!(directives.contains_key("Path"));
366 assert!(directives.contains_key("Swap"));
367 assert!(directives.contains_key("Automount"));
368 assert!(directives.contains_key("Slice"));
369 assert!(directives.contains_key("Scope"));
370 }
371
372 #[test]
373 fn test_section_directives_contain_valid_directives() {
374 let directives = SystemdConstants::section_directives();
375
376 let unit_directives = directives.get("Unit").unwrap();
378 assert!(!unit_directives.is_empty());
379 assert!(unit_directives.contains(&"Description"));
380
381 let service_directives = directives.get("Service").unwrap();
383 assert!(!service_directives.is_empty());
384 assert!(service_directives.contains(&"Type"));
385 assert!(service_directives.contains(&"ExecStart"));
386
387 let install_directives = directives.get("Install").unwrap();
389 assert!(!install_directives.is_empty());
390 assert!(install_directives.contains(&"WantedBy"));
391 }
392
393 #[test]
394 fn test_directive_descriptions_contain_expected_entries() {
395 let descriptions = SystemdConstants::directive_descriptions();
396
397 assert!(descriptions.contains_key(&("Unit", "Description")));
399 assert!(descriptions.contains_key(&("Unit", "Wants")));
400 assert!(descriptions.contains_key(&("Service", "Type")));
401 assert!(descriptions.contains_key(&("Service", "ExecStart")));
402 assert!(descriptions.contains_key(&("Install", "WantedBy")));
403
404 assert!(!descriptions[&("Unit", "Description")].is_empty());
406 assert!(!descriptions[&("Service", "Type")].is_empty());
407 }
408
409 #[test]
410 fn test_valid_values_for_type_directive() {
411 let service_type_values =
413 SystemdConstants::valid_values_for_section("Service", "Type").unwrap();
414 assert!(service_type_values.contains(&"simple"));
415 assert!(service_type_values.contains(&"exec"));
416 assert!(service_type_values.contains(&"forking"));
417 assert!(service_type_values.contains(&"oneshot"));
418 assert!(service_type_values.contains(&"dbus"));
419 assert!(service_type_values.contains(&"notify"));
420 assert!(service_type_values.contains(&"idle"));
421
422 let mount_type_values =
424 SystemdConstants::valid_values_for_section("Mount", "Type").unwrap();
425 assert!(mount_type_values.contains(&"ext4"));
426 assert!(mount_type_values.contains(&"exfat"));
427 assert!(mount_type_values.contains(&"ntfs"));
428 assert!(mount_type_values.contains(&"tmpfs"));
429 }
430
431 #[test]
432 fn test_valid_values_for_restart_directive() {
433 let valid_values = SystemdConstants::valid_values();
434
435 let restart_values = valid_values.get("Restart").unwrap();
436 assert!(restart_values.contains(&"no"));
437 assert!(restart_values.contains(&"on-success"));
438 assert!(restart_values.contains(&"on-failure"));
439 assert!(restart_values.contains(&"always"));
440 }
441
442 #[test]
443 fn test_valid_values_for_boolean_directives() {
444 let valid_values = SystemdConstants::valid_values();
445
446 let boolean_directives = [
447 "NoNewPrivileges",
448 "PrivateTmp",
449 "PrivateDevices",
450 "PrivateNetwork",
451 "DynamicUser",
452 ];
453
454 for directive in &boolean_directives {
455 let values = valid_values.get(directive).unwrap();
456 assert!(values.contains(&"true"));
457 assert!(values.contains(&"false"));
458 assert!(values.contains(&"yes"));
459 assert!(values.contains(&"no"));
460 assert!(values.contains(&"1"));
461 assert!(values.contains(&"0"));
462 }
463 }
464
465 #[test]
466 fn test_valid_values_for_standard_io() {
467 let valid_values = SystemdConstants::valid_values();
468
469 let standard_output = valid_values.get("StandardOutput").unwrap();
470 assert!(standard_output.contains(&"inherit"));
471 assert!(standard_output.contains(&"null"));
472 assert!(standard_output.contains(&"journal"));
473 assert!(standard_output.contains(&"file:"));
474
475 let standard_error = valid_values.get("StandardError").unwrap();
476 assert!(standard_error.contains(&"inherit"));
477 assert!(standard_error.contains(&"null"));
478 assert!(standard_error.contains(&"journal"));
479 }
480
481 #[test]
482 fn test_section_documentation_not_empty() {
483 let docs = SystemdConstants::section_documentation();
484
485 assert!(docs.contains_key("Unit"));
487 assert!(docs.contains_key("Service"));
488 assert!(docs.contains_key("Install"));
489 assert!(docs.contains_key("Automount"));
490 assert!(docs.contains_key("Slice"));
491 assert!(docs.contains_key("Scope"));
492
493 assert!(!docs["Unit"].is_empty());
495 assert!(!docs["Service"].is_empty());
496 assert!(!docs["Install"].is_empty());
497 assert!(!docs["Automount"].is_empty());
498 assert!(!docs["Slice"].is_empty());
499 assert!(!docs["Scope"].is_empty());
500 }
501
502 #[test]
503 fn test_app_name_constant() {
504 assert_eq!(SystemdConstants::APP_NAME, "systemdls");
505 }
506
507 #[test]
508 fn test_protect_system_values() {
509 let valid_values = SystemdConstants::valid_values();
510 let protect_system = valid_values.get("ProtectSystem").unwrap();
511
512 assert!(protect_system.contains(&"true"));
513 assert!(protect_system.contains(&"false"));
514 assert!(protect_system.contains(&"strict"));
515 assert!(protect_system.contains(&"full"));
516 assert!(protect_system.contains(&"yes"));
517 assert!(protect_system.contains(&"no"));
518 }
519
520 #[test]
521 fn test_protect_home_values() {
522 let valid_values = SystemdConstants::valid_values();
523 let protect_home = valid_values.get("ProtectHome").unwrap();
524
525 assert!(protect_home.contains(&"true"));
526 assert!(protect_home.contains(&"false"));
527 assert!(protect_home.contains(&"read-only"));
528 assert!(protect_home.contains(&"tmpfs"));
529 assert!(protect_home.contains(&"yes"));
530 assert!(protect_home.contains(&"no"));
531 }
532}