1use crate::utils::*;
2use crate::{Callbacks, Error, Result};
3
4use std::ffi::{c_void, CString};
5use std::fmt;
6use std::os::raw::c_int;
7use std::path::Path;
8use std::ptr::NonNull;
9
10use alpm_sys_ll::*;
11use bitflags::bitflags;
12use libloading::library_filename;
13use once_cell::race::OnceBox;
14
15extern "C" {
16 pub(crate) fn free(ptr: *mut c_void);
17}
18
19pub static LIBRARY: OnceBox<libalpm> = OnceBox::new();
20
21pub trait Library {
22 unsafe fn set_lib<P: AsRef<Path>>(&self, path: P) -> Option<&libalpm>;
23 unsafe fn load(&self) -> Option<&libalpm>;
24 unsafe fn force_load(&self) -> &libalpm {
25 self.load().expect("Failed loading libalpm")
26 }
27}
28
29impl Library for OnceBox<libalpm> {
30 unsafe fn set_lib<P: AsRef<Path>>(&self, path: P) -> Option<&libalpm> {
31 let library = libalpm::new(path.as_ref()).ok()?;
32 self.set(Box::new(library)).ok()?;
33 self.get()
34 }
35
36 unsafe fn load(&self) -> Option<&libalpm> {
37 self.get_or_try_init(|| libalpm::new(library_filename("alpm")).map(|l| Box::new(l))).ok()
38 }
39}
40
41#[allow(dead_code)]
42pub struct Alpm {
43 handle: NonNull<alpm_handle_t>,
44 pub(crate) cbs: Callbacks,
45 pub(crate) lib: &'static libalpm
46}
47
48impl std::fmt::Debug for Alpm {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("Alpm").finish()
51 }
52}
53
54unsafe impl Send for Alpm {}
56
57impl Drop for Alpm {
58 fn drop(&mut self) {
59 unsafe { self.lib.alpm_release(self.as_ptr()) };
60 }
61}
62
63#[derive(Debug, Eq, PartialEq, Copy, Clone, Ord, PartialOrd, Hash)]
64pub struct ReleaseError;
65
66impl fmt::Display for ReleaseError {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.write_str("failed to release alpm")
69 }
70}
71
72impl std::error::Error for ReleaseError {}
73
74impl Alpm {
75 #[doc(alias("alpm_initialize", "initialize"))]
76 pub fn new<S: Into<Vec<u8>>>(root: S, db_path: S) -> Result<Alpm> {
77 let mut err = alpm_errno_t::ALPM_ERR_OK;
78 let root = CString::new(root).unwrap();
79 let db_path = CString::new(db_path).unwrap();
80 let library = unsafe { LIBRARY.load().ok_or(Error::FailedLoading)? };
81
82 let handle = unsafe { library.alpm_initialize(root.as_ptr(), db_path.as_ptr(), &mut err) };
83
84 match NonNull::new(handle) {
85 None => unsafe { Err(Error::new(err)) },
86 Some(handle) => Ok(Alpm {
87 handle,
88 cbs: Callbacks::default(),
89 lib: library
90 }),
91 }
92 }
93
94 pub fn new2(root: &str, db_path: &str) -> Result<Alpm> {
95 Alpm::new(root, db_path)
96 }
97
98 pub fn release(self) -> std::result::Result<(), ReleaseError> {
99 if unsafe { self.lib.alpm_release(self.as_ptr()) } == 0 {
100 std::mem::forget(self);
101 Ok(())
102 } else {
103 std::mem::forget(self);
104 Err(ReleaseError)
105 }
106 }
107
108 pub(crate) unsafe fn from_ptr(handle: *mut alpm_handle_t) -> Alpm {
109 Alpm {
110 handle: NonNull::new_unchecked(handle),
111 cbs: Callbacks::default(),
112 lib: LIBRARY.force_load()
113 }
114 }
115
116 pub(crate) fn as_ptr(&self) -> *mut alpm_handle_t {
117 self.handle.as_ptr()
118 }
119
120 pub(crate) fn check_ret(&self, int: c_int) -> Result<()> {
121 if int != 0 {
122 Err(self.last_error())
123 } else {
124 Ok(())
125 }
126 }
127
128 pub(crate) fn check_null<T>(&self, ptr: *const T) -> Result<()> {
129 if ptr.is_null() {
130 Err(self.last_error())
131 } else {
132 Ok(())
133 }
134 }
135}
136
137pub fn version() -> &'static str {
138 unsafe { from_cstr(LIBRARY.force_load().alpm_version()) }
139}
140
141bitflags! {
142 pub struct Capabilities: u32 {
143 const NLS = alpm_caps::ALPM_CAPABILITY_NLS;
144 const DOWNLOADER = alpm_caps::ALPM_CAPABILITY_DOWNLOADER;
145 const SIGNATURES = alpm_caps::ALPM_CAPABILITY_SIGNATURES;
146 }
147}
148
149impl Default for Capabilities {
150 fn default() -> Capabilities {
151 Capabilities::new()
152 }
153}
154
155impl Capabilities {
156 pub fn new() -> Capabilities {
157 Capabilities::from_bits(unsafe { LIBRARY.force_load().alpm_capabilities() as u32 }).unwrap()
158 }
159
160 pub fn nls(self) -> bool {
161 self.intersects(Capabilities::NLS)
162 }
163
164 pub fn downloader(self) -> bool {
165 self.intersects(Capabilities::DOWNLOADER)
166 }
167
168 pub fn signatures(self) -> bool {
169 self.intersects(Capabilities::SIGNATURES)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::SigLevel;
177
178 #[test]
179 fn test_lifetime() {
180 let handle = Alpm::new("/", "tests/db").unwrap();
181 let db = handle.register_syncdb("core", SigLevel::NONE).unwrap();
182 let pkg = db.pkg("linux").unwrap();
183 let name = pkg.name();
184
185 drop(pkg);
186 drop(db);
187 assert_eq!(name, "linux");
188 }
189
190 #[test]
191 fn test_list_lifetime() {
192 let handle = Alpm::new("/", "tests/db").unwrap();
193 let db = handle.register_syncdb("core", SigLevel::NONE).unwrap();
194 let pkgs = db.pkgs();
195
196 drop(db);
197 assert!(pkgs.len() > 10);
198 }
199}