rust_apt/
cache.rs

1//! Contains Cache related structs.
2
3use std::cell::OnceCell;
4use std::fs;
5use std::path::Path;
6
7use cxx::{Exception, UniquePtr};
8
9use crate::config::{init_config_system, Config};
10use crate::depcache::DepCache;
11use crate::error::AptErrors;
12use crate::progress::{AcquireProgress, InstallProgress, OperationProgress};
13use crate::raw::{
14	create_cache, create_pkgmanager, create_problem_resolver, IntoRawIter, IterPkgIterator,
15	PackageManager, PkgCacheFile, PkgIterator, ProblemResolver,
16};
17use crate::records::PackageRecords;
18use crate::util::{apt_lock, apt_unlock, apt_unlock_inner};
19use crate::Package;
20
21/// Selection of Upgrade type
22#[repr(i32)]
23pub enum Upgrade {
24	/// Upgrade will Install new and Remove packages in addition to
25	/// upgrading them.
26	///
27	/// Equivalent to `apt full-upgrade` and `apt-get dist-upgrade`.
28	FullUpgrade = 0,
29	/// Upgrade will Install new but not Remove packages.
30	///
31	/// Equivalent to `apt upgrade`.
32	Upgrade = 1,
33	/// Upgrade will Not Install new or Remove packages.
34	///
35	/// Equivalent to `apt-get upgrade`.
36	SafeUpgrade = 3,
37}
38
39/// Selection of how to sort
40enum Sort {
41	/// Disable the sort method.
42	Disable,
43	/// Enable the sort method.
44	Enable,
45	/// Reverse the sort method.
46	Reverse,
47}
48
49/// Determines how to sort packages from the Cache.
50pub struct PackageSort {
51	names: bool,
52	upgradable: Sort,
53	virtual_pkgs: Sort,
54	installed: Sort,
55	auto_installed: Sort,
56	auto_removable: Sort,
57}
58
59impl Default for PackageSort {
60	fn default() -> PackageSort {
61		PackageSort {
62			names: false,
63			upgradable: Sort::Disable,
64			virtual_pkgs: Sort::Disable,
65			installed: Sort::Disable,
66			auto_installed: Sort::Disable,
67			auto_removable: Sort::Disable,
68		}
69	}
70}
71
72impl PackageSort {
73	/// Packages will be sorted by their names a -> z.
74	pub fn names(mut self) -> Self {
75		self.names = true;
76		self
77	}
78
79	/// Only packages that are upgradable will be included.
80	pub fn upgradable(mut self) -> Self {
81		self.upgradable = Sort::Enable;
82		self
83	}
84
85	/// Only packages that are NOT upgradable will be included.
86	pub fn not_upgradable(mut self) -> Self {
87		self.upgradable = Sort::Reverse;
88		self
89	}
90
91	/// Virtual packages will be included.
92	pub fn include_virtual(mut self) -> Self {
93		self.virtual_pkgs = Sort::Enable;
94		self
95	}
96
97	/// Only Virtual packages will be included.
98	pub fn only_virtual(mut self) -> Self {
99		self.virtual_pkgs = Sort::Reverse;
100		self
101	}
102
103	/// Only packages that are installed will be included.
104	pub fn installed(mut self) -> Self {
105		self.installed = Sort::Enable;
106		self
107	}
108
109	/// Only packages that are NOT installed will be included.
110	pub fn not_installed(mut self) -> Self {
111		self.installed = Sort::Reverse;
112		self
113	}
114
115	/// Only packages that are auto installed will be included.
116	pub fn auto_installed(mut self) -> Self {
117		self.auto_installed = Sort::Enable;
118		self
119	}
120
121	/// Only packages that are manually installed will be included.
122	pub fn manually_installed(mut self) -> Self {
123		self.auto_installed = Sort::Reverse;
124		self
125	}
126
127	/// Only packages that are auto removable will be included.
128	pub fn auto_removable(mut self) -> Self {
129		self.auto_removable = Sort::Enable;
130		self
131	}
132
133	/// Only packages that are NOT auto removable will be included.
134	pub fn not_auto_removable(mut self) -> Self {
135		self.auto_removable = Sort::Reverse;
136		self
137	}
138}
139
140/// The main struct for accessing any and all `apt` data.
141pub struct Cache {
142	pub(crate) ptr: UniquePtr<PkgCacheFile>,
143	depcache: OnceCell<DepCache>,
144	records: OnceCell<PackageRecords>,
145	pkgmanager: OnceCell<UniquePtr<PackageManager>>,
146	problem_resolver: OnceCell<UniquePtr<ProblemResolver>>,
147	local_debs: Vec<String>,
148}
149
150impl Cache {
151	/// Initialize the configuration system, open and return the cache.
152	/// This is the entry point for all operations of this crate.
153	///
154	/// `local_files` allows you to temporarily add local files to the cache, as
155	/// long as they are one of the following:
156	///
157	/// - `*.deb` or `*.ddeb` files
158	/// - `Packages` and `Sources` files from apt repositories. These files can
159	///   be compressed.
160	/// - `*.dsc` or `*.changes` files
161	/// - A valid directory containing the file `./debian/control`
162	///
163	/// This function returns an [`AptErrors`] if any of the files cannot
164	/// be found or are invalid.
165	///
166	/// Note that if you run [`Cache::commit`] or [`Cache::update`],
167	/// You will be required to make a new cache to perform any further changes
168	pub fn new<T: AsRef<str>>(local_files: &[T]) -> Result<Cache, AptErrors> {
169		let volatile_files: Vec<_> = local_files.iter().map(|d| d.as_ref()).collect();
170
171		init_config_system();
172		Ok(Cache {
173			ptr: create_cache(&volatile_files)?,
174			depcache: OnceCell::new(),
175			records: OnceCell::new(),
176			pkgmanager: OnceCell::new(),
177			problem_resolver: OnceCell::new(),
178			local_debs: volatile_files
179				.into_iter()
180				.filter(|f| f.ends_with(".deb"))
181				.map(|f| f.to_string())
182				.collect(),
183		})
184	}
185
186	/// Internal Method for generating the package list.
187	pub fn raw_pkgs(&self) -> impl Iterator<Item = UniquePtr<PkgIterator>> {
188		unsafe { self.begin().raw_iter() }
189	}
190
191	/// Get the DepCache
192	pub fn depcache(&self) -> &DepCache {
193		self.depcache
194			.get_or_init(|| DepCache::new(unsafe { self.create_depcache() }))
195	}
196
197	/// Get the PkgRecords
198	pub fn records(&self) -> &PackageRecords {
199		self.records
200			.get_or_init(|| PackageRecords::new(unsafe { self.create_records() }))
201	}
202
203	/// Get the PkgManager
204	pub fn pkg_manager(&self) -> &PackageManager {
205		self.pkgmanager
206			.get_or_init(|| unsafe { create_pkgmanager(self.depcache()) })
207	}
208
209	/// Get the ProblemResolver
210	pub fn resolver(&self) -> &ProblemResolver {
211		self.problem_resolver
212			.get_or_init(|| unsafe { create_problem_resolver(self.depcache()) })
213	}
214
215	/// Iterate through the packages in a random order
216	pub fn iter(&self) -> CacheIter {
217		CacheIter {
218			pkgs: unsafe { self.begin().raw_iter() },
219			cache: self,
220		}
221	}
222
223	/// An iterator of packages in the cache.
224	pub fn packages(&self, sort: &PackageSort) -> impl Iterator<Item = Package> {
225		let mut pkg_list = vec![];
226		for pkg in self.raw_pkgs() {
227			match sort.virtual_pkgs {
228				// Virtual packages are enabled, include them.
229				// This works differently than the rest. I should probably change defaults.
230				Sort::Enable => {},
231				// If disabled and pkg has no versions, exclude
232				Sort::Disable => {
233					if unsafe { pkg.versions().end() } {
234						continue;
235					}
236				},
237				// If reverse and the package has versions, exclude
238				// This section is for if you only want virtual packages
239				Sort::Reverse => {
240					if unsafe { !pkg.versions().end() } {
241						continue;
242					}
243				},
244			}
245
246			match sort.upgradable {
247				// Virtual packages are enabled, include them.
248				Sort::Disable => {},
249				// If disabled and pkg has no versions, exclude
250				Sort::Enable => {
251					// If the package isn't installed, then it can not be upgradable
252					if unsafe { pkg.current_version().end() }
253						|| !self.depcache().is_upgradable(&pkg)
254					{
255						continue;
256					}
257				},
258				// If reverse and the package is installed and upgradable, exclude
259				// This section is for if you only want packages that are not upgradable
260				Sort::Reverse => {
261					if unsafe { !pkg.current_version().end() }
262						&& self.depcache().is_upgradable(&pkg)
263					{
264						continue;
265					}
266				},
267			}
268
269			match sort.installed {
270				// Installed Package is Disabled, so we keep them
271				Sort::Disable => {},
272				Sort::Enable => {
273					if unsafe { pkg.current_version().end() } {
274						continue;
275					}
276				},
277				// Only include installed packages.
278				Sort::Reverse => {
279					if unsafe { !pkg.current_version().end() } {
280						continue;
281					}
282				},
283			}
284
285			match sort.auto_installed {
286				// Installed Package is Disabled, so we keep them
287				Sort::Disable => {},
288				Sort::Enable => {
289					if !self.depcache().is_auto_installed(&pkg) {
290						continue;
291					}
292				},
293				// Only include installed packages.
294				Sort::Reverse => {
295					if self.depcache().is_auto_installed(&pkg) {
296						continue;
297					}
298				},
299			}
300
301			match sort.auto_removable {
302				// auto_removable is Disabled, so we keep them
303				Sort::Disable => {},
304				// If the package is not auto removable skip it.
305				Sort::Enable => {
306					// If the Package isn't auto_removable skip
307					if !self.depcache().is_garbage(&pkg) {
308						continue;
309					}
310				},
311				// If the package is auto removable skip it.
312				Sort::Reverse => {
313					if self.depcache().is_garbage(&pkg) {
314						continue;
315					}
316				},
317			}
318
319			// If this is reached we're clear to include the package.
320			pkg_list.push(pkg);
321		}
322
323		if sort.names {
324			pkg_list.sort_by_cached_key(|pkg| pkg.name().to_string());
325		}
326
327		pkg_list.into_iter().map(|pkg| Package::new(self, pkg))
328	}
329
330	/// Updates the package cache and returns a Result
331	///
332	/// Here is an example of how you may parse the Error messages.
333	///
334	/// ```
335	/// use rust_apt::new_cache;
336	/// use rust_apt::progress::AcquireProgress;
337	///
338	/// let cache = new_cache!().unwrap();
339	/// let mut progress = AcquireProgress::apt();
340
341	/// if let Err(e) = cache.update(&mut progress) {
342	///     for error in e.iter() {
343	///         if error.is_error {
344	///             println!("Error: {}", error.msg);
345	///         } else {
346	///             println!("Warning: {}", error.msg);
347	///         }
348	///     }
349	/// }
350	/// ```
351	/// # Known Errors:
352	/// * E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)
353	/// * E:Unable to lock directory /var/lib/apt/lists/
354	pub fn update(self, progress: &mut AcquireProgress) -> Result<(), AptErrors> {
355		Ok(self.ptr.update(progress.mut_status())?)
356	}
357
358	/// Mark all packages for upgrade
359	///
360	/// # Example:
361	///
362	/// ```
363	/// use rust_apt::new_cache;
364	/// use rust_apt::cache::Upgrade;
365	///
366	/// let cache = new_cache!().unwrap();
367	///
368	/// cache.upgrade(Upgrade::FullUpgrade).unwrap();
369	/// ```
370	pub fn upgrade(&self, upgrade_type: Upgrade) -> Result<(), AptErrors> {
371		let mut progress = OperationProgress::quiet();
372		Ok(self
373			.depcache()
374			.upgrade(progress.pin().as_mut(), upgrade_type as i32)?)
375	}
376
377	/// Resolve dependencies with the changes marked on all packages. This marks
378	/// additional packages for installation/removal to satisfy the dependency
379	/// chain.
380	///
381	/// Note that just running a `mark_*` function on a package doesn't
382	/// guarantee that the selected state will be kept during dependency
383	/// resolution. If you need such, make sure to run
384	/// [`crate::Package::protect`] after marking your requested
385	/// modifications.
386	///
387	/// If `fix_broken` is set to [`true`], the library will try to repair
388	/// broken dependencies of installed packages.
389	///
390	/// Returns [`Err`] if there was an error reaching dependency resolution.
391	#[allow(clippy::result_unit_err)]
392	pub fn resolve(&self, fix_broken: bool) -> Result<(), AptErrors> {
393		Ok(self
394			.resolver()
395			.resolve(fix_broken, OperationProgress::quiet().pin().as_mut())?)
396	}
397
398	/// Autoinstall every broken package and run the problem resolver
399	/// Returns false if the problem resolver fails.
400	///
401	/// # Example:
402	///
403	/// ```
404	/// use rust_apt::new_cache;
405	///
406	/// let cache = new_cache!().unwrap();
407	///
408	/// cache.fix_broken();
409	///
410	/// for pkg in cache.get_changes(false) {
411	///     println!("Pkg Name: {}", pkg.name())
412	/// }
413	/// ```
414	pub fn fix_broken(&self) -> bool { self.depcache().fix_broken() }
415
416	/// Fetch any archives needed to complete the transaction.
417	///
418	/// # Returns:
419	/// * A [`Result`] enum: the [`Ok`] variant if fetching succeeded, and
420	///   [`Err`] if there was an issue.
421	///
422	/// # Example:
423	/// ```
424	/// use rust_apt::new_cache;
425	/// use rust_apt::progress::AcquireProgress;
426	///
427	/// let cache = new_cache!().unwrap();
428	/// let pkg = cache.get("neovim").unwrap();
429	/// let mut progress = AcquireProgress::apt();
430	///
431	/// pkg.mark_install(true, true);
432	/// pkg.protect();
433	/// cache.resolve(true).unwrap();
434	///
435	/// cache.get_archives(&mut progress).unwrap();
436	/// ```
437	/// # Known Errors:
438	/// * W:Problem unlinking the file
439	///   /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb -
440	///   PrepareFiles (13: Permission denied)
441	/// * W:Problem unlinking the file
442	///   /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb -
443	///   PrepareFiles (13: Permission denied)
444	/// * W:Problem unlinking the file
445	///   /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb -
446	///   PrepareFiles (13: Permission denied)
447	/// * W:Problem unlinking the file
448	///   /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb -
449	///   PrepareFiles (13: Permission denied)
450	/// * W:Problem unlinking the file
451	///   /var/cache/apt/archives/partial/neofetch_7.1.0-4_all.deb -
452	///   PrepareFiles (13: Permission denied)
453	/// * W:Problem unlinking the file /var/log/apt/eipp.log.xz - FileFd::Open
454	///   (13: Permission denied)
455	/// * W:Could not open file /var/log/apt/eipp.log.xz - open (17: File
456	///   exists)
457	/// * W:Could not open file '/var/log/apt/eipp.log.xz' - EIPP::OrderInstall
458	///   (17: File exists)
459	/// * E:Internal Error, ordering was unable to handle the media swap"
460	pub fn get_archives(&self, progress: &mut AcquireProgress) -> Result<(), Exception> {
461		self.pkg_manager()
462			.get_archives(&self.ptr, self.records(), progress.mut_status())
463	}
464
465	/// Install, remove, and do any other actions requested by the cache.
466	///
467	/// # Returns:
468	/// * A [`Result`] enum: the [`Ok`] variant if transaction was successful,
469	///   and [`Err`] if there was an issue.
470	///
471	/// # Example:
472	/// ```
473	/// use rust_apt::new_cache;
474	/// use rust_apt::progress::{AcquireProgress, InstallProgress};
475	///
476	/// let cache = new_cache!().unwrap();
477	/// let pkg = cache.get("neovim").unwrap();
478	/// let mut acquire_progress = AcquireProgress::apt();
479	/// let mut install_progress = InstallProgress::apt();
480	///
481	/// pkg.mark_install(true, true);
482	/// pkg.protect();
483	/// cache.resolve(true).unwrap();
484	///
485	/// // These need root
486	/// // cache.get_archives(&mut acquire_progress).unwrap();
487	/// // cache.do_install(&mut install_progress).unwrap();
488	/// ```
489	///
490	/// # Known Errors:
491	/// * W:Problem unlinking the file /var/log/apt/eipp.log.xz - FileFd::Open
492	///   (13: Permission denied)
493	/// * W:Could not open file /var/log/apt/eipp.log.xz - open (17: File
494	///   exists)
495	/// * W:Could not open file '/var/log/apt/eipp.log.xz' - EIPP::OrderInstall
496	///   (17: File exists)
497	/// * E:Could not create temporary file for /var/lib/apt/extended_states -
498	///   mkstemp (13: Permission denied)
499	/// * E:Failed to write temporary StateFile /var/lib/apt/extended_states
500	/// * W:Could not open file '/var/log/apt/term.log' - OpenLog (13:
501	///   Permission denied)
502	/// * E:Sub-process /usr/bin/dpkg returned an error code (2)
503	/// * W:Problem unlinking the file /var/cache/apt/pkgcache.bin -
504	///   pkgDPkgPM::Go (13: Permission denied)
505	pub fn do_install(self, progress: &mut InstallProgress) -> Result<(), AptErrors> {
506		Ok(self.pkg_manager().do_install(progress.pin().as_mut())?)
507	}
508
509	/// Handle get_archives and do_install in an easy wrapper.
510	///
511	/// # Returns:
512	/// * A [`Result`]: the [`Ok`] variant if transaction was successful, and
513	///   [`Err`] if there was an issue.
514	/// # Example:
515	/// ```
516	/// use rust_apt::new_cache;
517	/// use rust_apt::progress::{AcquireProgress, InstallProgress};
518	///
519	/// let cache = new_cache!().unwrap();
520	/// let pkg = cache.get("neovim").unwrap();
521	/// let mut acquire_progress = AcquireProgress::apt();
522	/// let mut install_progress = InstallProgress::apt();
523	///
524	/// pkg.mark_install(true, true);
525	/// pkg.protect();
526	/// cache.resolve(true).unwrap();
527	///
528	/// // This needs root
529	/// // cache.commit(&mut acquire_progress, &mut install_progress).unwrap();
530	/// ```
531	pub fn commit(
532		self,
533		progress: &mut AcquireProgress,
534		install_progress: &mut InstallProgress,
535	) -> Result<(), AptErrors> {
536		// Lock the whole thing so as to prevent tamper
537		apt_lock()?;
538
539		let config = Config::new();
540		let archive_dir = config.dir("Dir::Cache::Archives", "/var/cache/apt/archives/");
541
542		// Copy local debs into archives dir
543		for deb in &self.local_debs {
544			// If it reaches this point it really will be a valid filename, allegedly
545			if let Some(filename) = Path::new(deb).file_name() {
546				// Append the file name onto the archive dir
547				fs::copy(deb, archive_dir.to_string() + &filename.to_string_lossy())?;
548			}
549		}
550
551		// The archives can be grabbed during the apt lock.
552		self.get_archives(progress)?;
553
554		// If the system is locked we will want to unlock the dpkg files.
555		// This way when dpkg is running it can access its files.
556		apt_unlock_inner();
557
558		// Perform the operation.
559		self.do_install(install_progress)?;
560
561		// Finally Unlock the whole thing.
562		apt_unlock();
563		Ok(())
564	}
565
566	/// Get a single package.
567	///
568	/// `cache.get("apt")` Returns a Package object for the native arch.
569	///
570	/// `cache.get("apt:i386")` Returns a Package object for the i386 arch
571	pub fn get(&self, name: &str) -> Option<Package> {
572		Some(Package::new(self, unsafe {
573			self.find_pkg(name).make_safe()?
574		}))
575	}
576
577	/// An iterator over the packages
578	/// that will be altered when `cache.commit()` is called.
579	///
580	/// # sort_name:
581	/// * [`true`] = Packages will be in alphabetical order
582	/// * [`false`] = Packages will not be sorted by name
583	pub fn get_changes(&self, sort_name: bool) -> impl Iterator<Item = Package> {
584		let mut changed = Vec::new();
585		let depcache = self.depcache();
586
587		for pkg in self.raw_pkgs() {
588			if depcache.marked_install(&pkg)
589				|| depcache.marked_delete(&pkg)
590				|| depcache.marked_upgrade(&pkg)
591				|| depcache.marked_downgrade(&pkg)
592				|| depcache.marked_reinstall(&pkg)
593			{
594				changed.push(pkg);
595			}
596		}
597
598		if sort_name {
599			// Sort by cached key seems to be the fastest for what we're doing.
600			// Maybe consider impl ord or something for these.
601			changed.sort_by_cached_key(|pkg| pkg.name().to_string());
602		}
603
604		changed
605			.into_iter()
606			.map(|pkg_ptr| Package::new(self, pkg_ptr))
607	}
608}
609
610/// Iterator Implementation for the Cache.
611pub struct CacheIter<'a> {
612	pkgs: IterPkgIterator,
613	cache: &'a Cache,
614}
615
616impl<'a> Iterator for CacheIter<'a> {
617	type Item = Package<'a>;
618
619	fn next(&mut self) -> Option<Self::Item> { Some(Package::new(self.cache, self.pkgs.next()?)) }
620}
621
622#[cxx::bridge]
623pub(crate) mod raw {
624	impl UniquePtr<PkgRecords> {}
625
626	unsafe extern "C++" {
627		include!("rust-apt/apt-pkg-c/cache.h");
628		type PkgCacheFile;
629
630		type PkgIterator = crate::raw::PkgIterator;
631		type VerIterator = crate::raw::VerIterator;
632		type PkgFileIterator = crate::raw::PkgFileIterator;
633		type PkgRecords = crate::records::raw::PkgRecords;
634		type IndexFile = crate::records::raw::IndexFile;
635		type PkgDepCache = crate::depcache::raw::PkgDepCache;
636		type AcqTextStatus = crate::acquire::raw::AcqTextStatus;
637		type PkgAcquire = crate::acquire::raw::PkgAcquire;
638
639		/// Create the CacheFile.
640		pub fn create_cache(volatile_files: &[&str]) -> Result<UniquePtr<PkgCacheFile>>;
641
642		/// Update the package lists, handle errors and return a Result.
643		pub fn update(self: &PkgCacheFile, progress: Pin<&mut AcqTextStatus>) -> Result<()>;
644
645		/// Loads the index files into PkgAcquire.
646		///
647		/// Used to get to source list uris.
648		///
649		/// It's not clear if this returning a bool is useful.
650		pub fn get_indexes(self: &PkgCacheFile, fetcher: &PkgAcquire) -> bool;
651
652		/// Return a pointer to PkgDepcache.
653		///
654		/// # Safety
655		///
656		/// The returned UniquePtr cannot outlive the cache.
657		unsafe fn create_depcache(self: &PkgCacheFile) -> UniquePtr<PkgDepCache>;
658
659		/// Return a pointer to PkgRecords.
660		///
661		/// # Safety
662		///
663		/// The returned UniquePtr cannot outlive the cache.
664		unsafe fn create_records(self: &PkgCacheFile) -> UniquePtr<PkgRecords>;
665
666		/// The priority of the Version as shown in `apt policy`.
667		pub fn priority(self: &PkgCacheFile, version: &VerIterator) -> i32;
668
669		/// Lookup the IndexFile of the Package file
670		///
671		/// # Safety
672		///
673		/// The IndexFile can not outlive PkgCacheFile.
674		///
675		/// The returned UniquePtr cannot outlive the cache.
676		unsafe fn find_index(self: &PkgCacheFile, file: &PkgFileIterator) -> UniquePtr<IndexFile>;
677
678		/// Return a package by name and optionally architecture.
679		///
680		/// # Safety
681		///
682		/// If the Internal Pkg Pointer is NULL, operations can segfault.
683		/// You should call `make_safe()` asap to convert it to an Option.
684		///
685		/// The returned UniquePtr cannot outlive the cache.
686		unsafe fn find_pkg(self: &PkgCacheFile, name: &str) -> UniquePtr<PkgIterator>;
687
688		/// Return the pointer to the start of the PkgIterator.
689		///
690		/// # Safety
691		///
692		/// If the Internal Pkg Pointer is NULL, operations can segfault.
693		/// You should call `raw_iter()` asap.
694		///
695		/// The returned UniquePtr cannot outlive the cache.
696		unsafe fn begin(self: &PkgCacheFile) -> UniquePtr<PkgIterator>;
697	}
698}