1use self::source_deb822::SourceListDeb822;
2
3use super::*;
4use std::collections::HashSet;
5use std::fmt::{self, Display, Formatter};
6use std::fs::{self, File};
7use std::io::{self, Write};
8use std::ops::{Deref, DerefMut};
9use std::path::{Path, PathBuf};
10use std::str::FromStr;
11
12#[derive(Clone, Debug)]
13pub struct SourcesList {
14 pub path: PathBuf,
15 pub entries: SourceListType,
16}
17
18#[derive(PartialEq, Clone, Debug)]
19pub enum SourceListType {
20 SourceLine(SourceListLineStyle),
21 Deb822(SourceListDeb822),
22}
23
24#[derive(PartialEq, Clone, Debug)]
25pub struct SourceListLineStyle(pub Vec<SourceLine>);
26
27impl FromStr for SourceListLineStyle {
28 type Err = SourcesListError;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 let mut entries = vec![];
32 for (line_num, line) in s.lines().enumerate() {
33 let entry = line
34 .parse::<SourceLine>()
35 .map_err(|why| SourcesListError::BadLine {
36 line: line_num,
37 why,
38 })?;
39
40 entries.push(entry);
41 }
42
43 Ok(SourceListLineStyle(entries))
44 }
45}
46
47impl SourcesList {
48 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, SourcesListError> {
49 let path = path.as_ref();
50 let data = fs::read_to_string(path).map_err(|why| SourcesListError::SourcesListOpen {
51 path: path.to_path_buf(),
52 why,
53 })?;
54
55 let mut sources_file = match path.extension() {
56 Some(x) if x == "sources" => SourcesList {
57 path: path.to_path_buf(),
58 entries: SourceListType::Deb822(SourceListDeb822::from_str(&data).map_err(
59 |e| SourcesListError::Deb822 {
60 path: path.to_path_buf(),
61 why: e,
62 },
63 )?),
64 },
65 Some(x) if x == "list" => get_line_style_sources_list(&data)?,
66 _ => {
67 return Err(SourcesListError::UnknownFile {
68 path: path.to_path_buf(),
69 })
70 }
71 };
72
73 sources_file.path = path.to_path_buf();
74
75 Ok(sources_file)
76 }
77
78 pub fn contains_entry(&self, entry: &str) -> Option<usize> {
79 let elem = &self.entries;
80 match elem {
81 SourceListType::SourceLine(lines) => lines.0.iter().position(|e| {
82 if let SourceLine::Entry(e) = e {
83 e.url == entry
84 } else {
85 false
86 }
87 }),
88 SourceListType::Deb822(e) => e.entries.iter().position(|x| x.url == entry),
89 }
90 }
91
92 pub fn get_entries_mut<'a>(
93 &'a mut self,
94 entry: &'a str,
95 ) -> Box<dyn Iterator<Item = &'a mut SourceEntry> + 'a> {
96 match self.entries {
97 SourceListType::SourceLine(ref mut line) => {
98 Box::new(line.0.iter_mut().filter_map(move |line| {
99 if let SourceLine::Entry(ref mut e) = line {
100 if entry == e.url {
101 return Some(e);
102 }
103 }
104
105 None
106 }))
107 }
108 SourceListType::Deb822(ref mut e) => {
109 Box::new(e.entries.iter_mut().filter_map(move |e| {
110 if entry == e.url {
111 return Some(e);
112 }
113
114 None
115 }))
116 }
117 }
118 }
119
120 pub fn is_active(&self) -> bool {
121 match &self.entries {
122 SourceListType::SourceLine(line) => line
123 .0
124 .iter()
125 .any(|line| matches!(line, SourceLine::Entry(_))),
126 SourceListType::Deb822(e) => !e.entries.is_empty(),
127 }
128 }
129
130 pub fn write_sync(&mut self) -> io::Result<()> {
131 fs::OpenOptions::new()
132 .truncate(true)
133 .write(true)
134 .open(&self.path)
135 .and_then(|mut file| writeln!(&mut file, "{}", self))
136 }
137
138 pub fn reload(&mut self) -> Result<(), SourcesListError> {
139 *self = Self::new(&self.path)?;
140 Ok(())
141 }
142}
143
144fn get_line_style_sources_list(data: &str) -> Result<SourcesList, SourcesListError> {
145 let mut entries = vec![];
146 for (line_num, line) in data.lines().enumerate() {
147 let entry = line
148 .parse::<SourceLine>()
149 .map_err(|why| SourcesListError::BadLine {
150 line: line_num,
151 why,
152 })?;
153
154 entries.push(entry);
155 }
156
157 Ok(SourcesList {
158 path: PathBuf::from(""),
159 entries: SourceListType::SourceLine(SourceListLineStyle(entries)),
160 })
161}
162
163impl Display for SourcesList {
164 fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
165 match &self.entries {
166 SourceListType::SourceLine(lines) => {
167 for line in &lines.0 {
168 writeln!(fmt, "{}", line)?;
169 }
170 }
171 SourceListType::Deb822(e) => {
172 for entry in &e.entries {
173 writeln!(fmt, "{}", entry)?;
174 }
175 }
176 }
177
178 Ok(())
179 }
180}
181
182#[derive(Clone, Debug)]
183pub struct SourcesLists {
185 pub(crate) files: Vec<SourcesList>,
186 pub(crate) modified: Vec<u16>,
187}
188
189impl Deref for SourcesLists {
190 type Target = Vec<SourcesList>;
191
192 fn deref(&self) -> &Self::Target {
193 &self.files
194 }
195}
196
197impl DerefMut for SourcesLists {
198 fn deref_mut(&mut self) -> &mut Self::Target {
199 &mut self.files
200 }
201}
202
203impl SourcesLists {
204 pub fn scan() -> Result<Self, SourcesListError> {
208 scan_inner("/")
209 }
210
211 pub fn scan_from_root<P: AsRef<Path>>(root: P) -> Result<Self, SourcesListError> {
215 scan_inner(root)
216 }
217
218 pub fn new_from_paths<P: AsRef<Path>, I: Iterator<Item = P>>(
220 paths: I,
221 ) -> Result<Self, SourcesListError> {
222 let files = paths
223 .map(SourcesList::new)
224 .collect::<Result<Vec<SourcesList>, SourcesListError>>()?;
225
226 Ok(SourcesLists {
227 modified: Vec::with_capacity(files.len()),
228 files,
229 })
230 }
231
232 pub fn repo_modify(&mut self, repo: &str, enabled: bool) -> bool {
234 let &mut Self {
235 ref mut modified,
236 ref mut files,
237 } = self;
238
239 let iterator = files
240 .iter_mut()
241 .enumerate()
242 .flat_map(|(pos, list)| list.get_entries_mut(repo).map(move |e| (pos, e)));
243
244 let mut found = false;
245 for (pos, entry) in iterator {
246 add_modified(modified, pos as u16);
247 entry.enabled = enabled;
248 found = true;
249 }
250
251 found
252 }
253
254 pub fn entries(&self) -> impl Iterator<Item = &SourceEntry> {
256 self.iter()
257 .flat_map(|list| -> Box<dyn Iterator<Item = &SourceEntry>> {
258 match &list.entries {
259 SourceListType::SourceLine(lines) => Box::new(lines.0.iter().filter_map(|x| {
260 if let SourceLine::Entry(entry) = x {
261 Some(entry)
262 } else {
263 None
264 }
265 })),
266 SourceListType::Deb822(e) => Box::new(e.entries.iter()),
267 }
268 })
269 }
270
271 pub fn entries_mut<F: FnMut(&mut SourceEntry) -> bool>(&mut self, mut func: F) {
273 let &mut Self {
274 ref mut files,
275 ref mut modified,
276 } = self;
277 for (pos, list) in files.iter_mut().enumerate() {
278 match list.entries {
279 SourceListType::SourceLine(ref mut lines) => {
280 for entry in &mut lines.0 {
281 if let SourceLine::Entry(entry) = entry {
282 if func(entry) {
283 add_modified(modified, pos as u16)
284 }
285 }
286 }
287 }
288 SourceListType::Deb822(ref mut e) => {
289 for entry in &mut e.entries {
290 if func(entry) {
291 add_modified(modified, pos as u16)
292 }
293 }
294 }
295 }
296 }
297 }
298
299 pub fn insert_entry<P: AsRef<Path>>(
305 &mut self,
306 path: P,
307 entry: SourceEntry,
308 ) -> SourceResult<()> {
309 let path = path.as_ref();
310 let &mut Self {
311 ref mut modified,
312 ref mut files,
313 } = self;
314
315 for (id, list) in files.iter_mut().enumerate() {
316 if list.path == path {
317 match list.contains_entry(&entry.url) {
318 Some(pos) => match list.entries {
319 SourceListType::SourceLine(ref mut lines) => {
320 lines.0[pos] = SourceLine::Entry(entry)
321 }
322 SourceListType::Deb822(ref mut e) => {
323 e.entries[pos] = entry;
324 }
325 },
326 None => match list.entries {
327 SourceListType::SourceLine(ref mut lines) => {
328 lines.0.push(SourceLine::Entry(entry))
329 }
330 SourceListType::Deb822(ref mut e) => {
331 e.entries.push(entry);
332 }
333 },
334 }
335
336 add_modified(modified, id as u16);
337 return Ok(());
338 }
339 }
340
341 files.push(SourcesList {
342 path: path.to_path_buf(),
343 entries: SourceListType::SourceLine(SourceListLineStyle(vec![SourceLine::Entry(
344 entry,
345 )])),
346 });
347
348 Ok(())
349 }
350
351 pub fn remove_entry(&mut self, repo: &str) {
353 let &mut Self {
354 ref mut modified,
355 ref mut files,
356 } = self;
357 for (id, list) in files.iter_mut().enumerate() {
358 if let Some(line) = list.contains_entry(repo) {
359 match list.entries {
360 SourceListType::SourceLine(ref mut lines) => {
361 lines.0.remove(line);
362 }
363 SourceListType::Deb822(ref mut e) => {
364 e.entries.remove(line);
365 }
366 }
367 add_modified(modified, id as u16);
368 }
369 }
370 }
371
372 pub fn dist_replace(&mut self, from_suite: &str, to_suite: &str) {
377 let &mut Self {
378 ref mut modified,
379 ref mut files,
380 } = self;
381 for (id, file) in files.iter_mut().enumerate() {
382 let mut changed = false;
383 match file.entries {
384 SourceListType::SourceLine(ref mut lines) => {
385 for line in &mut lines.0 {
386 if let SourceLine::Entry(ref mut entry) = line {
387 if entry.suite.starts_with(from_suite) {
388 entry.suite = entry.suite.replace(from_suite, to_suite);
389 changed = true;
390 }
391 }
392 }
393 }
394 SourceListType::Deb822(ref mut e) => {
395 for entry in &mut e.entries {
396 if entry.suite.starts_with(from_suite) {
397 entry.suite = entry.suite.replace(from_suite, to_suite);
398 changed = true;
399 }
400 }
401 }
402 }
403
404 if changed {
405 add_modified(modified, id as u16);
406 }
407 }
408 }
409
410 pub fn dist_upgrade(
415 &mut self,
416 retain: &HashSet<Box<str>>,
417 from_suite: &str,
418 to_suite: &str,
419 ) -> io::Result<()> {
420 fn newfile(modified: &mut Vec<PathBuf>, path: &Path) -> io::Result<File> {
421 let backup_path = path
422 .file_name()
423 .map(|str| {
424 let mut string = str.to_os_string();
425 string.push(".save");
426
427 let mut backup = path.to_path_buf();
428 backup.set_file_name(&string);
429 backup
430 })
431 .ok_or_else(|| {
432 io::Error::new(
433 io::ErrorKind::NotFound,
434 format!("filename not found for apt source at '{}'", path.display()),
435 )
436 })?;
437
438 fs::copy(path, &backup_path)?;
439 modified.push(backup_path);
440 fs::OpenOptions::new().truncate(true).write(true).open(path)
441 }
442
443 fn apply(
444 sources: &mut SourcesLists,
445 modified: &mut Vec<PathBuf>,
446 retain: &HashSet<Box<str>>,
447 from_suite: &str,
448 to_suite: &str,
449 ) -> io::Result<()> {
450 for list in sources.iter_mut() {
451 let mut current_file = newfile(modified, &list.path)?;
452
453 match list.entries {
454 SourceListType::SourceLine(ref mut lines) => {
455 for line in &mut lines.0 {
456 if let SourceLine::Entry(entry) = line {
457 if !retain.contains(entry.url.as_str())
458 && entry.url.starts_with("http")
459 && entry.suite.starts_with(from_suite)
460 {
461 entry.suite = entry.suite.replace(from_suite, to_suite);
462 }
463 }
464
465 writeln!(&mut current_file, "{}", line)?
466 }
467 }
468 SourceListType::Deb822(ref mut e) => writeln!(&mut current_file, "{}", e)?,
469 }
470
471 current_file.flush()?;
472 }
473
474 Ok(())
475 }
476
477 let mut modified = Vec::new();
478 apply(self, &mut modified, retain, from_suite, to_suite).inspect_err(|_| {
479 for (original, backup) in self.iter().zip(modified.iter()) {
483 if let Err(why) = fs::copy(backup, &original.path) {
484 eprintln!("failed to restore backup of {:?}: {}", backup, why);
485 }
486 }
487 })
488 }
489
490 pub fn dist_upgrade_paths<'a>(
495 &'a self,
496 from_suite: &'a str,
497 to_suite: &'a str,
498 ) -> impl Iterator<Item = String> + 'a {
499 self.entries().filter_map(move |entry| {
500 if entry.url.starts_with("http") && entry.suite.starts_with(from_suite) {
501 let entry = {
502 let mut entry = entry.clone();
503 entry.suite = entry.suite.replace(from_suite, to_suite);
504 entry
505 };
506
507 let dist_path = entry.dist_path();
508 Some(dist_path)
509 } else {
510 None
511 }
512 })
513 }
514
515 pub fn write_sync(&mut self) -> io::Result<()> {
517 let &mut Self {
518 ref mut modified,
519 ref mut files,
520 } = self;
521 modified
522 .drain(..)
523 .try_for_each(|id| files[id as usize].write_sync())
524 }
525}
526
527fn scan_inner<P: AsRef<Path>>(dir: P) -> Result<SourcesLists, SourcesListError> {
528 let paths = sources_list(dir)?;
529
530 SourcesLists::new_from_paths(paths.iter())
531}
532
533pub(crate) fn sources_list<P: AsRef<Path>>(dir: P) -> Result<Vec<PathBuf>, SourcesListError> {
534 let dir = dir.as_ref();
535 let mut paths = vec![];
536 let default = dir.join("etc/apt/sources.list");
537
538 if default.exists() {
539 paths.push(default);
540 }
541
542 if dir.join("etc/apt/sources.list.d/").exists() {
543 for entry in fs::read_dir(dir.join("etc/apt/sources.list.d/"))? {
544 let entry = entry?;
545 let path = entry.path();
546 if path
547 .extension()
548 .map_or(false, |e| e == "list" || e == "sources")
549 {
550 paths.push(path);
551 }
552 }
553 }
554
555 Ok(paths)
556}
557
558fn add_modified(modified: &mut Vec<u16>, list: u16) {
559 if !modified.iter().any(|&v| v == list) {
560 modified.push(list);
561 }
562}