1use std::path::{Path, PathBuf};
13use crate::dh::dh_installsystemd::InstallRecipe;
14use std::str;
15
16use crate::assets::Asset;
17use crate::dh::dh_lib::{autoscript, pkgfile, ScriptFragments};
18use crate::listener::Listener;
19use crate::util::fname_from_path;
20use crate::{CDResult, CargoDebError};
21
22const SYSUSERS_D_DIR: &str = "usr/lib/sysusers.d/";
23
24pub type ConfigFile = Option<(PathBuf, InstallRecipe)>;
25
26pub fn find_config(dir: &Path, main_package: &str) -> ConfigFile {
31 let src_path = pkgfile(dir, main_package, main_package, "sysusers", None)?;
32
33 Some((src_path, InstallRecipe {
34 path: Path::new(SYSUSERS_D_DIR).join(format!("{main_package}.conf")),
35 mode: 0o644,
36 }))
37}
38
39pub fn generate(package: &str, assets: &[Asset], scripts: &mut ScriptFragments, listener: &dyn Listener) -> CDResult<()> {
40 let mut sysusers_files = assets
41 .iter()
42 .filter(|a| a.c.target_path.starts_with(SYSUSERS_D_DIR))
43 .map(|v| {
44 v.source.source_path()
45 .and_then(|p| fname_from_path(&p.with_extension("conf")))
46 .ok_or(CargoDebError::Str("dh_installsysusers: invalid source path"))
47 })
48 .collect::<CDResult<Vec<String>>>()?;
49
50 if sysusers_files.is_empty() {
51 return Ok(());
52 }
53
54 sysusers_files.sort();
55
56 autoscript(scripts, package, "postinst", "postinst-sysusers",
57 &map!{ "CONFILE_BASENAME" => sysusers_files.join(" ") }, false, listener)?;
58
59 Ok(())
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::assets::{Asset, AssetKind, AssetSource, IsBuilt};
66 use crate::util::tests::{add_test_fs_paths, get_read_count, set_test_fs_path_content};
67
68 #[test]
69 fn find_units_in_empty_dir_finds_nothing() {
70 let pkg_config_file = find_config(Path::new(""), "mypkg");
71 assert_eq!(None, pkg_config_file);
72 }
73
74 fn assert_eq_found_config(pkg_config_file: &ConfigFile, expected_install_path: &str, source_path: &str) {
75 let expected = InstallRecipe {
76 path: PathBuf::from(expected_install_path),
77 mode: 0o644,
78 };
79 assert_eq!(*pkg_config_file, Some((PathBuf::from(source_path), expected)));
80 }
81
82 #[test]
83 fn find_config_for_package() {
84 let _g = add_test_fs_paths(&[
88 "debian/mypkg.sysusers",
89 ]);
90 let pkg_config_file = find_config(Path::new("debian"), "mypkg");
91 assert_eq_found_config(&pkg_config_file, "usr/lib/sysusers.d/mypkg.conf", "debian/mypkg.sysusers");
92 }
93
94 #[test]
95 fn generate_with_empty_inputs_does_nothing() {
96 let mut mock_listener = crate::listener::MockListener::new();
97 mock_listener.expect_info().times(0).return_const(());
98
99 let mut fragments = ScriptFragments::new();
100 generate("", &[], &mut fragments, &mock_listener).unwrap();
101
102 assert!(fragments.is_empty());
103 }
104
105 #[test]
106 fn generate_with_arbitrary_asset_does_nothing() {
107 let mut mock_listener = crate::listener::MockListener::new();
108 mock_listener.expect_info().times(0).return_const(());
109
110 let assets = vec![Asset::new(
111 AssetSource::Path(PathBuf::new()),
112 PathBuf::new(),
113 Some(0o0),
114 IsBuilt::No,
115 AssetKind::Any,
116 )];
117
118 let mut fragments = ScriptFragments::new();
119 generate("mypkg", &assets, &mut fragments, &mock_listener).unwrap();
120 assert!(fragments.is_empty());
121 }
122
123 #[test]
124 fn generate_with_invalid_tmp_file_asset_fails() {
125 let mut mock_listener = crate::listener::MockListener::new();
126 mock_listener.expect_info().times(0).return_const(());
127
128 let assets = vec![Asset::new(
129 AssetSource::Path(PathBuf::new()), Path::new("usr/lib/sysusers.d/blah").to_path_buf(),
131 Some(0o0),
132 IsBuilt::No,
133 AssetKind::Any,
134 )];
135
136 assert!(generate("mypkg", &assets, &mut ScriptFragments::new(), &mock_listener).is_err());
137 }
138
139 #[test]
140 fn generate_with_data_tmp_file_asset_fails() {
141 let mut mock_listener = crate::listener::MockListener::new();
142 mock_listener.expect_info().times(0).return_const(());
143
144 let assets = vec![Asset::new(
145 AssetSource::Data(vec![]), Path::new("usr/lib/sysusers.d/blah").to_path_buf(),
147 Some(0o0),
148 IsBuilt::No,
149 AssetKind::Any,
150 )];
151
152 assert!(generate("mypkg", &assets, &mut ScriptFragments::new(), &mock_listener).is_err());
153 }
154
155 #[test]
156 fn generate_with_empty_sysusers_asset() {
157 use crate::dh::dh_lib::get_embedded_autoscript;
158
159 const TMP_FILE_NAME: &str = "mypkg.sysusers";
160 let tmp_file_path = PathBuf::from(format!("debian/{TMP_FILE_NAME}"));
161
162 let mut mock_listener = crate::listener::MockListener::new();
163 mock_listener.expect_progress().times(1).return_const(());
164
165 let assets = vec![Asset::new(
166 AssetSource::Path(tmp_file_path),
167 Path::new("usr/lib/sysusers.d/blah").to_path_buf(),
168 Some(0o0),
169 IsBuilt::No,
170 AssetKind::Any,
171 )];
172
173 let mut fragments = ScriptFragments::new();
174 generate("mypkg", &assets, &mut fragments, &mock_listener).unwrap();
175 assert_eq!(1, fragments.len());
176
177 let (fragment_name, created_text) = fragments.into_iter().next().unwrap();
178
179 assert_eq!("mypkg.postinst.debhelper", fragment_name);
181
182 let autoscript_text = get_embedded_autoscript("postinst-sysusers");
188 let autoscript_line_count = autoscript_text.lines().count();
189 let created_line_count = created_text.lines().count();
190 assert_eq!(autoscript_line_count + 2, created_line_count);
191
192 let mut lines = created_text.lines();
194 assert!(lines.next().unwrap().starts_with("# Automatically added by"));
195 assert_eq!(lines.nth_back(0).unwrap(), "# End automatically added section");
196
197 let expected_autoscript_text = autoscript_text.replace("#CONFILE_BASENAME#", TMP_FILE_NAME.replace(".sysusers", ".conf").as_str());
200 let expected_autoscript_text = expected_autoscript_text.trim_end();
201 let start1 = 1;
202 let end1 = start1 + autoscript_line_count;
203 let created_autoscript_text = created_text.lines().collect::<Vec<&str>>()[start1..end1].join("\n");
204 assert_ne!(expected_autoscript_text, autoscript_text);
205 assert_eq!(expected_autoscript_text, created_autoscript_text);
206 }
207
208 #[test]
209 fn generate_acts_only_on_config_files_with_the_expected_install_path() {
210 let mut mock_listener = crate::listener::MockListener::new();
212 mock_listener.expect_info().times(0).return_const(());
213
214 let assets = vec![Asset::new(
215 AssetSource::Path(PathBuf::from("debian/mypkg.sysusers")),
216 Path::new("some/other/path/").to_path_buf(),
217 Some(0o0),
218 IsBuilt::No,
219 AssetKind::Any,
220 )];
221
222 let mut fragments = ScriptFragments::new();
223 generate("mypkg", &assets, &mut fragments, &mock_listener).unwrap();
224 assert_eq!(0, fragments.len());
225 }
226
227 #[test]
228 fn generate_creates_expected_autoscript_fragments() {
229 let config_file_path = "debian/mypkg.sysusers";
230
231 let assets = vec![Asset::new(
233 AssetSource::Path(PathBuf::from(config_file_path)),
234 format!("usr/lib/sysusers.d/mypkg.conf").into(),
235 Some(0o0),
236 IsBuilt::No,
237 AssetKind::Any,
238 )];
239
240 let mut mock_listener = crate::listener::MockListener::new();
242 mock_listener.expect_progress().return_const(());
243
244 let config_file_content = "u ego -\n".to_owned();
245 set_test_fs_path_content(config_file_path, config_file_content);
246
247 let _g = add_test_fs_paths(&[
250 "postinst-sysusers",
251 ]);
252
253 let mut fragments = ScriptFragments::new();
255 generate("mypkg", &assets, &mut fragments, &mock_listener).unwrap();
256
257 let mut autoscript_fragments_to_check_for = std::collections::HashSet::new();
267
268 assert_eq!(1, get_read_count("postinst-sysusers"));
269 autoscript_fragments_to_check_for.insert("postinst.debhelper");
270
271 for autoscript in &autoscript_fragments_to_check_for {
272 let key = format!("mypkg.{autoscript}");
273 assert!(fragments.contains_key(&key), "{}", key);
274 }
275 }
276}