1#[cfg(feature = "alloc")]
26extern crate alloc;
27
28#[cfg(feature = "alloc")]
29use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
30
31use crate::error::KernelError;
32
33#[cfg(feature = "alloc")]
35#[derive(Clone)]
36pub struct ConfigRecord {
37 pub path: String,
39 pub original_hash: [u8; 32],
41 pub is_user_modified: bool,
43}
44
45#[cfg(feature = "alloc")]
47pub struct PackageDatabase {
48 packages: BTreeMap<String, DbPackageRecord>,
50 db_path: String,
52 dirty: bool,
54 config_files: BTreeMap<String, Vec<ConfigRecord>>,
56}
57
58#[cfg(feature = "alloc")]
60#[derive(Clone)]
61pub struct DbPackageRecord {
62 pub name: String,
64 pub version: String,
66 pub installed_at: u64,
68 pub files_count: u32,
70 pub size_bytes: u64,
72 pub dependencies: Vec<String>,
74}
75
76#[cfg(feature = "alloc")]
77impl PackageDatabase {
78 pub fn new(db_path: &str) -> Self {
79 Self {
80 packages: BTreeMap::new(),
81 db_path: String::from(db_path),
82 dirty: false,
83 config_files: BTreeMap::new(),
84 }
85 }
86
87 pub fn load(&mut self) -> Result<(), KernelError> {
92 let vfs_lock = match crate::fs::try_get_vfs() {
93 Some(v) => v,
94 None => return Ok(()), };
96
97 let data = {
98 let vfs = vfs_lock.read();
99 match vfs.resolve_path(&self.db_path) {
100 Ok(node) => {
101 let meta = node.metadata()?;
102 let mut buf = vec![0u8; meta.size];
103 let n = node.read(0, &mut buf)?;
104 buf.truncate(n);
105 buf
106 }
107 Err(_) => return Ok(()), }
109 };
110
111 self.packages = Self::deserialize(&data)?;
112 self.dirty = false;
113 Ok(())
114 }
115
116 pub fn save(&self) -> Result<(), KernelError> {
121 if crate::fs::try_get_vfs().is_none() {
122 return Ok(());
123 }
124
125 let data = self.serialize();
126
127 if let Some(parent_end) = self.db_path.rfind('/') {
129 if parent_end > 0 {
130 let parent = &self.db_path[..parent_end];
131 ensure_directories(parent)?;
132 }
133 }
134
135 crate::fs::write_file(&self.db_path, &data)?;
136 Ok(())
137 }
138
139 pub fn record_install(&mut self, record: DbPackageRecord) {
141 self.packages.insert(record.name.clone(), record);
142 self.dirty = true;
143 }
144
145 pub fn record_remove(&mut self, name: &str) -> Option<DbPackageRecord> {
149 let removed = self.packages.remove(name);
150 if removed.is_some() {
151 self.dirty = true;
152 }
153 removed
154 }
155
156 pub fn query_installed(&self) -> Vec<&DbPackageRecord> {
158 self.packages.values().collect()
159 }
160
161 pub fn is_installed(&self, name: &str) -> bool {
163 self.packages.contains_key(name)
164 }
165
166 pub fn get(&self, name: &str) -> Option<&DbPackageRecord> {
168 self.packages.get(name)
169 }
170
171 pub fn is_dirty(&self) -> bool {
173 self.dirty
174 }
175
176 pub fn track_config_file(&mut self, package: &str, config: ConfigRecord) {
182 let configs = self.config_files.entry(String::from(package)).or_default();
183 if let Some(pos) = configs.iter().position(|c| c.path == config.path) {
185 configs[pos] = config;
186 } else {
187 configs.push(config);
188 }
189 self.dirty = true;
190 }
191
192 pub fn is_config_modified(&self, package: &str, path: &str) -> bool {
194 self.config_files
195 .get(package)
196 .and_then(|configs| configs.iter().find(|c| c.path == path))
197 .map(|c| c.is_user_modified)
198 .unwrap_or(false)
199 }
200
201 pub fn list_config_files(&self, package: &str) -> &[ConfigRecord] {
203 self.config_files.get(package).map_or(&[], |v| v.as_slice())
204 }
205
206 pub fn find_orphans(&self) -> Vec<String> {
210 let mut orphans = Vec::new();
211 for name in self.packages.keys() {
212 let is_depended_on = self
213 .packages
214 .values()
215 .any(|record| record.dependencies.iter().any(|dep| dep == name));
216 if !is_depended_on {
217 orphans.push(name.clone());
218 }
219 }
220 orphans
221 }
222
223 fn serialize(&self) -> Vec<u8> {
228 let mut buf = Vec::new();
229
230 for record in self.packages.values() {
231 Self::write_str(&mut buf, &record.name);
232 Self::write_str(&mut buf, &record.version);
233 buf.extend_from_slice(&record.installed_at.to_le_bytes());
234 buf.extend_from_slice(&record.files_count.to_le_bytes());
235 buf.extend_from_slice(&record.size_bytes.to_le_bytes());
236
237 let dep_count = record.dependencies.len() as u16;
238 buf.extend_from_slice(&dep_count.to_le_bytes());
239 for dep in &record.dependencies {
240 Self::write_str(&mut buf, dep);
241 }
242 }
243
244 buf
245 }
246
247 fn deserialize(data: &[u8]) -> Result<BTreeMap<String, DbPackageRecord>, KernelError> {
248 let mut map = BTreeMap::new();
249 let mut pos = 0;
250
251 while pos < data.len() {
252 let name = Self::read_str(data, &mut pos)?;
253 let version = Self::read_str(data, &mut pos)?;
254
255 let installed_at = Self::read_u64(data, &mut pos)?;
256 let files_count = Self::read_u32(data, &mut pos)?;
257 let size_bytes = Self::read_u64(data, &mut pos)?;
258
259 let dep_count = Self::read_u16(data, &mut pos)? as usize;
260 let mut dependencies = Vec::with_capacity(dep_count);
261 for _ in 0..dep_count {
262 dependencies.push(Self::read_str(data, &mut pos)?);
263 }
264
265 let record = DbPackageRecord {
266 name: name.clone(),
267 version,
268 installed_at,
269 files_count,
270 size_bytes,
271 dependencies,
272 };
273 map.insert(name, record);
274 }
275
276 Ok(map)
277 }
278
279 fn write_str(buf: &mut Vec<u8>, s: &str) {
281 let len = s.len() as u16;
282 buf.extend_from_slice(&len.to_le_bytes());
283 buf.extend_from_slice(s.as_bytes());
284 }
285
286 fn read_str(data: &[u8], pos: &mut usize) -> Result<String, KernelError> {
288 let len = Self::read_u16(data, pos)? as usize;
289 if *pos + len > data.len() {
290 return Err(KernelError::InvalidArgument {
291 name: "db_record",
292 value: "truncated_string",
293 });
294 }
295 let s = core::str::from_utf8(&data[*pos..*pos + len]).map_err(|_| {
296 KernelError::InvalidArgument {
297 name: "db_record",
298 value: "invalid_utf8",
299 }
300 })?;
301 *pos += len;
302 Ok(String::from(s))
303 }
304
305 fn read_u16(data: &[u8], pos: &mut usize) -> Result<u16, KernelError> {
306 if *pos + 2 > data.len() {
307 return Err(KernelError::InvalidArgument {
308 name: "db_record",
309 value: "truncated_u16",
310 });
311 }
312 let val = u16::from_le_bytes([data[*pos], data[*pos + 1]]);
313 *pos += 2;
314 Ok(val)
315 }
316
317 fn read_u32(data: &[u8], pos: &mut usize) -> Result<u32, KernelError> {
318 if *pos + 4 > data.len() {
319 return Err(KernelError::InvalidArgument {
320 name: "db_record",
321 value: "truncated_u32",
322 });
323 }
324 let val = u32::from_le_bytes([data[*pos], data[*pos + 1], data[*pos + 2], data[*pos + 3]]);
325 *pos += 4;
326 Ok(val)
327 }
328
329 fn read_u64(data: &[u8], pos: &mut usize) -> Result<u64, KernelError> {
330 if *pos + 8 > data.len() {
331 return Err(KernelError::InvalidArgument {
332 name: "db_record",
333 value: "truncated_u64",
334 });
335 }
336 let val = u64::from_le_bytes([
337 data[*pos],
338 data[*pos + 1],
339 data[*pos + 2],
340 data[*pos + 3],
341 data[*pos + 4],
342 data[*pos + 5],
343 data[*pos + 6],
344 data[*pos + 7],
345 ]);
346 *pos += 8;
347 Ok(val)
348 }
349}
350
351#[cfg(feature = "alloc")]
352impl Default for PackageDatabase {
353 fn default() -> Self {
354 Self::new("/var/pkg/db")
355 }
356}
357
358#[cfg(feature = "alloc")]
360fn ensure_directories(path: &str) -> Result<(), KernelError> {
361 use crate::fs::Permissions;
362
363 let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
364
365 let mut current_path = String::new();
366 for component in components {
367 current_path.push('/');
368 current_path.push_str(component);
369
370 if let Some(vfs_lock) = crate::fs::try_get_vfs() {
371 let perms = Permissions::from_mode(0o755);
372 let _ = vfs_lock.write().mkdir(¤t_path, perms);
373 }
374 }
375
376 Ok(())
377}