Skip to main content

pkg/backend/pkgar_backend/
mod.rs

1use std::{
2    cell::RefCell,
3    fs,
4    path::{Path, PathBuf},
5    rc::Rc,
6};
7
8use pkgar::{MergedTransaction, PackageFile, Transaction};
9use pkgar_core::PublicKey;
10
11use super::{Backend, Error};
12use crate::{
13    callback::Callback,
14    package::{RemotePackage, Repository},
15    package_state::PackageState,
16    repo_manager::RepoManager,
17    Package, PackageName, RepoPublicKeyFile,
18};
19
20/// Package backend using pkgar
21pub struct PkgarBackend {
22    /// Root path, usually "/"
23    install_path: PathBuf,
24    /// Things in "/etc/pkg/package.toml"
25    packages: PackageState,
26    /// Things in "/etc/pkg.d" and inet
27    repo_manager: RepoManager,
28    /// temporary commit
29    commits: Option<MergedTransaction>,
30    keys_synced: bool,
31    callback: Rc<RefCell<dyn Callback>>,
32}
33
34impl PkgarBackend {
35    pub fn new<P: AsRef<Path>>(install_path: P, repo_manager: RepoManager) -> Result<Self, Error> {
36        let install_path = install_path.as_ref();
37
38        let packages = PackageState::from_sysroot(install_path)?;
39
40        // TODO: Use File::lock. This only checks permission
41        packages.to_sysroot(install_path)?;
42
43        fs::create_dir_all(install_path.join(crate::PACKAGES_HEAD_DIR))?;
44
45        let callback = repo_manager.callback.clone();
46
47        Ok(PkgarBackend {
48            install_path: install_path.to_path_buf(),
49            packages,
50            repo_manager,
51            // packages_lock,
52            commits: Some(MergedTransaction::new()),
53            keys_synced: false,
54            callback,
55        })
56    }
57
58    fn add_transaction(&mut self, transaction: Transaction, src: Option<&PackageFile>) {
59        let mut commits = self
60            .commits
61            .take()
62            .unwrap_or_else(|| MergedTransaction::new());
63        commits.merge(transaction, src);
64        self.commits = Some(commits);
65    }
66
67    // reads /var/lib/packages/[package].pkgar_head
68    fn get_package_head(&self, package: &PackageName) -> Result<PackageFile, Error> {
69        let path = self
70            .install_path
71            .join(crate::PACKAGES_HEAD_DIR)
72            .join(format!("{package}.pkgar_head"));
73
74        let Some(pkg) = self.packages.installed.get(package) else {
75            return Err(Error::PackageNotInstalled(package.clone()));
76        };
77        let Some(remote) = self.packages.pubkeys.get(&pkg.remote) else {
78            return Err(Error::RepoCacheNotFound(package.clone()));
79        };
80
81        let pkg = PackageFile::new(&path, &remote.pkey).map_err(Error::from)?;
82
83        Ok(pkg)
84    }
85
86    fn remove_package_head(&mut self, package: &PackageName) -> Result<(), Error> {
87        let path = self
88            .install_path
89            .join(crate::PACKAGES_HEAD_DIR)
90            .join(format!("{package}.pkgar_head"));
91
92        fs::remove_file(path)?;
93        Ok(())
94    }
95
96    fn create_head(
97        &self,
98        archive_path: &Path,
99        package: &PackageName,
100        pubkey: &PublicKey,
101    ) -> Result<(), Error> {
102        // creates a head file
103        let head_path = self
104            .install_path
105            .join(crate::PACKAGES_HEAD_DIR)
106            .join(format!("{package}.pkgar_head"));
107
108        let mut package = PackageFile::new(archive_path, &pubkey)?;
109        package.split(&head_path, None::<&Path>)?;
110
111        Ok(())
112    }
113
114    fn sync_keys(&mut self) -> Result<(), Error> {
115        if self.keys_synced {
116            return Ok(());
117        }
118
119        for (name, map) in &mut self.repo_manager.remote_map {
120            if map.pubkey.is_none() {
121                if let Some(pubk) = self.packages.pubkeys.get(name) {
122                    map.pubkey = Some(pubk.pkey)
123                }
124            }
125        }
126
127        self.repo_manager.sync_keys()?;
128
129        self.keys_synced = true;
130        Ok(())
131    }
132}
133
134impl Backend for PkgarBackend {
135    fn install(&mut self, package: RemotePackage) -> Result<(), Error> {
136        self.sync_keys()?;
137        if package.package.version.is_empty() {
138            return Ok(()); // metapackage
139        }
140        // TODO: Actually use that specific remote
141        let (local_path, repo) = self
142            .repo_manager
143            .get_package_pkgar(&package.package.name, package.package.network_size)?;
144        let mut pkg = PackageFile::new(&local_path, &repo.pubkey.unwrap())?;
145        self.callback.borrow_mut().install_extract(&package);
146        let install = Transaction::install(&mut pkg, &self.install_path)?;
147        self.create_head(&local_path, &package.package.name, &repo.pubkey.unwrap())?;
148        self.add_transaction(install, Some(&pkg));
149        Ok(())
150    }
151
152    fn uninstall(&mut self, package: PackageName) -> Result<(), Error> {
153        if self.packages.protected.contains(&package) {
154            return Err(Error::ProtectedPackage(package));
155        }
156        self.sync_keys()?;
157
158        let mut pkg = self.get_package_head(&package)?;
159        let remove = Transaction::remove(&mut pkg, &self.install_path)?;
160        self.add_transaction(remove, Some(&pkg));
161
162        self.remove_package_head(&package)?;
163
164        Ok(())
165    }
166
167    fn upgrade(&mut self, package: &RemotePackage) -> Result<(), Error> {
168        self.sync_keys()?;
169
170        let name = &package.package.name;
171        let mut pkg = self.get_package_head(name)?;
172        let (local_path, repo) = self
173            .repo_manager
174            .get_package_pkgar(name, package.package.network_size)?;
175        let mut pkg2 = PackageFile::new(&local_path, &repo.pubkey.unwrap())?;
176        let update = Transaction::replace(&mut pkg, &mut pkg2, &self.install_path)?;
177        self.create_head(&local_path, &name, &repo.pubkey.unwrap())?;
178        self.add_transaction(update, Some(&pkg));
179        Ok(())
180    }
181
182    fn get_package_detail(&self, package: &PackageName) -> Result<RemotePackage, Error> {
183        let (toml, remote) = self.repo_manager.get_package_toml(package)?;
184
185        Ok(RemotePackage {
186            package: Package::from_toml(&toml)?,
187            remote,
188        })
189    }
190
191    /// TODO: Multiple repository support
192    fn get_repository_detail(&self) -> Result<Repository, Error> {
193        let repo_str = PackageName::new("repo".to_string())?;
194        let (toml, _) = self.repo_manager.get_package_toml(&repo_str)?;
195
196        Ok(Repository::from_toml(&toml)?)
197    }
198
199    fn get_package_state(&self) -> PackageState {
200        self.packages.clone()
201    }
202
203    fn commit_check_conflict(&self) -> Result<&Vec<pkgar::TransactionConflict>, Error> {
204        let transaction = self
205            .commits
206            .as_ref()
207            .ok_or_else(|| Error::Pkgar(Box::new(pkgar::Error::DataNotInitialized)))?;
208        Ok(transaction.get_possible_conflicts())
209    }
210
211    fn commit_state(&mut self, new_state: PackageState) -> Result<usize, Error> {
212        let mut transaction = self
213            .commits
214            .take()
215            .ok_or_else(|| Error::Pkgar(Box::new(pkgar::Error::DataNotInitialized)))?
216            .into_transaction();
217        self.callback
218            .borrow_mut()
219            .commit_start(transaction.pending_commit());
220        while transaction.pending_commit() > 0 {
221            self.callback.borrow_mut().commit_increment(&transaction);
222            if let Err(e) = transaction.commit_one() {
223                self.add_transaction(transaction, None);
224                return Err(Error::from(e));
225            }
226        }
227        self.callback.borrow_mut().commit_end();
228
229        self.packages = new_state;
230        for (k, v) in &self.repo_manager.remote_map {
231            let Some(pubkey) = v.pubkey else {
232                return Err(Error::RepoNotLoaded(k.to_string()));
233            };
234            let pk = RepoPublicKeyFile::new(pubkey);
235            self.packages.pubkeys.insert(k.to_string(), pk);
236        }
237        self.packages.to_sysroot(&self.install_path)?;
238        Ok(transaction.total_committed())
239    }
240
241    fn abort_state(&mut self) -> Result<usize, Error> {
242        let mut transaction = self
243            .commits
244            .take()
245            .ok_or_else(|| Error::Pkgar(Box::new(pkgar::Error::DataNotInitialized)))?
246            .into_transaction();
247        self.callback
248            .borrow_mut()
249            .abort_start(transaction.pending_commit());
250        while transaction.pending_commit() > 0 {
251            self.callback.borrow_mut().commit_increment(&transaction);
252            if let Err(e) = transaction.abort_one() {
253                self.add_transaction(transaction, None);
254                return Err(Error::from(e));
255            }
256        }
257        self.callback.borrow_mut().abort_end();
258        Ok(transaction.total_committed())
259    }
260}