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