veridian_kernel/fs/
tar.rs1use alloc::{string::String, vec::Vec};
9
10use crate::{
11 error::KernelError,
12 fs::{get_vfs, Permissions},
13};
14
15const BLOCK_SIZE: usize = 512;
17
18mod field {
20 pub const NAME_OFF: usize = 0;
22 pub const NAME_LEN: usize = 100;
23
24 pub const MODE_OFF: usize = 100;
26 pub const MODE_LEN: usize = 8;
27
28 pub const SIZE_OFF: usize = 124;
30 pub const SIZE_LEN: usize = 12;
31
32 pub const TYPE_OFF: usize = 156;
35
36 pub const LINK_OFF: usize = 157;
38 pub const LINK_LEN: usize = 100;
39
40 pub const PREFIX_OFF: usize = 345;
42 pub const PREFIX_LEN: usize = 155;
43
44 pub const MAGIC_OFF: usize = 257;
46 pub const MAGIC_LEN: usize = 6;
47}
48
49fn parse_str(buf: &[u8]) -> &str {
51 let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
52 core::str::from_utf8(&buf[..end]).unwrap_or("")
53}
54
55fn parse_octal(buf: &[u8]) -> usize {
60 if !buf.is_empty() && (buf[0] & 0x80) != 0 {
63 let mut val: usize = 0;
64 for &b in &buf[1..] {
65 val = val.wrapping_shl(8) | (b as usize);
66 }
67 return val;
68 }
69
70 let s = parse_str(buf).trim();
71 if s.is_empty() {
72 return 0;
73 }
74 usize::from_str_radix(s, 8).unwrap_or(0)
75}
76
77fn is_zero_block(block: &[u8]) -> bool {
79 block.iter().all(|&b| b == 0)
80}
81
82fn ensure_parent_dirs(path: &str) -> Result<(), KernelError> {
87 let vfs = get_vfs().read();
88
89 let mut accumulated = String::new();
90 for component in path.split('/').filter(|c| !c.is_empty()) {
91 accumulated.push('/');
92 accumulated.push_str(component);
93
94 if vfs.resolve_path(&accumulated).is_ok() {
96 continue;
97 }
98
99 let (parent_path, dir_name) = if let Some(pos) = accumulated.rfind('/') {
101 if pos == 0 {
102 ("/", &accumulated[1..])
103 } else {
104 (&accumulated[..pos], &accumulated[pos + 1..])
105 }
106 } else {
107 continue;
108 };
109
110 let parent_node = vfs.resolve_path(parent_path)?;
111 let _ = parent_node.mkdir(dir_name, Permissions::default());
113 }
114
115 Ok(())
116}
117
118pub fn load_tar_to_vfs(data: &[u8]) -> Result<usize, KernelError> {
131 #[allow(unused_imports)]
132 use crate::println;
133
134 if data.len() < BLOCK_SIZE {
135 return Ok(0);
136 }
137
138 let mut offset: usize = 0;
139 let mut count: usize = 0;
140 let mut deferred_symlinks: Vec<(String, String, u32)> = Vec::new();
143
144 while offset + BLOCK_SIZE <= data.len() {
145 let header = &data[offset..offset + BLOCK_SIZE];
146
147 if is_zero_block(header) {
149 if offset + 2 * BLOCK_SIZE <= data.len()
150 && is_zero_block(&data[offset + BLOCK_SIZE..offset + 2 * BLOCK_SIZE])
151 {
152 break;
153 }
154 offset += BLOCK_SIZE;
156 continue;
157 }
158
159 let magic = parse_str(&header[field::MAGIC_OFF..field::MAGIC_OFF + field::MAGIC_LEN]);
161 if !magic.is_empty() && !magic.starts_with("ustar") {
162 offset += BLOCK_SIZE;
164 continue;
165 }
166
167 let prefix = parse_str(&header[field::PREFIX_OFF..field::PREFIX_OFF + field::PREFIX_LEN]);
169 let name_raw = parse_str(&header[field::NAME_OFF..field::NAME_OFF + field::NAME_LEN]);
170 let mode = parse_octal(&header[field::MODE_OFF..field::MODE_OFF + field::MODE_LEN]);
171 let size = parse_octal(&header[field::SIZE_OFF..field::SIZE_OFF + field::SIZE_LEN]);
172 let typeflag = header[field::TYPE_OFF];
173
174 let full_name = if prefix.is_empty() {
176 String::from(name_raw)
177 } else {
178 let mut s = String::from(prefix);
179 s.push('/');
180 s.push_str(name_raw);
181 s
182 };
183
184 let path = if full_name.starts_with('/') {
186 full_name.clone()
187 } else {
188 let mut s = String::from("/");
189 s.push_str(&full_name);
190 s
191 };
192
193 let path = if path.len() > 1 && path.ends_with('/') {
195 String::from(&path[..path.len() - 1])
196 } else {
197 path
198 };
199
200 offset += BLOCK_SIZE;
202
203 match typeflag {
204 b'5' => {
205 ensure_parent_dirs(&path)?;
207 let vfs = get_vfs().read();
209 if vfs.resolve_path(&path).is_err() {
210 let (parent_path, dir_name) = split_path(&path)?;
211 let parent = vfs.resolve_path(parent_path)?;
212 let _ = parent.mkdir(dir_name, Permissions::from_mode(mode as u32));
213 }
214 count += 1;
215 }
216 b'0' | b'\0' => {
217 if let Some(pos) = path.rfind('/') {
220 if pos > 0 {
221 ensure_parent_dirs(&path[..pos])?;
222 }
223 }
224
225 let file_data = if size > 0 && offset + size <= data.len() {
227 &data[offset..offset + size]
228 } else {
229 &[] as &[u8]
230 };
231
232 let vfs = get_vfs().read();
234 let (parent_path, file_name) = split_path(&path)?;
235 let parent = vfs.resolve_path(parent_path)?;
236
237 let _ = parent.unlink(file_name);
239
240 let node = parent.create(file_name, Permissions::from_mode(mode as u32))?;
241 if !file_data.is_empty() {
242 node.write(0, file_data)?;
243 }
244
245 count += 1;
246
247 let data_blocks = size.div_ceil(BLOCK_SIZE);
249 offset += data_blocks * BLOCK_SIZE;
250 }
251 b'2' => {
252 let link_target_raw =
257 parse_str(&header[field::LINK_OFF..field::LINK_OFF + field::LINK_LEN]);
258
259 let link_target = if link_target_raw.starts_with('/') {
261 String::from(link_target_raw)
262 } else {
263 if let Some(pos) = path.rfind('/') {
265 let parent_dir = if pos == 0 { "/" } else { &path[..pos] };
266 let mut abs = String::from(parent_dir);
267 abs.push('/');
268 abs.push_str(link_target_raw);
269 abs
270 } else {
271 let mut abs = String::from("/");
272 abs.push_str(link_target_raw);
273 abs
274 }
275 };
276
277 if let Some(pos) = path.rfind('/') {
279 if pos > 0 {
280 ensure_parent_dirs(&path[..pos])?;
281 }
282 }
283
284 let vfs = get_vfs().read();
286 match vfs.resolve_path(&link_target) {
287 Ok(target_node) => {
288 let target_size = target_node.metadata().map(|m| m.size).unwrap_or(0);
290 if target_size > 0 && target_size <= 4 * 1024 * 1024 {
291 let mut buf = alloc::vec![0u8; target_size];
292 if let Ok(bytes_read) = target_node.read(0, &mut buf) {
293 let (parent_path, file_name) = split_path(&path)?;
294 let parent = vfs.resolve_path(parent_path)?;
295 let _ = parent.unlink(file_name);
296 let node = parent
297 .create(file_name, Permissions::from_mode(mode as u32))?;
298 node.write(0, &buf[..bytes_read])?;
299 count += 1;
300 }
301 } else if target_size == 0 {
302 let (parent_path, file_name) = split_path(&path)?;
304 let parent = vfs.resolve_path(parent_path)?;
305 let _ = parent.unlink(file_name);
306 let _ =
307 parent.create(file_name, Permissions::from_mode(mode as u32))?;
308 count += 1;
309 }
310 }
311 Err(_) => {
312 deferred_symlinks.push((path.clone(), link_target, mode as u32));
314 }
315 }
316
317 let data_blocks = size.div_ceil(BLOCK_SIZE);
319 offset += data_blocks * BLOCK_SIZE;
320 }
321 _ => {
322 let data_blocks = size.div_ceil(BLOCK_SIZE);
324 offset += data_blocks * BLOCK_SIZE;
325 }
326 }
327 }
328
329 for (sym_path, target_path, sym_mode) in &deferred_symlinks {
332 if let Some(pos) = sym_path.rfind('/') {
333 if pos > 0 {
334 let _ = ensure_parent_dirs(&sym_path[..pos]);
335 }
336 }
337
338 let vfs = get_vfs().read();
339 if let Ok(target_node) = vfs.resolve_path(target_path) {
340 let target_size = target_node.metadata().map(|m| m.size).unwrap_or(0);
341 if target_size > 0 && target_size <= 4 * 1024 * 1024 {
342 let mut buf = alloc::vec![0u8; target_size];
343 if let Ok(bytes_read) = target_node.read(0, &mut buf) {
344 if let Ok((parent_path, file_name)) = split_path(sym_path) {
345 if let Ok(parent) = vfs.resolve_path(parent_path) {
346 let _ = parent.unlink(file_name);
347 if let Ok(node) =
348 parent.create(file_name, Permissions::from_mode(*sym_mode))
349 {
350 let _ = node.write(0, &buf[..bytes_read]);
351 count += 1;
352 }
353 }
354 }
355 }
356 }
357 }
358 }
359
360 if !deferred_symlinks.is_empty() {
361 let resolved = deferred_symlinks.len();
362 println!("[TAR] Resolved {} deferred symlinks", resolved);
363 }
364
365 println!("[TAR] Loaded {} entries into VFS", count);
366 Ok(count)
367}
368
369fn split_path(path: &str) -> Result<(&str, &str), KernelError> {
373 if let Some(pos) = path.rfind('/') {
374 let parent = if pos == 0 { "/" } else { &path[..pos] };
375 let name = &path[pos + 1..];
376 if name.is_empty() {
377 return Err(KernelError::FsError(crate::error::FsError::InvalidPath));
378 }
379 Ok((parent, name))
380 } else {
381 Err(KernelError::FsError(crate::error::FsError::InvalidPath))
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 fn make_tar_header(name: &str, size: usize, typeflag: u8, mode: u32) -> [u8; 512] {
392 let mut header = [0u8; 512];
393
394 let name_bytes = name.as_bytes();
396 let len = name_bytes.len().min(100);
397 header[..len].copy_from_slice(&name_bytes[..len]);
398
399 let mode_str = alloc::format!("{:07o}\0", mode);
401 header[100..108].copy_from_slice(mode_str.as_bytes());
402
403 let size_str = alloc::format!("{:011o}\0", size);
405 header[124..136].copy_from_slice(size_str.as_bytes());
406
407 header[156] = typeflag;
409
410 header[257..263].copy_from_slice(b"ustar\0");
412
413 header[263..265].copy_from_slice(b"00");
415
416 header[148..156].copy_from_slice(b" ");
419 let cksum: u32 = header.iter().map(|&b| b as u32).sum();
420 let cksum_str = alloc::format!("{:06o}\0 ", cksum);
421 header[148..156].copy_from_slice(&cksum_str.as_bytes()[..8]);
422
423 header
424 }
425
426 fn build_tar(entries: &[(&str, usize, u8, u32, &[u8])]) -> alloc::vec::Vec<u8> {
428 let mut archive = alloc::vec::Vec::new();
429 for &(name, size, typeflag, mode, data) in entries {
430 let header = make_tar_header(name, size, typeflag, mode);
431 archive.extend_from_slice(&header);
432 if !data.is_empty() {
433 archive.extend_from_slice(data);
434 let remainder = data.len() % 512;
436 if remainder != 0 {
437 let padding = 512 - remainder;
438 archive.extend(core::iter::repeat(0u8).take(padding));
439 }
440 }
441 }
442 archive.extend(core::iter::repeat(0u8).take(1024));
444 archive
445 }
446
447 #[test]
448 fn test_parse_octal_basic() {
449 assert_eq!(parse_octal(b"0000755\0"), 0o755);
450 assert_eq!(parse_octal(b"0000644\0"), 0o644);
451 assert_eq!(parse_octal(b"00000000013\0"), 11); }
453
454 #[test]
455 fn test_parse_octal_empty() {
456 assert_eq!(parse_octal(b"\0\0\0\0"), 0);
457 assert_eq!(parse_octal(b""), 0);
458 }
459
460 #[test]
461 fn test_parse_str() {
462 assert_eq!(parse_str(b"hello\0world"), "hello");
463 assert_eq!(parse_str(b"hello"), "hello");
464 assert_eq!(parse_str(b"\0"), "");
465 }
466
467 #[test]
468 fn test_is_zero_block() {
469 let zero = [0u8; 512];
470 assert!(is_zero_block(&zero));
471
472 let mut nonzero = [0u8; 512];
473 nonzero[100] = 1;
474 assert!(!is_zero_block(&nonzero));
475 }
476
477 #[test]
478 fn test_split_path() {
479 let (parent, name) = split_path("/bin/ls").unwrap();
480 assert_eq!(parent, "/bin");
481 assert_eq!(name, "ls");
482
483 let (parent, name) = split_path("/hello").unwrap();
484 assert_eq!(parent, "/");
485 assert_eq!(name, "hello");
486 }
487
488 #[test]
489 fn test_split_path_trailing_slash_fails() {
490 assert!(split_path("/foo/").is_err());
491 }
492
493 #[test]
494 fn test_make_tar_header_magic() {
495 let header = make_tar_header("test.txt", 5, b'0', 0o644);
496 let magic = parse_str(&header[257..263]);
497 assert!(magic.starts_with("ustar"));
498 }
499
500 #[test]
501 fn test_build_tar_not_empty() {
502 let tar = build_tar(&[("file.txt", 5, b'0', 0o644, b"hello")]);
503 assert!(tar.len() >= 512 + 512 + 1024);
505 }
506}