apt_sources/
distribution.rs1use std::fs;
2use std::process::Command;
3
4#[derive(Debug, Clone, PartialEq)]
6pub enum Distribution {
7 Ubuntu,
9 Debian,
11 Other(String),
13}
14
15impl Distribution {
16 pub fn current() -> Result<Distribution, String> {
18 let lsb_id = Command::new("lsb_release").args(["-i", "-s"]).output();
20
21 if let Ok(output) = lsb_id {
22 if output.status.success() {
23 let distro_id = String::from_utf8_lossy(&output.stdout)
24 .trim()
25 .to_lowercase();
26
27 return Ok(match distro_id.as_str() {
28 "ubuntu" => Distribution::Ubuntu,
29 "debian" => Distribution::Debian,
30 other => Distribution::Other(other.to_owned()),
31 });
32 }
33 }
34
35 if let Ok(content) = fs::read_to_string("/etc/os-release") {
37 for line in content.lines() {
38 if line.starts_with("ID=") {
39 let id = line
40 .trim_start_matches("ID=")
41 .trim_matches('"')
42 .to_lowercase();
43
44 return Ok(match id.as_str() {
45 "ubuntu" => Distribution::Ubuntu,
46 "debian" => Distribution::Debian,
47 other => Distribution::Other(other.to_owned()),
48 });
49 }
50 }
51 }
52
53 Ok(Distribution::Other("unknown".to_owned()))
55 }
56
57 pub fn default_components(&self) -> Vec<&'static str> {
59 match self {
60 Distribution::Ubuntu => vec!["main", "universe"],
61 Distribution::Debian => vec!["main"],
62 Distribution::Other(_) => vec!["main"],
63 }
64 }
65
66 pub fn is_main_repository(&self, repo: &crate::Repository) -> bool {
68 for uri in &repo.uris {
69 if let Some(host) = uri.host_str() {
70 match self {
71 Distribution::Ubuntu => {
72 if host.contains("ubuntu.com")
73 || host.contains("canonical.com")
74 || host == "archive.ubuntu.com"
75 || host == "security.ubuntu.com"
76 || host == "ports.ubuntu.com"
77 {
78 return true;
79 }
80 }
81 Distribution::Debian => {
82 if host.contains("debian.org")
83 || host == "deb.debian.org"
84 || host == "security.debian.org"
85 {
86 return true;
87 }
88 }
89 _ => {}
90 }
91 }
92 }
93 false
94 }
95}
96
97pub fn get_system_info() -> Result<(String, String), String> {
99 let lsb_release = Command::new("lsb_release")
101 .args(["-c", "-s"])
102 .output()
103 .map_err(|e| format!("Failed to run lsb_release: {}", e))?;
104
105 if !lsb_release.status.success() {
106 return Err("Failed to determine distribution codename".to_string());
107 }
108
109 let codename = String::from_utf8_lossy(&lsb_release.stdout)
110 .trim()
111 .to_string();
112
113 let dpkg_arch = Command::new("dpkg")
115 .arg("--print-architecture")
116 .output()
117 .map_err(|e| format!("Failed to run dpkg: {}", e))?;
118
119 if !dpkg_arch.status.success() {
120 return Err("Failed to determine system architecture".to_string());
121 }
122
123 let arch = String::from_utf8_lossy(&dpkg_arch.stdout)
124 .trim()
125 .to_string();
126
127 Ok((codename, arch))
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_is_main_repository() {
136 let dist = Distribution::Ubuntu;
137 let repo = crate::Repository {
138 uris: vec![url::Url::parse("http://archive.ubuntu.com/ubuntu").unwrap()],
139 suites: vec!["jammy".to_string()],
140 components: Some(vec!["main".to_string()]),
141 ..Default::default()
142 };
143 assert!(dist.is_main_repository(&repo));
144
145 let non_main_repo = crate::Repository {
146 uris: vec![url::Url::parse("http://example.com/ubuntu").unwrap()],
147 suites: vec!["jammy".to_string()],
148 components: Some(vec!["main".to_string()]),
149 ..Default::default()
150 };
151 assert!(!dist.is_main_repository(&non_main_repo));
152 }
153
154 #[test]
155 fn test_is_main_repository_all_ubuntu_hosts() {
156 let dist = Distribution::Ubuntu;
157
158 let ubuntu_hosts = [
160 "http://archive.ubuntu.com/ubuntu",
161 "http://security.ubuntu.com/ubuntu",
162 "http://ports.ubuntu.com/ubuntu-ports",
163 "http://us.archive.ubuntu.com/ubuntu", "http://mirrors.canonical.com/ubuntu", ];
166
167 for host in &ubuntu_hosts {
168 let repo = crate::Repository {
169 uris: vec![url::Url::parse(host).unwrap()],
170 ..Default::default()
171 };
172 assert!(dist.is_main_repository(&repo), "Failed for host: {}", host);
173 }
174 }
175
176 #[test]
177 fn test_is_main_repository_all_debian_hosts() {
178 let dist = Distribution::Debian;
179
180 let debian_hosts = [
182 "http://deb.debian.org/debian",
183 "http://security.debian.org/debian-security",
184 "http://ftp.debian.org/debian", "http://mirrors.debian.org/debian", ];
187
188 for host in &debian_hosts {
189 let repo = crate::Repository {
190 uris: vec![url::Url::parse(host).unwrap()],
191 ..Default::default()
192 };
193 assert!(dist.is_main_repository(&repo), "Failed for host: {}", host);
194 }
195 }
196
197 #[test]
198 fn test_is_main_repository_other_distribution() {
199 let dist = Distribution::Other("mint".to_string());
200
201 let repo = crate::Repository {
203 uris: vec![url::Url::parse("http://archive.ubuntu.com/ubuntu").unwrap()],
204 ..Default::default()
205 };
206 assert!(!dist.is_main_repository(&repo));
207
208 let repo2 = crate::Repository {
209 uris: vec![url::Url::parse("http://deb.debian.org/debian").unwrap()],
210 ..Default::default()
211 };
212 assert!(!dist.is_main_repository(&repo2));
213 }
214
215 #[test]
216 fn test_is_main_repository_empty_uris() {
217 let dist = Distribution::Ubuntu;
218 let repo = crate::Repository {
219 uris: vec![],
220 ..Default::default()
221 };
222 assert!(!dist.is_main_repository(&repo));
223 }
224
225 #[test]
226 fn test_is_main_repository_multiple_uris() {
227 let dist = Distribution::Ubuntu;
228 let repo = crate::Repository {
229 uris: vec![
230 url::Url::parse("http://example.com/ubuntu").unwrap(),
231 url::Url::parse("http://archive.ubuntu.com/ubuntu").unwrap(),
232 ],
233 ..Default::default()
234 };
235 assert!(dist.is_main_repository(&repo));
237 }
238
239 #[test]
240 fn test_default_components() {
241 assert_eq!(
242 Distribution::Ubuntu.default_components(),
243 vec!["main", "universe"]
244 );
245 assert_eq!(Distribution::Debian.default_components(), vec!["main"]);
246 assert_eq!(
247 Distribution::Other("mint".to_string()).default_components(),
248 vec!["main"]
249 );
250 }
251}