1use itertools::Itertools; use std::collections::{BTreeSet, HashMap};
16use std::io::prelude::*;
17use std::path::{Path, PathBuf};
18use std::str;
19
20use crate::assets::Asset;
21use crate::dh::dh_lib::{autoscript, pkgfile, ScriptFragments};
22use crate::listener::Listener;
23use crate::util::{fname_from_path, MyJoin};
24use crate::{CDResult, CargoDebError};
25
26const LIB_SYSTEMD_SYSTEM_DIR: &str = "usr/lib/systemd/system/";
42const USR_LIB_TMPFILES_D_DIR: &str = "usr/lib/tmpfiles.d/";
43const SYSTEMD_UNIT_FILE_INSTALL_MAPPINGS: [(&str, &str, &str); 12] = [
44 ("", "mount", LIB_SYSTEMD_SYSTEM_DIR),
45 ("", "path", LIB_SYSTEMD_SYSTEM_DIR),
46 ("@", "path", LIB_SYSTEMD_SYSTEM_DIR),
47 ("", "service", LIB_SYSTEMD_SYSTEM_DIR),
48 ("@", "service", LIB_SYSTEMD_SYSTEM_DIR),
49 ("", "socket", LIB_SYSTEMD_SYSTEM_DIR),
50 ("@", "socket", LIB_SYSTEMD_SYSTEM_DIR),
51 ("", "target", LIB_SYSTEMD_SYSTEM_DIR),
52 ("@", "target", LIB_SYSTEMD_SYSTEM_DIR),
53 ("", "timer", LIB_SYSTEMD_SYSTEM_DIR),
54 ("@", "timer", LIB_SYSTEMD_SYSTEM_DIR),
55 ("", "tmpfile", USR_LIB_TMPFILES_D_DIR),
56];
57
58#[derive(Debug, PartialEq, Eq)]
59pub struct InstallRecipe {
60 pub path: PathBuf,
61 pub mode: u32,
62}
63
64pub type PackageUnitFiles = HashMap<PathBuf, InstallRecipe>;
65
66#[derive(Default, Debug)]
120pub struct Options {
121 pub no_enable: bool,
122 pub no_start: bool,
123 pub restart_after_upgrade: bool,
124 pub no_stop_on_upgrade: bool,
125}
126
127pub fn find_units(dir: &Path, main_package: &str, unit_name: Option<&str>) -> PackageUnitFiles {
137 let mut installables = HashMap::new();
138
139 for (package_suffix, unit_type, install_dir) in &SYSTEMD_UNIT_FILE_INSTALL_MAPPINGS {
140 let package_name = &format!("{main_package}{package_suffix}");
141 if let Some(src_path) = pkgfile(dir, main_package, package_name, unit_type, unit_name) {
142 let actual_suffix = match &unit_type[..] {
146 "tmpfile" => "conf",
147 _ => unit_type,
148 };
149
150 let install_filename = if let Some(unit_name) = unit_name {
153 format!("{unit_name}{package_suffix}.{actual_suffix}")
154 } else {
155 format!("{package_name}.{actual_suffix}")
156 };
157
158 let install_path = Path::new(install_dir).join(install_filename);
160
161 installables.insert(src_path, InstallRecipe {
164 path: install_path,
165 mode: 0o644,
166 });
167 }
168 }
169
170 installables
171}
172
173fn is_comment(s: &str) -> bool {
178 matches!(s.chars().next(), Some('#' | ';'))
179}
180
181fn is_unit_of(fname: &str, unit_name: &str) -> bool {
186 fname.rsplit_once('.').is_some_and(|(stem, _)| stem == unit_name)
187}
188
189fn unquote(s: &str) -> &str {
195 if s.len() > 1 &&
196 ((s.starts_with('"') && s.ends_with('"')) ||
197 (s.starts_with('\'') && s.ends_with('\''))) {
198 &s[1..s.len()-1]
199 } else {
200 s
201 }
202}
203
204pub fn generate(package: &str, assets: &[Asset], unit_name: Option<&str>, options: &Options, scripts: &mut ScriptFragments, listener: &dyn Listener) -> CDResult<()> {
231 let tmp_file_names = assets
236 .iter()
237 .filter(|a| a.c.target_path.starts_with(USR_LIB_TMPFILES_D_DIR))
238 .filter(|a| match unit_name {
239 Some(unit_name) => fname_from_path(a.c.target_path.as_path())
240 .is_some_and(|fname| fname == unit_name || is_unit_of(&fname, unit_name)),
241 None => true,
242 })
243 .map(|v| {
244 v.source.source_path()
245 .and_then(|p| fname_from_path(&p.with_extension("conf")))
246 .ok_or(CargoDebError::Str("dh_installsystemd: invalid source path"))
247 })
248 .collect::<CDResult<Vec<String>>>()?
249 .join(" ");
250
251 if !tmp_file_names.is_empty() {
252 autoscript(scripts, package, "postinst", "postinst-init-tmpfiles",
253 &map!{ "TMPFILES" => tmp_file_names }, false, listener)?;
254 }
255
256 let mut installed_non_template_units: BTreeSet<String> = BTreeSet::new();
264 installed_non_template_units.extend(
265 assets
266 .iter()
267 .filter(|a| a.c.target_path.parent() == Some(LIB_SYSTEMD_SYSTEM_DIR.as_ref()))
268 .filter_map(|a| fname_from_path(a.c.target_path.as_path()))
269 .filter(|fname| !fname.contains('@'))
270 .filter(|fname| match unit_name {
271 Some(unit_name) => is_unit_of(fname, unit_name),
272 None => true,
273 }),
274 );
275
276 let mut enable_units = BTreeSet::new();
280 let mut start_units = BTreeSet::new();
281 let mut seen = BTreeSet::new();
282
283 let mut units = installed_non_template_units;
286
287 while !units.is_empty() {
291 let mut also_units = BTreeSet::<String>::new();
293
294 for unit in &units {
296 listener.progress("Checking", format!("augmentations needed for systemd unit {unit}"));
297
298 start_units.insert(unit.clone());
300
301 let needle = Path::new(LIB_SYSTEMD_SYSTEM_DIR).join(unit);
303 let data = assets.iter().find(move |&item| item.c.target_path == needle).unwrap().source.data()?;
304 let reader = data.into_owned();
305
306 for line in reader.lines().map(|line| line.unwrap()).filter(|s| !is_comment(s)) {
324 let possible_kv_pair = line.splitn(2, '=').map(|s| s.trim()).next_tuple();
325 if let Some((key, value)) = possible_kv_pair {
326 let other_unit = unquote(value).to_string();
327 match key {
328 "Also" => {
329 if seen.insert(other_unit.clone()) {
338 also_units.insert(other_unit);
339 }
340 },
341 "Alias" => {
342 },
344 _ => (),
345 }
346 } else if line.starts_with("[Install]") {
347 enable_units.insert(unit.clone());
348 }
349 }
350 }
351 units = also_units;
352 }
353
354 if !enable_units.is_empty() {
358 let snippet = if options.no_enable { "postinst-systemd-dont-enable" } else { "postinst-systemd-enable" };
359 for unit in &enable_units {
360 autoscript(scripts, package, "postinst", snippet,
361 &map!{ "UNITFILE" => unit.clone() }, true, listener)?;
362 }
363 autoscript(scripts, package, "postrm", "postrm-systemd",
364 &map!{ "UNITFILES" => enable_units.join(" ") }, false, listener)?;
365 }
366
367 if !start_units.is_empty() {
371 let mut replace = map! { "UNITFILES" => start_units.join(" ") };
372
373 if options.restart_after_upgrade {
374 let snippet = if options.no_start {
375 replace.insert("RESTART_ACTION", "try-restart".into());
376 "postinst-systemd-restartnostart"
377 } else {
378 replace.insert("RESTART_ACTION", "restart".into());
379 "postinst-systemd-restart"
380 };
381 autoscript(scripts, package, "postinst", snippet, &replace, true, listener)?;
382 } else if !options.no_start {
383 autoscript(scripts, package, "postinst", "postinst-systemd-start", &replace, true, listener)?;
385 }
386
387 if options.no_stop_on_upgrade || options.restart_after_upgrade {
388 autoscript(scripts, package, "prerm", "prerm-systemd-restart", &replace, true, listener)?;
390 } else if !options.no_start {
391 autoscript(scripts, package, "prerm", "prerm-systemd", &replace, true, listener)?;
393 }
394
395 autoscript(scripts, package, "postrm", "postrm-systemd-reload-only", &replace, false, listener)?;
398 }
399
400 Ok(())
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use crate::assets::{Asset, AssetKind, AssetSource, IsBuilt};
407 use crate::util::tests::{add_test_fs_paths, get_read_count, set_test_fs_path_content};
408 use rstest::*;
409
410 #[test]
411 fn is_comment_detects_comments() {
412 assert!(is_comment("#"));
413 assert!(is_comment("# "));
414 assert!(is_comment("# some comment"));
415 assert!(is_comment(";"));
416 assert!(is_comment("; "));
417 assert!(is_comment("; some comment"));
418 }
419
420 #[test]
421 fn is_comment_detects_non_comments() {
422 assert!(!is_comment(" #"));
423 assert!(!is_comment(" # "));
424 assert!(!is_comment(" # some comment"));
425 assert!(!is_comment(" ;"));
426 assert!(!is_comment(" ; "));
427 assert!(!is_comment(" ; some comment"));
428 }
429
430 #[test]
431 fn unquote_unquotes_matching_single_quotes() {
432 assert_eq!("", unquote("''"));
433 assert_eq!("a", unquote("'a'"));
434 assert_eq!("ab", unquote("'ab'"));
435 }
436
437 #[test]
438 fn unquote_unquotes_matching_double_quotes() {
439 assert_eq!("", unquote(r#""""#));
440 assert_eq!("a", unquote(r#""a""#));
441 assert_eq!("ab", unquote(r#""ab""#));
442 }
443
444 #[test]
445 fn unquote_ignores_embedded_quotes() {
446 assert_eq!("a'b", unquote("'a'b'"));
447 assert_eq!(r#"a"b"#, unquote(r#"'a"b'"#));
448 assert_eq!(r#"a"b"#, unquote(r#""a"b""#));
449 assert_eq!(r"a'b", unquote(r#""a'b""#));
450 }
451
452 #[test]
453 fn unquote_ignores_partial_quotes() {
454 assert_eq!("'", unquote("'"));
455 assert_eq!("'ab", unquote("'ab"));
456 assert_eq!("ab'", unquote("ab'"));
457 assert_eq!("'ab'ab", unquote("'ab'ab"));
458 assert_eq!("ab'ab'", unquote("ab'ab'"));
459 assert_eq!(r#"""#, unquote(r#"""#));
460 assert_eq!(r#""ab"#, unquote(r#""ab"#));
461 assert_eq!(r#"ab""#, unquote(r#"ab""#));
462 assert_eq!(r#""ab"ab"#, unquote(r#""ab"ab"#));
463 assert_eq!(r#"ab"ab""#, unquote(r#"ab"ab""#));
464 }
465
466 #[test]
467 fn unquote_ignores_mismatched_quotes() {
468 assert_eq!(r#""'"#, unquote(r#""'"#));
469 assert_eq!(r#"'""#, unquote(r#"'""#));
470 assert_eq!(r#""a'"#, unquote(r#""a'"#));
471 assert_eq!(r#"'a""#, unquote(r#"'a""#));
472 assert_eq!(r#""ab'"#, unquote(r#""ab'"#));
473 assert_eq!(r#"'ab""#, unquote(r#"'ab""#));
474 }
475
476 #[test]
477 fn find_units_in_empty_dir_finds_nothing() {
478 let pkg_unit_files = find_units(Path::new(""), "mypkg", None);
479 assert!(pkg_unit_files.is_empty());
480 }
481
482 fn assert_eq_found_unit(pkg_unit_files: &PackageUnitFiles, expected_install_path: &str, source_path: &str) {
483 let expected = InstallRecipe {
484 path: PathBuf::from(expected_install_path),
485 mode: 0o644,
486 };
487 let actual = pkg_unit_files.get(&PathBuf::from(source_path)).unwrap();
488 assert_eq!(&expected, actual);
489 }
490
491 #[test]
492 fn find_units_for_package() {
493 let _g = add_test_fs_paths(&[
497 "debian/mypkg.mount",
498 "debian/mypkg@.path",
499 "debian/service", "debian/mypkg@.socket",
501 "debian/mypkg.target",
502 "debian/mypkg@.timer",
503 "debian/mypkg.tmpfile",
504 "debian/mypkg.myunit.service", ]);
506 let pkg_unit_files = find_units(Path::new("debian"), "mypkg", None);
507 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/mypkg.mount", "debian/mypkg.mount");
508 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/mypkg@.path", "debian/mypkg@.path");
509 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/mypkg.service", "debian/service");
510 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/mypkg@.socket", "debian/mypkg@.socket");
511 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/mypkg.target", "debian/mypkg.target");
512 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/mypkg@.timer", "debian/mypkg@.timer");
513 assert_eq_found_unit(&pkg_unit_files, "usr/lib/tmpfiles.d/mypkg.conf", "debian/mypkg.tmpfile");
514 assert_eq!(7, pkg_unit_files.len());
515 }
516
517 #[test]
518 fn find_named_units_for_package() {
519 let _g = add_test_fs_paths(&[
523 "debian/mypkg.myunit.mount",
524 "debian/mypkg@.myunit.path",
525 "debian/service", "debian/mypkg@.myunit.socket",
527 "debian/target", "debian/mypkg@.myunit.timer",
529 "debian/mypkg.tmpfile", "debian/mypkg.myunit.service", ]);
532
533 let _g = add_test_fs_paths(&[
535 "debian/nested/dir/mykpg.myunit.mount",
536 "debian/README.md",
537 "mypkg.myunit.mount",
538 "mypkg.mount",
539 "mount",
540 "postinit",
541 "mypkg.postinit",
542 "mypkg.myunit.postinit",
543 ]);
544
545 let pkg_unit_files = find_units(Path::new("debian"), "mypkg", Some("myunit"));
546 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/myunit.mount", "debian/mypkg.myunit.mount");
548 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/myunit@.path", "debian/mypkg@.myunit.path");
549 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/myunit.service", "debian/mypkg.myunit.service");
550 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/myunit@.socket", "debian/mypkg@.myunit.socket");
551 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/myunit.target", "debian/target");
552 assert_eq_found_unit(&pkg_unit_files, "usr/lib/systemd/system/myunit@.timer", "debian/mypkg@.myunit.timer");
553
554 assert_eq_found_unit(&pkg_unit_files, "usr/lib/tmpfiles.d/myunit.conf", "debian/mypkg.tmpfile");
556
557 assert_eq!(7, pkg_unit_files.len());
558 }
559
560 #[test]
561 fn generate_with_empty_inputs_does_nothing() {
562 let mut mock_listener = crate::listener::MockListener::new();
563 mock_listener.expect_info().times(0).return_const(());
564
565 let mut fragments = ScriptFragments::new();
566 generate("", &[], None, &Options::default(), &mut fragments, &mock_listener).unwrap();
567
568 assert!(fragments.is_empty());
569 }
570
571 #[test]
572 fn generate_with_arbitrary_asset_does_nothing() {
573 let mut mock_listener = crate::listener::MockListener::new();
574 mock_listener.expect_info().times(0).return_const(());
575
576 let assets = vec![Asset::new(
577 AssetSource::Path(PathBuf::new()),
578 PathBuf::new(),
579 Some(0o0),
580 IsBuilt::No,
581 AssetKind::Any,
582 )];
583
584 let mut fragments = ScriptFragments::new();
585 generate("mypkg", &assets, None, &Options::default(), &mut fragments, &mock_listener).unwrap();
586 assert!(fragments.is_empty());
587 }
588
589 #[test]
590 fn generate_with_invalid_tmp_file_asset_fails() {
591 let mut mock_listener = crate::listener::MockListener::new();
592 mock_listener.expect_info().times(0).return_const(());
593
594 let assets = vec![Asset::new(
595 AssetSource::Path(PathBuf::new()), Path::new("usr/lib/tmpfiles.d/blah").to_path_buf(),
597 Some(0o0),
598 IsBuilt::No,
599 AssetKind::Any,
600 )];
601
602 assert!(generate("mypkg", &assets, None, &Options::default(), &mut ScriptFragments::new(), &mock_listener).is_err());
603 }
604
605 #[test]
606 fn generate_with_data_tmp_file_asset_fails() {
607 let mut mock_listener = crate::listener::MockListener::new();
608 mock_listener.expect_info().times(0).return_const(());
609
610 let assets = vec![Asset::new(
611 AssetSource::Data(vec![]), Path::new("usr/lib/tmpfiles.d/blah").to_path_buf(),
613 Some(0o0),
614 IsBuilt::No,
615 AssetKind::Any,
616 )];
617
618 assert!(generate("mypkg", &assets, None, &Options::default(), &mut ScriptFragments::new(), &mock_listener).is_err());
619 }
620
621 #[test]
622 fn generate_with_empty_tmp_file_asset() {
623 use crate::dh::dh_lib::get_embedded_autoscript;
624
625 const TMP_FILE_NAME: &str = "my_tmp_file.tmpfile";
626 let tmp_file_path = PathBuf::from(format!("debian/{TMP_FILE_NAME}"));
627
628 let mut mock_listener = crate::listener::MockListener::new();
629 mock_listener.expect_progress().times(1).return_const(());
630
631 let assets = vec![Asset::new(
632 AssetSource::Path(tmp_file_path),
633 Path::new("usr/lib/tmpfiles.d/blah").to_path_buf(),
634 Some(0o0),
635 IsBuilt::No,
636 AssetKind::Any,
637 )];
638
639 let mut fragments = ScriptFragments::new();
640 generate("mypkg", &assets, None, &Options::default(), &mut fragments, &mock_listener).unwrap();
641 assert_eq!(1, fragments.len());
642
643 let (fragment_name, created_text) = fragments.into_iter().next().unwrap();
644
645 assert_eq!("mypkg.postinst.debhelper", fragment_name);
647
648 let autoscript_text = get_embedded_autoscript("postinst-init-tmpfiles");
654 let autoscript_line_count = autoscript_text.lines().count();
655 let created_line_count = created_text.lines().count();
656 assert_eq!(autoscript_line_count + 2, created_line_count);
657
658 let mut lines = created_text.lines();
660 assert!(lines.next().unwrap().starts_with("# Automatically added by"));
661 assert_eq!(lines.nth_back(0).unwrap(), "# End automatically added section");
662
663 let expected_autoscript_text = autoscript_text.replace("#TMPFILES#", TMP_FILE_NAME.replace(".tmpfile", ".conf").as_str());
666 let expected_autoscript_text = expected_autoscript_text.trim_end();
667 let start1 = 1;
668 let end1 = start1 + autoscript_line_count;
669 let created_autoscript_text = created_text.lines().collect::<Vec<&str>>()[start1..end1].join("\n");
670 assert_ne!(expected_autoscript_text, autoscript_text);
671 assert_eq!(expected_autoscript_text, created_autoscript_text);
672 }
673
674 #[test]
675 fn generate_filters_out_template_units() {
676 let mut mock_listener = crate::listener::MockListener::new();
680 mock_listener.expect_info().times(0).return_const(());
681
682 let assets = vec![Asset::new(
683 AssetSource::Path(PathBuf::from("debian/my_unit@.service")),
684 Path::new("usr/lib/systemd/system/").to_path_buf(),
685 Some(0o0),
686 IsBuilt::No,
687 AssetKind::Any,
688 )];
689
690 let mut fragments = ScriptFragments::new();
691 generate("mypkg", &assets, None, &Options::default(), &mut fragments, &mock_listener).unwrap();
692 assert_eq!(0, fragments.len());
693 }
694
695 #[test]
696 fn generate_filters_out_subdir() {
697 let mut mock_listener = crate::listener::MockListener::new();
698 mock_listener.expect_info().times(0).return_const(());
699
700 let assets = vec![Asset::new(
701 AssetSource::Path(PathBuf::from("debian/10-extra-hardening.conf")),
702 Path::new("usr/lib/systemd/system/foobar.service.d/").to_path_buf(),
703 Some(0o0),
704 IsBuilt::No,
705 AssetKind::Any,
706 )];
707
708 let mut fragments = ScriptFragments::new();
709 generate("mypkg", &assets, None, &Options::default(), &mut fragments, &mock_listener).unwrap();
710 assert_eq!(0, fragments.len());
711 }
712
713 #[test]
714 fn generate_acts_only_on_unit_files_with_the_expected_install_path() {
715 let mut mock_listener = crate::listener::MockListener::new();
717 mock_listener.expect_info().times(0).return_const(());
718
719 let assets = vec![Asset::new(
720 AssetSource::Path(PathBuf::from("debian/my_unit.service")),
721 Path::new("some/other/path/").to_path_buf(),
722 Some(0o0),
723 IsBuilt::No,
724 AssetKind::Any,
725 )];
726
727 let mut fragments = ScriptFragments::new();
728 generate("mypkg", &assets, None, &Options::default(), &mut fragments, &mock_listener).unwrap();
729 assert_eq!(0, fragments.len());
730 }
731
732 fn unit_asset(source: &'static str, target: &str) -> Asset {
733 let test_unit_file_content = "[Unit]
734Description=A test unit
735
736[Service]
737Type=simple".to_owned();
738
739 set_test_fs_path_content(source, test_unit_file_content);
740
741 Asset::new(
742 AssetSource::Path(PathBuf::from(source)),
743 Path::new(target).to_path_buf(),
744 Some(0o0),
745 IsBuilt::No,
746 AssetKind::Any,
747 )
748 }
749
750 #[test]
751 fn generate_scopes_actions_to_the_given_unit_name() {
752 let mut mock_listener = crate::listener::MockListener::new();
753 mock_listener.expect_progress().return_const(());
754
755 let _g = add_test_fs_paths(&[]);
756
757 let assets = vec![
758 unit_asset("debian/main.service", "usr/lib/systemd/system/main.service"),
759 unit_asset("debian/other.service", "usr/lib/systemd/system/other.service"),
760 ];
761
762 let mut fragments = ScriptFragments::new();
763 generate("mypkg", &assets, Some("other"), &Options::default(), &mut fragments, &mock_listener).unwrap();
764
765 let postinst = fragments.get("mypkg.postinst.service").unwrap();
766 assert!(postinst.contains("other.service"));
767 assert!(!postinst.contains("main.service"));
768 }
769
770 #[test]
771 fn generate_accumulates_fragments_with_per_entry_options() {
772 let mut mock_listener = crate::listener::MockListener::new();
773 mock_listener.expect_progress().return_const(());
774
775 let _g = add_test_fs_paths(&[]);
776
777 let assets = vec![
778 unit_asset("debian/main.service", "usr/lib/systemd/system/main.service"),
779 unit_asset("debian/other.service", "usr/lib/systemd/system/other.service"),
780 ];
781
782 let mut fragments = ScriptFragments::new();
783
784 let entries = [
785 (Some("main"), Options { restart_after_upgrade: true, ..Options::default() }),
787 (Some("other"), Options { restart_after_upgrade: true, no_start: true, ..Options::default() }),
789 ];
790
791 for (unit_name, options) in &entries {
792 generate("mypkg", &assets, *unit_name, options, &mut fragments, &mock_listener).unwrap();
793 }
794
795 let postinst = fragments.get("mypkg.postinst.service").unwrap();
796 assert!(postinst.contains("deb-systemd-invoke $_dh_action main.service"));
797 assert!(postinst.contains("deb-systemd-invoke try-restart other.service"));
799
800 assert!(!postinst.contains("try-restart main.service"));
801 assert!(!postinst.contains("$_dh_action other.service"));
802
803 let prerm = fragments.get("mypkg.prerm.service").unwrap();
805 assert!(prerm.contains("deb-systemd-invoke stop main.service"));
806 assert!(prerm.contains("deb-systemd-invoke stop other.service"));
807
808 let postrm = fragments.get("mypkg.postrm.debhelper").unwrap();
809 assert_eq!(1, postrm.matches("daemon-reload").count());
810 }
811
812 #[rstest(ip, inst, ne, rau, ns, nsou,
813 case("ult", false, false, false, false, false),
814
815 case("lss", false, false, false, false, false),
816 case("lss", false, false, false, false, true),
817 case("lss", false, false, false, true, false),
818 case("lss", false, false, false, true, true),
819 case("lss", false, false, true, false, false),
820 case("lss", false, false, true, false, true),
821 case("lss", false, false, true, true, false),
822 case("lss", false, false, true, true, true),
823 case("lss", false, true, false, false, false),
824 case("lss", false, true, false, false, true),
825 case("lss", false, true, false, true, false),
826 case("lss", false, true, false, true, true),
827 case("lss", false, true, true, false, false),
828 case("lss", false, true, true, false, true),
829 case("lss", false, true, true, true, false),
830 case("lss", false, true, true, true, true),
831 case("lss", true, false, false, false, false),
832 case("lss", true, false, false, false, true),
833 case("lss", true, false, false, true, false),
834 case("lss", true, false, false, true, true),
835 case("lss", true, false, true, false, false),
836 case("lss", true, false, true, false, true),
837 case("lss", true, false, true, true, false),
838 case("lss", true, false, true, true, true),
839 case("lss", true, true, false, false, false),
840 case("lss", true, true, false, false, true),
841 case("lss", true, true, false, true, false),
842 case("lss", true, true, false, true, true),
843 case("lss", true, true, true, false, false),
844 case("lss", true, true, true, false, true),
845 case("lss", true, true, true, true, false),
846 case("lss", true, true, true, true, true),
847 )]
848 #[test]
849 fn generate_creates_expected_autoscript_fragments(
850 ip: &str,
851 inst: bool,
852 ne: bool,
853 rau: bool,
854 ns: bool,
855 nsou: bool,
856 ) {
857 let unit_file_path = "debian/mypkg.service";
858
859 let install_base_path = match ip {
860 "ult" => "usr/lib/tmpfiles.d",
861 "lss" => "usr/lib/systemd/system",
862 x => panic!("Unsupported install path value '{x}'"),
863 };
864
865 let assets = vec![Asset::new(
867 AssetSource::Path(PathBuf::from(unit_file_path)),
868 format!("{install_base_path}/mypkg.service").into(),
869 Some(0o0),
870 IsBuilt::No,
871 AssetKind::Any,
872 )];
873
874 let options = Options {
875 no_enable: ne,
876 no_start: ns,
877 restart_after_upgrade: rau,
878 no_stop_on_upgrade: nsou,
879 };
880
881 let mut mock_listener = crate::listener::MockListener::new();
883 mock_listener.expect_progress().return_const(());
884
885 let mut unit_file_content = "[Unit]
889Description=A test unit
890
891[Service]
892Type=simple
893".to_owned();
894
895 if inst {
896 unit_file_content.push_str("[Install]
897WantedBy=multi-user.target");
898 }
899
900 set_test_fs_path_content(unit_file_path, unit_file_content);
901
902 let _g = add_test_fs_paths(&[
905 "postinst-init-tmpfiles",
906 "postinst-systemd-dont-enable",
907 "postinst-systemd-enable",
908 "postinst-systemd-restart",
909 "postinst-systemd-restartnostart",
910 "postinst-systemd-start",
911 "postrm-systemd",
912 "postrm-systemd-reload-only",
913 "prerm-systemd",
914 "prerm-systemd-restart",
915 ]);
916
917 let mut fragments = ScriptFragments::new();
919 generate("mypkg", &assets, None, &options, &mut fragments, &mock_listener).unwrap();
920
921 let mut autoscript_fragments_to_check_for = std::collections::HashSet::new();
962
963 match ip {
964 "ult" => {
965 assert_eq!(1, get_read_count("postinst-init-tmpfiles"));
966 autoscript_fragments_to_check_for.insert("postinst.debhelper");
967 },
968 "lss" => {
969 assert_eq!(1, get_read_count(unit_file_path));
970 if inst {
971 if options.no_enable {
972 assert_eq!(1, get_read_count("postinst-systemd-dont-enable"));
973 } else {
974 assert_eq!(1, get_read_count("postinst-systemd-enable"));
975 }
976 assert_eq!(1, get_read_count("postrm-systemd"));
977 autoscript_fragments_to_check_for.insert("postinst.service");
978 autoscript_fragments_to_check_for.insert("postrm.debhelper");
979 }
980 if options.restart_after_upgrade {
981 if options.no_start {
982 assert_eq!(1, get_read_count("postinst-systemd-restartnostart"));
983 } else {
984 assert_eq!(1, get_read_count("postinst-systemd-restart"));
985 }
986 autoscript_fragments_to_check_for.insert("postinst.service");
987 } else if !options.no_start {
988 assert_eq!(1, get_read_count("postinst-systemd-start"));
989 autoscript_fragments_to_check_for.insert("postinst.service");
990 }
991 if options.restart_after_upgrade || options.no_stop_on_upgrade {
992 assert_eq!(1, get_read_count("prerm-systemd-restart"));
993 autoscript_fragments_to_check_for.insert("prerm.service");
994 } else if !options.no_start {
995 assert_eq!(1, get_read_count("prerm-systemd"));
996 autoscript_fragments_to_check_for.insert("prerm.service");
997 }
998 assert_eq!(1, get_read_count("postrm-systemd-reload-only"));
999 autoscript_fragments_to_check_for.insert("postrm.debhelper");
1000 },
1001 _ => unreachable!(),
1002 }
1003
1004 for autoscript in &autoscript_fragments_to_check_for {
1005 let key = format!("mypkg.{autoscript}");
1006 assert!(fragments.contains_key(&key), "{}", key);
1007 }
1008 }
1009}