]>
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; | |
8ada1785 FE |
15 | use release::get_current_release_codename; |
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. | |
52 | pub fn check_repositories(files: &[APTRepositoryFile]) -> Result<Vec<APTRepositoryInfo>, Error> { | |
53 | let mut infos = vec![]; | |
54 | ||
55 | for file in files.iter() { | |
56 | infos.append(&mut file.check_suites()?); | |
57 | infos.append(&mut file.check_uris()); | |
58 | } | |
59 | ||
60 | Ok(infos) | |
61 | } | |
62 | ||
8ada1785 FE |
63 | /// Get the repository associated to the handle and the path where its usually configured. |
64 | pub fn get_standard_repository( | |
65 | handle: APTRepositoryHandle, | |
66 | product: &str, | |
67 | ) -> Result<(APTRepository, String), Error> { | |
68 | let suite = get_current_release_codename()?; | |
69 | ||
70 | let repo = handle.to_repository(product, &suite); | |
71 | let path = handle.path(product); | |
72 | ||
73 | Ok((repo, path)) | |
74 | } | |
75 | ||
76 | /// Return handles for standard Proxmox repositories and whether their status, where | |
77 | /// None means not configured, and Some(bool) indicates enabled or disabled | |
78 | pub fn standard_repositories( | |
79 | product: &str, | |
80 | files: &[APTRepositoryFile], | |
81 | ) -> Vec<APTStandardRepository> { | |
82 | let mut result = vec![ | |
83 | APTStandardRepository { | |
84 | handle: APTRepositoryHandle::Enterprise, | |
85 | status: None, | |
3f715238 | 86 | name: APTRepositoryHandle::Enterprise.name(), |
8ada1785 FE |
87 | }, |
88 | APTStandardRepository { | |
89 | handle: APTRepositoryHandle::NoSubscription, | |
90 | status: None, | |
3f715238 | 91 | name: APTRepositoryHandle::NoSubscription.name(), |
8ada1785 FE |
92 | }, |
93 | APTStandardRepository { | |
94 | handle: APTRepositoryHandle::Test, | |
95 | status: None, | |
3f715238 | 96 | name: APTRepositoryHandle::Test.name(), |
8ada1785 FE |
97 | }, |
98 | ]; | |
99 | ||
100 | if product == "pve" { | |
101 | result.append(&mut vec![ | |
102 | APTStandardRepository { | |
103 | handle: APTRepositoryHandle::CephPacific, | |
104 | status: None, | |
3f715238 | 105 | name: APTRepositoryHandle::CephPacific.name(), |
8ada1785 FE |
106 | }, |
107 | APTStandardRepository { | |
108 | handle: APTRepositoryHandle::CephPacificTest, | |
109 | status: None, | |
3f715238 | 110 | name: APTRepositoryHandle::CephPacificTest.name(), |
8ada1785 FE |
111 | }, |
112 | APTStandardRepository { | |
113 | handle: APTRepositoryHandle::CephOctopus, | |
114 | status: None, | |
3f715238 | 115 | name: APTRepositoryHandle::CephOctopus.name(), |
8ada1785 FE |
116 | }, |
117 | APTStandardRepository { | |
118 | handle: APTRepositoryHandle::CephOctopusTest, | |
119 | status: None, | |
3f715238 | 120 | name: APTRepositoryHandle::CephOctopusTest.name(), |
8ada1785 FE |
121 | }, |
122 | ]); | |
123 | } | |
124 | ||
125 | for file in files.iter() { | |
126 | for repo in file.repositories.iter() { | |
127 | for entry in result.iter_mut() { | |
128 | if entry.status == Some(true) { | |
129 | continue; | |
130 | } | |
131 | ||
132 | if repo.is_referenced_repository(entry.handle, product) { | |
133 | entry.status = Some(repo.enabled); | |
134 | } | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | result | |
140 | } | |
141 | ||
b6be0f39 FE |
142 | /// Returns all APT repositories configured in `/etc/apt/sources.list` and |
143 | /// in `/etc/apt/sources.list.d` including disabled repositories. | |
144 | /// | |
145 | /// Returns the succesfully parsed files, a list of errors for files that could | |
146 | /// not be read or parsed and a common digest for the succesfully parsed files. | |
147 | /// | |
148 | /// The digest is guaranteed to be set for each successfully parsed file. | |
149 | pub fn repositories() -> Result< | |
150 | ( | |
151 | Vec<APTRepositoryFile>, | |
152 | Vec<APTRepositoryFileError>, | |
153 | [u8; 32], | |
154 | ), | |
155 | Error, | |
156 | > { | |
157 | let to_result = |files: Vec<APTRepositoryFile>, errors: Vec<APTRepositoryFileError>| { | |
158 | let common_digest = common_digest(&files); | |
159 | ||
160 | (files, errors, common_digest) | |
161 | }; | |
162 | ||
163 | let mut files = vec![]; | |
164 | let mut errors = vec![]; | |
165 | ||
166 | let sources_list_path = PathBuf::from(APT_SOURCES_LIST_FILENAME); | |
167 | ||
168 | let sources_list_d_path = PathBuf::from(APT_SOURCES_LIST_DIRECTORY); | |
169 | ||
170 | match APTRepositoryFile::new(sources_list_path) { | |
171 | Ok(Some(mut file)) => match file.parse() { | |
172 | Ok(()) => files.push(file), | |
173 | Err(err) => errors.push(err), | |
174 | }, | |
175 | _ => bail!("internal error with '{}'", APT_SOURCES_LIST_FILENAME), | |
176 | } | |
177 | ||
178 | if !sources_list_d_path.exists() { | |
179 | return Ok(to_result(files, errors)); | |
180 | } | |
181 | ||
182 | if !sources_list_d_path.is_dir() { | |
183 | errors.push(APTRepositoryFileError { | |
184 | path: APT_SOURCES_LIST_DIRECTORY.to_string(), | |
185 | error: "not a directory!".to_string(), | |
186 | }); | |
187 | return Ok(to_result(files, errors)); | |
188 | } | |
189 | ||
190 | for entry in std::fs::read_dir(sources_list_d_path)? { | |
191 | let path = entry?.path(); | |
192 | ||
193 | match APTRepositoryFile::new(path) { | |
194 | Ok(Some(mut file)) => match file.parse() { | |
195 | Ok(()) => { | |
196 | if file.digest.is_none() { | |
197 | bail!("internal error - digest not set"); | |
198 | } | |
199 | files.push(file); | |
200 | } | |
201 | Err(err) => errors.push(err), | |
202 | }, | |
203 | Ok(None) => (), | |
204 | Err(err) => errors.push(err), | |
205 | } | |
206 | } | |
207 | ||
208 | Ok(to_result(files, errors)) | |
209 | } |