]>
Commit | Line | Data |
---|---|---|
b6be0f39 FE |
1 | use std::collections::BTreeMap; |
2 | use std::path::PathBuf; | |
3 | ||
4 | use anyhow::{bail, Error}; | |
5 | ||
6 | mod repository; | |
7 | pub use repository::{ | |
8 | APTRepository, APTRepositoryFileType, APTRepositoryOption, APTRepositoryPackageType, | |
9 | }; | |
10 | ||
11 | mod file; | |
76d3a5ba FE |
12 | pub use file::{APTRepositoryFile, APTRepositoryFileError, APTRepositoryInfo}; |
13 | ||
14 | mod release; | |
f48c12b0 | 15 | pub use release::get_current_release_codename; |
8ada1785 FE |
16 | |
17 | mod standard; | |
18 | pub use standard::{APTRepositoryHandle, APTStandardRepository}; | |
b6be0f39 FE |
19 | |
20 | const APT_SOURCES_LIST_FILENAME: &str = "/etc/apt/sources.list"; | |
21 | const APT_SOURCES_LIST_DIRECTORY: &str = "/etc/apt/sources.list.d/"; | |
22 | ||
23 | /// Calculates a common digest for successfully parsed repository files. | |
24 | /// | |
25 | /// The digest is invariant with respect to file order. | |
26 | /// | |
27 | /// Files without a digest are ignored. | |
28 | fn common_digest(files: &[APTRepositoryFile]) -> [u8; 32] { | |
29 | let mut digests = BTreeMap::new(); | |
30 | ||
31 | for file in files.iter() { | |
32 | digests.insert(file.path.clone(), &file.digest); | |
33 | } | |
34 | ||
35 | let mut common_raw = Vec::<u8>::with_capacity(digests.len() * 32); | |
36 | for digest in digests.values() { | |
37 | match digest { | |
38 | Some(digest) => common_raw.extend_from_slice(&digest[..]), | |
39 | None => (), | |
40 | } | |
41 | } | |
42 | ||
43 | openssl::sha::sha256(&common_raw[..]) | |
44 | } | |
45 | ||
76d3a5ba FE |
46 | /// Provides additional information about the repositories. |
47 | /// | |
48 | /// The kind of information can be: | |
49 | /// `warnings` for bad suites. | |
50 | /// `ignore-pre-upgrade-warning` when the next stable suite is configured. | |
51 | /// `badge` for official URIs. | |
13cdf8d1 FE |
52 | pub fn check_repositories( |
53 | files: &[APTRepositoryFile], | |
54 | current_suite: &str, | |
55 | ) -> Result<Vec<APTRepositoryInfo>, Error> { | |
76d3a5ba FE |
56 | let mut infos = vec![]; |
57 | ||
58 | for file in files.iter() { | |
13cdf8d1 | 59 | infos.append(&mut file.check_suites(current_suite)?); |
76d3a5ba FE |
60 | infos.append(&mut file.check_uris()); |
61 | } | |
62 | ||
63 | Ok(infos) | |
64 | } | |
65 | ||
f48c12b0 | 66 | /// Get the repository associated to the handle and the path where it is usually configured. |
8ada1785 FE |
67 | pub fn get_standard_repository( |
68 | handle: APTRepositoryHandle, | |
69 | product: &str, | |
f48c12b0 FE |
70 | suite: &str, |
71 | ) -> (APTRepository, String) { | |
8ada1785 FE |
72 | let repo = handle.to_repository(product, &suite); |
73 | let path = handle.path(product); | |
74 | ||
f48c12b0 | 75 | (repo, path) |
8ada1785 FE |
76 | } |
77 | ||
f48c12b0 FE |
78 | /// Return handles for standard Proxmox repositories and their status, where |
79 | /// `None` means not configured, and `Some(bool)` indicates enabled or disabled. | |
8ada1785 | 80 | pub fn standard_repositories( |
8ada1785 | 81 | files: &[APTRepositoryFile], |
f48c12b0 FE |
82 | product: &str, |
83 | suite: &str, | |
8ada1785 FE |
84 | ) -> Vec<APTStandardRepository> { |
85 | let mut result = vec![ | |
3c4e441d FE |
86 | APTStandardRepository::from(APTRepositoryHandle::Enterprise), |
87 | APTStandardRepository::from(APTRepositoryHandle::NoSubscription), | |
88 | APTStandardRepository::from(APTRepositoryHandle::Test), | |
8ada1785 FE |
89 | ]; |
90 | ||
91 | if product == "pve" { | |
92 | result.append(&mut vec![ | |
3c4e441d FE |
93 | APTStandardRepository::from(APTRepositoryHandle::CephPacific), |
94 | APTStandardRepository::from(APTRepositoryHandle::CephPacificTest), | |
95 | APTStandardRepository::from(APTRepositoryHandle::CephOctopus), | |
96 | APTStandardRepository::from(APTRepositoryHandle::CephOctopusTest), | |
8ada1785 FE |
97 | ]); |
98 | } | |
99 | ||
100 | for file in files.iter() { | |
101 | for repo in file.repositories.iter() { | |
102 | for entry in result.iter_mut() { | |
103 | if entry.status == Some(true) { | |
104 | continue; | |
105 | } | |
106 | ||
f48c12b0 | 107 | if repo.is_referenced_repository(entry.handle, product, suite) { |
8ada1785 FE |
108 | entry.status = Some(repo.enabled); |
109 | } | |
110 | } | |
111 | } | |
112 | } | |
113 | ||
114 | result | |
115 | } | |
116 | ||
b6be0f39 FE |
117 | /// Returns all APT repositories configured in `/etc/apt/sources.list` and |
118 | /// in `/etc/apt/sources.list.d` including disabled repositories. | |
119 | /// | |
120 | /// Returns the succesfully parsed files, a list of errors for files that could | |
121 | /// not be read or parsed and a common digest for the succesfully parsed files. | |
122 | /// | |
123 | /// The digest is guaranteed to be set for each successfully parsed file. | |
124 | pub fn repositories() -> Result< | |
125 | ( | |
126 | Vec<APTRepositoryFile>, | |
127 | Vec<APTRepositoryFileError>, | |
128 | [u8; 32], | |
129 | ), | |
130 | Error, | |
131 | > { | |
132 | let to_result = |files: Vec<APTRepositoryFile>, errors: Vec<APTRepositoryFileError>| { | |
133 | let common_digest = common_digest(&files); | |
134 | ||
135 | (files, errors, common_digest) | |
136 | }; | |
137 | ||
138 | let mut files = vec![]; | |
139 | let mut errors = vec![]; | |
140 | ||
141 | let sources_list_path = PathBuf::from(APT_SOURCES_LIST_FILENAME); | |
142 | ||
143 | let sources_list_d_path = PathBuf::from(APT_SOURCES_LIST_DIRECTORY); | |
144 | ||
145 | match APTRepositoryFile::new(sources_list_path) { | |
146 | Ok(Some(mut file)) => match file.parse() { | |
147 | Ok(()) => files.push(file), | |
148 | Err(err) => errors.push(err), | |
149 | }, | |
150 | _ => bail!("internal error with '{}'", APT_SOURCES_LIST_FILENAME), | |
151 | } | |
152 | ||
153 | if !sources_list_d_path.exists() { | |
154 | return Ok(to_result(files, errors)); | |
155 | } | |
156 | ||
157 | if !sources_list_d_path.is_dir() { | |
158 | errors.push(APTRepositoryFileError { | |
159 | path: APT_SOURCES_LIST_DIRECTORY.to_string(), | |
160 | error: "not a directory!".to_string(), | |
161 | }); | |
162 | return Ok(to_result(files, errors)); | |
163 | } | |
164 | ||
165 | for entry in std::fs::read_dir(sources_list_d_path)? { | |
166 | let path = entry?.path(); | |
167 | ||
168 | match APTRepositoryFile::new(path) { | |
169 | Ok(Some(mut file)) => match file.parse() { | |
170 | Ok(()) => { | |
171 | if file.digest.is_none() { | |
172 | bail!("internal error - digest not set"); | |
173 | } | |
174 | files.push(file); | |
175 | } | |
176 | Err(err) => errors.push(err), | |
177 | }, | |
178 | Ok(None) => (), | |
179 | Err(err) => errors.push(err), | |
180 | } | |
181 | } | |
182 | ||
183 | Ok(to_result(files, errors)) | |
184 | } |